Views
Personal tools

BuildInstall

From X-Plane SDK

Jump to: navigation, search

Contents

Platform Choices And Decisions

X-Plane supports plugins for all three operating systems (OS X, Windows, and Linux). This chapter covers issues of building and packaging plugins.

Macintosh: Mach-O or CFM

Apple has recently supported two ABIs:

  • The CFM (code fragment manager) system supports only PPC code, and can be used to write a plugin that runs on OS 9 or OS X.
  • The Mach-O system casino supports PPC or x86 code (or both at the same time), but can only be used to write a plugin that runs on OS X.

X-Plane was ported to OS X (from OS 9) during the early part of the version 6 run; however, the plugin-system did not support Mach-O plugins until mid-version 7. (X-Plane itself went Mach-O for version 840.) Thus the version of X-Plane you are targeting affects the ABI you choose:

  • If you need your plugin to run on X-Plane 6, you'll need to build a CFM plugin.
  • If you need your plugin to run on Intel Macintoshes, you must build a Mach-O plugin.
  • The SDK 2.0 API is not available to CFM plugins; if you need 2.0 features, you must build a Mach-O plugin.
  • X-Plane 8 and later only run on OS X; if you don't need to run on X-Plane 6 or 7, there is no advantage to using CFM.

As a default, pick Mach-O; this will provide support to all Macintosh users and use of modern tools.

1.0 or 2.0 API

There are two revisions of the X-Plane plugin API:

  • 1.0 API is supported by X-Plane 670 and newer.
  • 2.0 API is supported by X-Plane 900 and newer ,is a superset of the 1.0 API
  • plugins is almost the same for both APIs
  • a few exceptions for Compiling and linking 2.0 plugins will be noted.
  • use 1.0 or 2.0 SDK to build a 1.0 plugin.
  • but use 2.0 SDK to build a 2.0 plugin.

Plugin Cotainers and Fat or Thin Plugin

Plugins are DLLs (dynamically linked libraries); while these have different names on different operating systems, the concept is the same: code that is linked into X-Plane while it is running. We will use the term DLL generically to mean a shared library of the appropriate type for your chosen ABI. The container types are:

  • DLLs (.dll) for Windows.
  • Shared Objects (.so) for Linux.
  • Shared Libraries (.shlb) for CFM on Mac.
  • Shared Dynamic Libraries (.dylib) for Mach-O on OS X.
  • The plugin system also allows a .dylib to be wrapped in a series of directories to form a "bundle" on OS X, but this is not recommended. Use a native shlbs or dylib.

Note that in all of these cases the plugin's extension is dictated by SDK conventions, not the native platform. So a Windows plugin is a DLL with the extension .xpl.

Thin Plugins

A "thin" plugin is the original way of packaging plugins: the plugin consists of a single DLL with the extension .xpl. All auxiliary files (image files, etc.) must be kept outside the international portfolio inc plugin. Thin plugins are the only packaging supported by the 1.0 API. (Originally thin plugins were just called "plugins".)

In order to build a thin plugin, make sure your plugin ends in the extension ".xpl".

Fat Plugins

A "fat" plugin is a folder containing one or more plugins. The plugins inside the folder have the specific names win.xpl, mac.xpl and lin.xpl. A fat plugin provides a container format that is portable across multiple operating systems; X-Plane loads only the plugin that is appropriate for the host computer and ignores the rest. The folder can also contain support files, allowing the user to install the plugin by dragging a single folder.

Fat plugins require the 2.0 API, and are the recommended way of packaging plugins that would require the 2.0 API or X-Plane 9 for other reasons.

To build a fat plugin, make sure the actual binary is inside a folder (which in turn is inside the plugins folder) and that the binary is named win.xpl for Windows, mac.xpl for OS X, or lin.xpl for Linux.

Global or Aircraft Plugin

A "global" plugin is one that is installed in the Resources/plugins folder. This is the original way to install a plugin and the only one supported by the 1.0 SDK. Global plugins must be installed directly into the plugins folder (which is in turn inside the Resources folder); sub-folders are not examined.

The 2.0 API also allows plugins to be stored with aircraft; such aircraft-based plugins are loaded when the user loads the plane (not counting multi-player use of the plane) and unloads the plugin when the user picks another aircraft.

