Plugins built as CFM libraries are not scriptable under Mach-O built browsers
Author: Peter LubczynskiPlatform
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.