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.xBrowsers 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.