Only fat plugins can be stored with an aircraft. An Aircraft plugin goes in a "plugins" folder that in turn is inside the root folder of your aircraft package.

Compiling Plugins

This section describes some of the issues when compiling plugins. This document is not meant to be substitute for the original documentation for your development tools, nor is it meant to be instructional in this regard. You should be able to use your chosen tool set to build DLLs before you begin writing plugins.

The simplest way to compile your plugins is to start with one of the examples, which comes with project files.

Recommended Compilers

We do not recommend any particular compiler, but there are more SDK-related resources available for the compilers used to produce the basic examples. Those compiles are:

  • OS X/Mach-O: GCC 4.01/X-Code. (X-Code is an IDE wrapper around GCC for OS X.)
  • OS X/CFM: CodeWarrior 8.
  • Windows: MS Visual Studio 6. (MSVS is an IDE that wraps around the MSVC compiler.)
  • Linux: GCC 3.3.x or 4.x.

A few warnings about CFM: while CodeWarrior 8 is the best tool available to make CFM plugins, CodeWarrior for OS X was terminated as a product, and the later versions (including version 8) had some serious bugs. Given the limited use of CFM as a deployment ABI and the necessity of Mach-O to support all new Macintoshes (with Intel CPUs), we strongly recommend against using CFM, due to the lack of high quality free development tools.

Setting Up Defined Macros

In order to use the X-Plane SDK headers, you must pre-define some macros before including any SDK headers. This is usually done by setting up the defines in your compiler settings. Most compilers accept the command-line option -D<symbol>=<value>; X-Code and Visual Studio both have project settings to predefine symbols, and in both cases they result in -D command-line options being sent to the compiler.

You must define one of the macros APL, IBM, or LIN to 1, depending on the OS you are compiling for. If no platform is defined, you will get a "platform not defined" compile error as soon as you include any SDK headers. Typically you would define the platform in the project settings or make file for each platform, so that the code can be shared between all three without modification.

Macros for the 2.0 SDK

In order to use the 2.0 APIs, you need to define the symbol XPLM200. Without this symbol, the new 2.0 SDK will default to only letting you use 1.0 APIs. This feature is designed to let you build 1.0 and 2.0 plugins from the same SDK header. If XPLM200 is not defined, you cannot accidentally use a 2.0 routine.

Including the SDK Headers

To use X-Plane SDK functionality, you must include the SDK headers, like this:

#include <XPLMProcessing.h>

In order for this to work, you must tell your compiler where to locate the header files. You must first decide where to install the header files. There are two basic choices:

  • If you work on your projects by yourself, you can pick one location on your hard drive to place the SDK and use it for all of your projects. The advantage is you will only have one copy of the SDK to update in the future.
  • If you share your projects with other developers, it is important that the SDK location be the same for all developers, and be located relative to the project. (Otherwise all developers would need the same hard drive name.) In this case, it makes sense to copy the SDK headers and libraries into the source tree of each project.

Once you have decided on an install location, you must provide your installer with an include path that tells it where the headers are. For example,

-IXPSDK/Headers/XPLM
-IXPSDK/Headers/XPWidgets

Most compilers require one include path for each directory to be searched.

OpenGL Considerations

OpenGL is not part of the SDK, but it is the main API for drawing from plugins, so you will almost certainly need it to create any kind of custom graphics. OpenGL deployment varies a bit by platform.

OpenGL on OS X for Mach-O

On OS X OpenGL is a framework; it is always available. Using OpenGL requires two steps:

  • Add the framework to your project (or use -framework OpenGL on the command line).
  • Then include the headers like this:
#include <OpenGL/gl.h>

OpenGL on OS X for CFM

The OpenGL headers for CFM applications are available as a download from Apple; you must install them and then provide an include path for your compiler, just like the SDK headers. Typically they are then included as

#include <gl.h>

OpenGL on Windows

The OpenGL SDK on Windows comes with the platform SDK. You can include them as:

#include <GL/gl.h>

once the Win32 platform SDK is correctly set up.

OpenGL on Linux

You can get the complete OGL SDK on Linux like this:

sudo apt-get install freeglut3-dev

This will install the GL headers into /usr/include, after which they are included like this

#include <GL/gl.h>

DLL Attach Functions for Windows

