You are currently viewing a snapshot of www.mozilla.org taken on April 21, 2008. Most of this content is highly out of date (some pages haven't been updated since the project began in 1998) and exists for historical purposes only. If there are any pages on this archive site that you think should be added back to www.mozilla.org, please file a bug.



Gecko Plugin Technote

Summary

Plugins built as CFM libraries are not scriptable under Mach-O built browsers

Created on: 11/1/02

Last changed: 3/26/03

Author: Peter Lubczynski

Platform

Mac OSX

Browsers not affected

Mozilla 1.2 and earlier Netscape 7.x, 6.x

Browsers affected:

Mozilla 1.3, Camino, and other applications which use Gecko based versions of Mozilla 1.3 and later.

Background

Mozilla on Mac OS X can be built in two different executable formats. Versions prior to 1.3, and applications based on them such as Netscape 7, used the legacy CFM format, built with Metrowerks CodeWarrior. With Mozilla 1.3, we moved to a Mach-O build, built with gcc 3.1, for the benefits of improved runtime performance, true pre-emptive threads, and a more easily maintained build system.

The executable format of the browser is of relevance to the plugin, since NPAPI plugins can be made scriptable in Mozilla with XPConnect through nsIClassInfo/NPP_GetValue. This technique exposes C++ interfaces implemented in the plugin to the browser, and, possibly, vice versa.

Problems

The Mach-O and CFM formats use different calling conventions when calling though function pointers. CFM uses TVectors while Mach-O uses function pointers. See this page for more information.

In addition, the C++ ABI (application binary interface) is different between CFM and Mach-O, and also changed between gcc2 and gcc3. Both the layout of vtables, and name mangling changed. This implies that scriptable plugins must be compiled with a compiler whose C++ ABI matches that of the browser, since the browser calls into C++ code of the plugin directly, and the plugin can obtain and call C++ interfaces of the browser.

Work around

No changes need to be made for simple NPAPI plugins that don't require scriptability. Mach-O browsers can load CFM code fragments, either as standalone shared libraries, or, preferably, packaged into a bundle (using the CFBundle API). Also, since the NPAPI uses simple C functions passed through a table, a Mach-O built browser is able to translate NPP plugin CFM TVectors into regular function pointers and NPN browser functions pointers into TVectors. This allows for CFM plugins that only use the NPAPI to work under Mach-O built browsers without being recompiled.

Scriptable plugins will need to be recompiled in full, or in part, to work with Mach-O browsers. See below for more details.

Mach-O plugins and the NPAPI

For compatibility, the NPAPI still uses TVectors in the function table. When recompiling your plugin for Mach-O, be sure to expect the NPAPI function table in the right format. You can use the macros PLUGIN_TO_HOST_GLUE and HOST_TO_PLUGIN_GLUE to assist in converting the functions passed in the tables in your main function. Here is some glue code from the Default Plugin:

#if TARGET_RT_MAC_MACHO

// glue for mapping outgoing Macho function pointers to TVectors
struct TFPtoTVGlue{
    void* glue[2];
};

struct {
    TFPtoTVGlue     newp;
    TFPtoTVGlue     destroy;
    TFPtoTVGlue     setwindow;
    TFPtoTVGlue     newstream;
    TFPtoTVGlue     destroystream;
    TFPtoTVGlue     asfile;
    TFPtoTVGlue     writeready;
    TFPtoTVGlue     write;
    TFPtoTVGlue     print;
    TFPtoTVGlue     event;
    TFPtoTVGlue     urlnotify;
    TFPtoTVGlue     getvalue;
    TFPtoTVGlue     setvalue;

    TFPtoTVGlue     shutdown;
} gPluginFuncsGlueTable;

static inline void* SetupFPtoTVGlue(TFPtoTVGlue* functionGlue, void* fp)
{
    functionGlue->glue[0] = fp;
    functionGlue->glue[1] = 0;
    return functionGlue;
}

#define PLUGIN_TO_HOST_GLUE(name, fp) (SetupFPtoTVGlue(&gPluginFuncsGlueTable.name, (void*)fp))

// glue for mapping netscape TVectors to Macho function pointers
struct TTVtoFPGlue {
    uint32 glue[6];
};

struct {
    TTVtoFPGlue             geturl;
    TTVtoFPGlue             posturl;
    TTVtoFPGlue             requestread;
    TTVtoFPGlue             newstream;
    TTVtoFPGlue             write;
    TTVtoFPGlue             destroystream;
    TTVtoFPGlue             status;
    TTVtoFPGlue             uagent;
    TTVtoFPGlue             memalloc;
    TTVtoFPGlue             memfree;
    TTVtoFPGlue             memflush;
    TTVtoFPGlue             reloadplugins;
    TTVtoFPGlue             getJavaEnv;
    TTVtoFPGlue             getJavaPeer;
    TTVtoFPGlue             geturlnotify;
    TTVtoFPGlue             posturlnotify;
    TTVtoFPGlue             getvalue;
    TTVtoFPGlue             setvalue;
    TTVtoFPGlue             invalidaterect;
    TTVtoFPGlue             invalidateregion;
    TTVtoFPGlue             forceredraw;
} gNetscapeFuncsGlueTable;

static void* SetupTVtoFPGlue(TTVtoFPGlue* functionGlue, void* tvp)
{
    static const TTVtoFPGlue glueTemplate = { 0x3D800000, 0x618C0000, 0x800C0000, 0x804C0004, 0x7C0903A6, 0x4E800420 };

    memcpy(functionGlue, &glueTemplate, sizeof(TTVtoFPGlue));
    functionGlue->glue[0] |= ((UInt32)tvp >> 16);
    functionGlue->glue[1] |= ((UInt32)tvp & 0xFFFF);
    ::MakeDataExecutable(functionGlue, sizeof(TTVtoFPGlue));
    return functionGlue;
}

#define HOST_TO_PLUGIN_GLUE(name, fp) (SetupTVtoFPGlue(&gNetscapeFuncsGlueTable.name, (void*)fp))

#else

#define PLUGIN_TO_HOST_GLUE(name, fp) (fp)
#define HOST_TO_PLUGIN_GLUE(name, fp) (fp)

#endif /* TARGET_RT_MAC_MACHO */

#ifdef __GNUC__
// gcc requires that main have an 'int' return type
int main(NPNetscapeFuncs* nsTable, NPPluginFuncs* pluginFuncs, NPP_ShutdownUPP* unloadUpp);
#else
NPError main(NPNetscapeFuncs* nsTable, NPPluginFuncs* pluginFuncs, NPP_ShutdownUPP* unloadUpp);
#endif

#ifdef __GNUC__
DEFINE_API_C(int) main(NPNetscapeFuncs* nsTable, NPPluginFuncs* pluginFuncs, NPP_ShutdownUPP* unloadUpp)
#else
DEFINE_API_C(NPError) main(NPNetscapeFuncs* nsTable, NPPluginFuncs* pluginFuncs, NPP_ShutdownUPP* unloadUpp)
#endif
{
  ...
  gNetscapeFuncs.version      = nsTable->version;
  gNetscapeFuncs.size         = nsTable->size;
  gNetscapeFuncs.posturl      = (NPN_PostURLUPP)HOST_TO_PLUGIN_GLUE(posturl, nsTable->posturl);
  gNetscapeFuncs.geturl       = (NPN_GetURLUPP)HOST_TO_PLUGIN_GLUE(geturl, nsTable->geturl);
  ...
  pluginFuncs->version        = (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR;
  pluginFuncs->size           = sizeof(NPPluginFuncs);
  pluginFuncs->newp           = NewNPP_NewProc(PLUGIN_TO_HOST_GLUE(newp, Private_New));
  pluginFuncs->destroy        = NewNPP_DestroyProc(PLUGIN_TO_HOST_GLUE(destroy, Private_Destroy));
  ...

  *unloadUpp = NewNPP_ShutdownProc(PLUGIN_TO_HOST_GLUE(shutdown, Private_Shutdown));
}

Scripting Solution

If your plugin wishes to support XPConnect scripting, we are not able to fix up C++ objects of different ABIs. New values for NPPVpluginScriptableInstance are generated in Mach-O builds so that older CFM plugins do not hand back incompatible objects

A possible workaround to the problem of supporting both types of Mac OS X browsers in a single plugin is that a plugin can choose to have a second library located in their bundle just implementing their scripting interface and compiled for the opposite format. When a Mach-O browser makes a request for the pointer to the scripting interface class through NPP_GetValue, the plugin can dynamically load the second library and proxy that request with the glue code above.

The first step in making a backwards compatible scripting plugin is creating a separate library to be compiled by the alternate compiler. You'll need to re-implement your nsIClassInfo scripting class and then export a C function for calling its constructor such as:

class nsScriptingPeer : public nsIClassInfo,
                        public acmeIScriptingInterface

extern "C" void * PLUGIN_GetScriptablePeer()
{
  nsScriptablePeer *raw = new nsScriptablePeer();
  if (raw)
    raw->AddRef();
  return raw;
}

Since you are separating libraries, you may also want to pass in the browser's memory allocator and other NPN functions for use by script callbacks. Recall these have been wrapped with TVector glue code and are only valid while your plugin is loaded. If you have any callbacks into your plugin, you'll need to take note of the ABI and factor as needed.

You'll want to package this library in a bundle so that you plugin appears as a single item in the Finder. Recall that on Mac OS X, a bundle is really a set of directories. Quicktime and the MRJPlugin are bundles. Place your second scripting library in a folder in the bundle.

Next, your plugin's NPP_GetValue will need to detect browsers of the alternate ABI. A different numerical value will be used for NPPVpluginScriptableInstance. You can determine what ABI is needed by xor or and'ing with NP_ABI_GCC3_MASK and NP_ABI_MACHO_MASK. Be sure you have the updated npapi.h. For example, a Mach-O plugin can detect a CFM browser with:

(NPPVpluginScriptableInstance ^ (NP_ABI_GCC3_MASK | NP_ABI_MACHO_MASK))

You can then use GetDiskFragment to dynamically load the library:

myErr = GetDiskFragment(&myFSSpec, 0, kCFragGoesToEOF, lib, 
               kReferenceCFrag, &myConnID, (Ptr*)&myMainAddr, 
               myErrName);

You'll then need to locate your exported C function in the second library:

typedef void *(*PLUG_GetScriptablePeer_func)();
PLUGIN_GetScriptablePeer_func getPeer;

myErr = FindSymbol(myConnID, "\pPLUGIN_GetScriptablePeer", &getPeer, &symClass);

Then this C function needs to be wrapped in one of the glue code HOST_TO_PLUGIN or PLUGIN_TO_HOST macros used for the NPP or NPN functions above so that it can be called by code compiled with the other compiler. Anytime you go between CFM or Mach-O boundries like this, you'll need to use glue code. Finally, call this function to instantiate your class and pass back the resulting pointer to the browser. Since this is a reference counted object, it will be destroyed automatically.