Mach-O Plugin Implementation Notes
This describes the way we moved from CFM to Mach-O plugins on OS X - at this point it is a historical document as we've had OS X forever, X-Plane only runs on OS X, and CFM is only for PPC anyway.
Some plugins are bundles; loading these plugins is straight forward. The regular plugin loader attempts to open a plugin both as a CFM shared library and as a CFBundle; either way the main function calls are extracted as TVectors. (CFBundle does this automatically.)
To load direct dylibs, the plugin loader will fetch the Mach-O "dlopen" APIs from the system bundle and then call dlopen on all possible plugins. Since the dlopen Mach-O APIs return Mach functions, glue is generated to make these calls into T-vectors...see glue below. (This same kind of glue is applied to function pointers returned from Mach-O plugins for calls like XPLMRegsterDrawingCallback).
Plugins Linking Back to the XPLM
In order for plugins to link to the XPLM, we must provide Mach-O symbols in the current image for all SDK functions. To do this we create wrapper frameworks - frameworks that contain the XPLM routines but call through to the CFM versions.
Framework wrappers must be frameworks because:
- Only bundles and frameworks can be loaded by CFM code using CFBundle; the dylib and NSImage APIs are not available. (Yes, we could extract them from the system bundle, but this seems excessive at best.)
- Bundles don't share their symbols with the Mach-O process namespace for resolution later.
Therefore we use a framework to set up the runtime environment, but plugins must not link against this framework; if they do they will end up with it in their required dylib load commands, and since it is not installed in a searchable location, it will not be found, and the plugin load will fail even if the wrapper framework's dylib is already in memory. (This is a limitation of X-Plane being a CFM app - app-relative frameworks are loaded from the LaunchCFM helper app.) To encourage users to not link against the framework, we don't have any headers in it.
Each plugin uses the wrapper framework's's symbols normally.
Implementation of Wrapper
Wrapper frameworks work as follows: the CFM shared library initialization function locates the framework in its own directory and loads it using CFBundle. It passes its own fragment ID to the framework, which saves it for later.
Each function in the framework wrapper contains a static function pointer that is lazily initialized by resolving the symbol in the CFM library. A small bit of glue is put in front of the symbol to make it callable from Mach-O. (This function is a CFM function and the glue makes it look like a Mach-O function.)
If a parameter that is passed from the wrapper framework to the CFM implementation of an SDK function is a function pointer, then glue is added to it in the reverse direction. (This function was a mach-o pointer and is wrapped to look like a CFM function.)
The glue to make a CFM T-vector look like a mach-o function is a small bit of code that loads the T-vector's second pointer into the RTOC and jumps; this stub must be made executable. The glue to make a mach-o function look like a T-vector is just a fake two-pointer T-vector; the first pointer is the function and the second is junk. (Mach-O code could care less what is in the RTOC.)
Wrapper Frameworks are built automatically from the XML definitions of the SDK.