Windows requires a "DLLMain" function to be included in your code. It is essentially boilerplate, and typically doesn't have to do any useful work. Here is a sample DLLMain function:

#if IBM
#include <windows.h>
BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}
#endif

Symbol Visibility for Linux (GCC4 or higher)

If you are using GCC4 on Linux, the recommended way to handle symbol visibility is to use thee "visible" attributes on your functions, like this:

PLUGIN_API __attribute__((visibility("default"))) void XPluginStop() { /* ... */ }

A future revision of the SDK headers will include __attribute__((visibility("default"))) automatically. The attribute tells GCC to make your 5 plugin routines visible, even if the rest of the symbols are not.

Linking Plugins

Linking is the process of taking your compiled code and making an actual DLL file on disk that is your plugin.

Linking Terminology and Technology

Linking is the process of taking all of the compiled code and libraries and merging them into one binary. In this step, "links" are made from your code to the library functions you call.

What makes linking more confusing is that some linking is deferred until your plugin is really loaded. This is called "dynamic linking" because what your plugin links to can change each time it runs. (The term DLL comes from this important property of dynamic linking.)

For example, your plugin does not get the pointers to the XPLM functions until it runs, because the XPLM is a dynamically linked library. This allows your plugin to link against the latest XPLM available when the user runs your plugin, rather than the old XPLM that was around when you compiled your plugin.

(This ability to link against the latest XPLM is critical; even if you do not need the published API of a newer XPLM, the XPLM talks to X-Plane, and X-Plane often demands a newer XPLM. So you need to link against the new XPLM because it's the one X-Plane wants.)

For the purpose of this chapter, to keep from going insane, we will call the time when you link your plugin "link time", and the time when your plugin is loaded (and DLLs are linked to it) "load time".

Static or Dynamic

You can link to a library statically or dynamically. With a static library, the code from the library is copied into your plugin at link-time. The end user does not have to have a copy of the library, because the code is copied into your plugin. Your plugin's size will become bigger. With a dynamic library, the code from the library is not copied into your plugin. Instead the code is found at load time. Your plugin does not become bigger, and the end user must have a copy of the library.

Missing Symbols

If you use a function but do not tell the linker where to find that function, the symbol can be missing. Missing symbols can come forward in three ways:

  1. At link time if no static or dynamic library contains the symbol, you will get an "unresolved symbol" error and the link will fail and you will not get a plugin.
  2. At load time if a function that was in your copy of a dynamic library is not in the user's copy of the library, then your plugin will not load due to an unresolved external symbol.
  3. At load time if a dynamic library that you link against is not on the user's computer at all, then your plugin will not load due to a missing DLL.

These later two errors are logged in Log.txt when X-Plane starts.

Two-Level Namespaces and Load Commands

DLLs are handled differently on different operating systems. Here are some of the design choices:

Flat vs. To-level namespaces. In a flat namespace system, any function can come from any library. In a two-level name-space system, functions are tracked by both their name and the library that provides them. Example: in a flat system you link against the DLLs foo.dll and bar.dll. On your system foo.dll has a function doit(). On the user's system bar.dll has a function doit(). This is not a problem in a flat namespace system because doit() exists in one of the DLLs we loaded. In a two-level namespace system, if doit) was in foo.dll on your system, it must be in foo.dll on the user's system.

Missing Symbol Resolution. In flat namespace systems, a missing symbol will be linked against any DLL that is already loaded. In other words, if your plugin is loaded by X-Plane, and X-Plane loads bar.dll with doit(), then your plugin can get doit() even if you only load foo.dll. What's interesting here is that you don't have to load the library bar.dll to get the symbol from bar.dll (and you don't where doit() comes from, foo.dll or bar.dll). This is not allowed in any two-level namespace system, because every symbol is only found based on a specific DLL.

Memory Searching. Most two-level namespace systems will use an already loaded DLL before it loads a new DLL. If your plugin uses foo.dll to get doit() and foo.dll is already loaded before your plugin, then you get doit() from that copy of foo.dll. What's interesting about this is that the copy of foo.dll that is already loaded may not be the copy that WOULD have been loaded if it wasn't already.

Two Patterns of Linking the XPLM

The "SDK libraries" (XPLM.dll for the main functions and XPWidgets.dll for the widget functions) are linked differently by OS/ABI, but all schemes fall into one of two patterns:

Two-Level Pattern (Windows, Mac CFM)

Both Windows and Mac CFM provide two level name spaces with memory searching. In these cases linking works as follows:

  • A "stub library" is provided with the SDK download. It contains the names of the SDK functions in the DLL to be found at runtime.
  • You link against the stub library. You will have no missing symbols.
  • When the plugin is run, the loader sees you want XPLM.dll, and then notices that X-Plane has already loaded it; it then resolves the plugin symbols against XPLM.dll.

Flat-Namespace Pattern (Mac Mach-O, Linux)

Both Mac Mach-O and Linux provide flat namespaces with missing symbol resolution. In these situations work like this:

  • There is no stub library in the SDK download to link against.
  • You tell the linker to ignore undefined symbols (allowing them into your plugin).
  • When the plugin is run, the loader sees that you need XPLM symbols, and finds them in the already loaded XPLM.dll.

NOTE: Mach-O also allows for two-level name-spaces, and a strange version of memory searching. We don't use the two-level pattern for Mach-O for historical reasons: when Mach-O was first supported, X-Plane was still CFM, and the memory-searching pattern for DLLs for CFM apps did not work correctly (the Mach-O loader would get confused by the Mach-O wrapper around the CFM app.)

Input Files for Linking

OS X Mach-O

For Mach-O, you do not need to add any input files to the linker for the SDK, because we use the flat-namespace design. You may want to link against the OpenGL framework; (add it to your X-Code project or add the line

-framework OpenGL

to your makefile. You will probably need to link against the System framework.

OS X CFM

To link a CFM plugin, you'll need to link against the stub libraries XPLM.dll and XPWidgets.dll, found in the Mac subfolder of the libraries folder in the 1.0 SDK.

You may also need to link against CarbonLib. For OpenGL you'll need OpenGLLibraryStub, OpenGLMemoryStub, and OpenGLUtilityStub.

Finally, you may need a C runtime library - for CodeWarrior it is called MSL_All_Carbon.Lib.

Windows

You will need to link against XPLM.lib, found in the SDK under SDK/Libraries/Windows/ImportLibs.

If you are using OpenGL, you'll want to link against OpenGL32.lib, found in the libraries folder of the platform SDK.

Linux

You do not need to link against any files on Linux for the XPLM; for OpenGL you may want to use

-lGL -lGLU

to include the OpenGL and GLU libraries.

Other Project Settings And Link Settings

Here are some platform-specific linker settings you may need.

OS X Mach-O

The terminology for linking on OS X is convoluted, because the decision about whether to 'bundle' can refer to three different things:

  1. Every Mach-O dylib has a Mach-O "file type" which can be MH_DYNAMIC or MH_BUNDLE. Use otool -h to see what file type you have. (6=dynamic, 8 = bundle). We will refer to this as the Mach-O file type.
  2. A plugin can be in an OS X "bundle" (that is, a hierarchy of files surrounding the actual executable binary file). We will refer to this as a "bundle wrapper".
  3. X-Code targets all have fundamental 'types' that control how X-Code runs gcc and ld. We will call this an "X-Code bundle target". (There are several X-Code project templates that all make underlying bundles. (When you do Get-Info on your target, in the 'General' tab the target type is shown, but is not editable.)

Our recommendation is:

  • Do set your Mach-O type to MH_BUNDLE. If you do not do this, your plugin will not be loaded if another plugin with the same dylib ID string is already loaded.
  • Do not ship your plugin as an OS X bundle - rather, ship the actualy binary dylib file as mac.xpl or your_plugin.xpl.
  • Set your project type to dynamic library, then specifically set the Mach-O type to bundle ("Mach-O Type" under "Linking") and set the "Compatibility Version" and "Current Version" to blank. (Note that this must be done on the target level.)

You can cope with undefined symbols using -undefined dynamic_lookup (add this under "Other Linker Flags").

OS X CFM

  • Executable Name. Make sure the shared library ends with .xpl.
  • Linker/Project Type. Create a Win32 Dynamic-Link Library.
  • Linker Entry Points. Use the defaults
  • Symbol exporting. Use the PLUGIN_API macro and this will be done for us.
  • File type and creator. The file type should be shlb. The creator does not matter.
  • PEF fragment settings. We do not need to name the PEF or include any versioning. However, I recommend naming the PEF fragment because it helps debuggers work properly.

Make sure the project type is set to CFM shared library. If the compiler supports __declspec (if not, we haven't gotten this far!) then make sure to use the PLUGIN_API macro in front of the required functions (XPluginStart, etc.).

If we had to delete the PLUGIN_API macro to compile, set the project to export all globals to the shared library.

Linux

To build a C++ plugin you need two boiler plate libs: "libstdc++" - defines iostreams, STL, and a bunch of runtime stuff for C++, and "libc" - defines C runtime.

You must not link to libc statically! Doing this will cause a plugin explosion of serious proportions. My recommendation: link to libc implicitly - it seems to work.

You should link to libstdc++ statically. The problem is that among x-plane users there's a lot of users with only libstdc++5. If you dynamically link to libstdc++6 your plugin will not load because a required shared object will be missing. However static linking of libstdc++ does seem to work.

You may be able to implicitly link to libstdc++, but probably only if you have a dev environment with libstdc++5 only. This certainly does not work everywhere.

Expect to see stdio-type functions undefined in your plugin...this seems to be normal and works.

Big disclaimer: this is all based on my XSquawkBox make file, which so far has worked on multiple distros...I will update this as I learn more, but these are not necessarily the best ways to do things!

These are some linker flags for making a plugin. All flags are for gcc, not ld! (-Wl,<foo> passes <foo> to ld, so for -Wl,<foo> flags, look up <foo> in the ld man pages.)

First we do this

-shared -rdynamic -nodefaultlibs
  • -shared tells GCC to make a shared object. BTW if you want to see your undefined symbols, remove this and watch it complain. Shared objects can have undefined stuff.
  • -rdynamic is apparently undocumented, but causes ld to do something perhaps useful...on Linux it apparently maps to --export-dynamic which then exports everything to the global symbol table, which we then control wtih our map script. OPEN ISSUE: is this really necessary if we have our own link map script? Hrm...
  • -nodefaultlibs - this tells gcc to not use the standard default libs, wihch are included dynamically. We need this to prevent dynamic linking to libc and libstdc++, so that we can run on different distributions. OPEN ISSUE: should we be statically linking to lgcc then?

To link dynamic libs (OpenGL should probably be the ONLY DLL you use!!

-lGL -lGLU

Linker magic lets you use stuff statically:

# All libs will be included statically
-Wl,-Bstatic
# Add an include path for STLPort - not always in a standard place. :-(
-L/usr/include/stlport/lib
# Statically link libpng, zlib, stl-lib so user doesn't need them.  link stdc++ to avoid mismatches.
-lstlport -lpng -lz -lstdc++
# Go back to dynamic linking
-Wl,-Bdynamic
 # Always link to GL dynamically!!!
-lGL -lGLU

To tell what libs will be utilized on a system (and whether they're resolved): ldd <plugin> or ldd -v <plugin> To list all load commands: readelf -d plugin To list symbols (even stripped): readelf -s plugin For non-stripped shared objects (plugins) see also nm and objread.

To figure out if we linked properly - remove -shared from the Makefile; unresolved symbols will be output...then do make >& errors.txt. Use grep to filter, e.g. grep undefined errors.txt | grep -v XPLM | grep -v Widget - this will get out most of the XPLM symbols and reveal what's really missing.

QUESTION: is there a way to cause ld to trace link failures when dlopen is called??

Linux Symbol Visibility with GCC 4

Use -fvisibility=hidden to GCC automatically hides symbol export. See the compiling section above to make sure you set the visibility to default for symbols like XPLMPluginStart!

Linux Symbol Visibility with GCC 3

With GCC 3, visibility control is not directly available - instead you use a linker script. Use --version-script=<text file> to ld lets you send a linker script. One of the form

{
 global:
  XPluginStart;
  XPluginStop;
  XPluginEnable;
  XPluginDisable;
  XPluginReceiveMessage;
 local:
   *;
};

will let you hide all but the big five symbols. This is important because otherwise there can be a symbol collision between globals in your plugin and globals in x-plane. Your globals will overwrite x-plane, possibly crashing the sim.

Navigation
TOOLBOX
LANGUAGES
Toolbox