topic: Plugin SDK

Building and Installing Plugins

Note: This article is for plugin developers. If you’re a user just trying to install a plugin, please see the “Expanding X-Plane” section of the X-Plane manual. (The exact method of installation depends on the plugin, but for many plugins, you can simply drop the plugin directory into your Resources\plugins\ directory.)


Platform Choices & Decisions

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

Which API to Use

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

  • 1.0 API is supported by X-Plane 6.70 and newer.
  • 2.0 API is supported by X-Plane 9.00 and newer, is a superset of the 1.0 API
  • 2.1 API is supported by X-Plane 10.00 and newer, is a superset of the 2.0 API
  • 3.0 API is supported by X-Plane 11.10 and newer, is a superset of the 2.1 API

Plugins are almost the same for all APIs; a few exceptions for compiling and linking 2.0+ plugins will be noted.

You can use the latest SDK download to build your plugin no matter what version of the API you are targeting. By default your plugin will only be able to use 1.0 APIs and run on any version of X-Plane.

If you define the symbol XPLM200, then the 2.0 API will be available to your code, and your plugin will only work on X-Plane 9 and later.

Likewise, if you define the symbol XPLM210, you’ll have access to the 2.1 API and your plugin will only work on X-Plane 10 and newer.

Finally, if you define the symbol XPLM300, you’ll get access to the 3.0 API, and your plugin will work X-Plane 11.10 and newer.

If you’re just starting out, we recommend using the SDK 3.0 API and defining XPLM200, XPLM210, and XPLM300.

If you’re building a backward-compatible plugin (say, for both X-Plane 10 and 11), you can link against the 2.0 API and use the XPLMFindSymbol() utility to optionally fetch newer APIs. If they are not present, your plugin will receive a NULL return value and can take a fallback path.

Plugin Containers

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 Dynamic Libraries (.dylib) for macOS

Note that in all of these cases the plugin’s extension is .xpl, as dictated by SDK conventions, not the native platform.

Packaging Thin Plugins

A “thin” plugin is the original way of packaging plugins: the plugin consists of a single DLL with the extension .xpl, which is dropped into X-Plane’s Resources\plugins\ directory. All auxiliary files (image files, etc.) must be kept outside the plugin. Thin plugins are the only packaging supported by the 1.0 API. (Originally thin plugins were just called “plugins”.)

The obvious downsides of this are that you have to distribute a different plugin for each operating system, and you don’t get a plugin “home” directory to pack your resources.

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

Packaging Fat Plugins (XPLM 2.0 API)

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.

The recommended layout for a fat plugin is:

plugin folder
 mac.xpl <- mac dylib with multiple code architectures: ppc, i386, x86_64
 32
 win.xpl <- 32-bit (win32) windows dll
 lin.xpl <- 32-bit (i386) Linux shared object
 optional other 32-bit dlls needed
 64
 win.xpl <- 64-bit (x64) windows dll
 lin.xpl <- 64-bit (x86_64) Linux shared object
 optional other 64-bit dlls needed
 other files for the plugin (pngs, etc.) in any sub folders as desired.
Packaging Fat Plugins (XPLM 2.1 API)

New in X-Plane 10 are the 32-bit and 64-bit variants of fat plugins. In this case, the plugin packaging looks like this:

  • my_plugin/
    • 64/
      • mac.xpl
      • win.xpl
      • lin.xpl
    • 32/
      • mac.xpl
      • win.xpl
      • lin.xpl

(Note that you don’t need to ship both 32- and 64-bit variants of your plugins to use this packaging.)

Packaging Fat Plugins (XPLM 3.0 API)

As of X-Plane 11.10, we support one more format for packaging plugins. The downside to the 2.1 format is that all plugins are named [platform].xpl, which makes debugging tools hard, if not impossible, to use with shipping plugins. (They assume that all DLLs will have unique names, and therefore get confused if you have multiple plugins loaded that are both called, e.g., win.xpl.)

For that reason, as of XPLM 3.0, you can package plugins like this:

  • my_plugin/
    • mac_x64/
      • my_plugin.xpl
    • win_x64/
      • my_plugin.xpl
    • lin_x64/
      • my_plugin.xpl

(Note, of course, that X-Plane 11 supports only 64-bit plugins.)

Global, Aircraft, or Scenery 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.

The 2.1 API also allows plugins to be stored with a scenery pack; such scenery-based plugins are loaded at startup with the global plugins, so be sure to keep resource use to zero when the user is not in your scenery area.

Only fat plugins can be stored with an aircraft or scenery pack. An aircraft plugin goes in a “plugins” folder that in turn is inside the root folder of your aircraft package. A scenery plugin goes into a “plugins” folder that in turn is inside the root folder of your scenery 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 come with project files and perform all the setup described below.

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 compilers are:

  • macOS: LLVM (via Xcode)
  • Windows: Visual Studio 2010 Express or Visual Studio 2015
  • Linux: GCC 4.x

The sample code provides templates that support these compilers. One way to get started is to download a sample and then replace the code while keeping the project.

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 definitions (a.k.a. preprocessor macros) in your compiler settings. Most compilers accept the command-line option -D<symbol>=<value>; Xcode 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 and newer SDK APIs

In order to use the 2.0 APIs, you’ll need to define the symbol XPLM200. To the the 2.1 APIs, you’ll need to also define XPLM210, and for the 3.0 APIs, you’ll need to define all three of XPLM200, XPLM210, and XPLM300.

(This feature is designed to let you build plugins that link against old versions of the SDK from the same SDK headers. If newer symbols are not defined, you cannot accidentally use a newer 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/CHeaders/XPLM
-IXPSDK/CHeaders/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.

On macOS, OpenGL is a framework, and it is always available. Using OpenGL requires two steps:

  1. Add the framework to your project in Xcode (or use -framework OpenGL on the command line).
  2. Include the headers like this:
#include <OpenGL/gl.h>

For Windows and Linux, include OpenGL like this:

#include <GL/gl.h>

On Linux, you may have to first install a package, e.g.:

sudo apt-get install freeglut3-dev

(Specific instructions for your distro may vary.)

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 (GCC4 or higher)

For GCC-based environments (command-line Linux), the default behavior is to export all non-static C functions out of your plugin. This is not what you want, and can cause serious compatibility problems for 32-bit plugins. To get around this, use

-fvisibility=hidden

on your compiling command line.

The macro PLUGIN_API (defined by XPLMDefs.h) automatically marks a symbol as exported, so yo can do this:

PLUGIN_API void XPluginStop() { /* ... */ }

and your plugin will work correctly.

Linking Plugins

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

Linking on Windows

You will need to link against XPLM.lib, found in the SDK under SDK/Libraries/Win. Link against:

  • 32bit plugins: XPWidgets.lib, XPLM.lib
  • 64bit plugins: XPWidgets_64.lib, XPLM_64.lib

If you are using OpenGL, you’ll also want to link against OpenGL32.lib.

We recommend you set all MSVC settings to avoid depending on external DLLs other than the CRT runtime; these DLLs may not be present on destination machines, and can cause your plugin load to fail. Linking the CRT runtime statically however will cause problems for users who have a lot of plugins installed and run out of TLS slots when loading plugins.

  • C++ Code Generation: set the runtime to “multi-threaded DLL”, not “multi-threaded.”
  • General: do not use common language runtime support or MFC.

Linking on macOS

You will need to add XPLM.framework and XPWidgets.framework from the SDK to your plugin; you may also need to add the system frameworks OpenGL and possibly System or CoreFoundation to your plugin depending on what Mac settings you use.

Do not use the options “-undefined_warning” or “-flat_namespace” – these are no longer needed and not recommended.

 

Linking on Linux

There are no link libraries on Linux for the SDK; instead pass the command-line option

-shared -rdynamic -nodefaultlibs -undefined_warning

to the linker. This will let you link despite not having XPLM symbols defined. To include libraries like OpenGL use this:

-lGL -lGLU

Finally, you may need to use a linker-script for your 32-bit plugin to avoid symbol collisions. To do this, create a file like this:

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

and then refer to it on the command line like this:

-Wl,--version-script=<text file>
Comments Off on Building and Installing Plugins

Developing Plugins

A plugin is executable code that runs inside X-Plane, extending what X-Plane does. Plugins are modular, allowing developers to extend the simulator without having to have the source code to the simulator. This article describes the basics of what a plugin is, how it works, and describes how we write one.

If you are an experienced programmer, you may already be familiar with some of the material presented here. If you have not done modular programming using DLLs, we introduce the concepts and also provide step-by-step examples of writing a very basic plugin.

What a Plugin is and What Plugins Can Do

Before discussing how to write a plugin, we will describe to some extent what it can do. You may not need a plugin for what you are trying to accomplish, and a plugin may not be able to accomplish what you are trying to do.

Plugins are executable code that are run inside X-Plane. Plugins allow you to extend the flight simulator’s capabilities or to gain access to the simulator’s data. Plugins are different from conventional programs in that they are not complete programs in themselves, but rather program fragments that get added to X-Plane while it runs. Because plugins run “inside” the simulator, they can accomplish things that a standalone program might not be able to. But since plugins are not full programs, they have limitations that normal programs do not.

Plugins can:

  • Run code from inside the simulator, either continually (for example, once per flight cycle) or in response to events inside the simulator. For example, a plugin can run constantly inside the simulator and log data to a file.
  • Read data from the simulator. For example, a plugin can continually read the values of the flight instruments and send them over the network.
  • Modify data in the simulator, changing its actions or behavior. For example, a plugin can change the aircraft’s position, or replace the simulator’s flight model entirely.
  • Create user interface inside the sim. For example, a plugin can create popup windows with instruments or dialog boxes.
  • Control various subsystems of the simulator. (These APIs are discussed in more detail below.)

Some parts of the simulator are controlled simply by writing data to the simulator. For example, to set the elevation of the cloud bases, we simply set that variable in the sim. Other subsystems require more complex interaction and have specific APIs. For example, there is an API to program the simulator’s Flight Management System.

Plugins and DLLs

Plugins are DLLs; an understanding of DLLs is necessary to understand how plugins work.

About DLLs

A dynamically linked library (DLL) is a set of compiled functions or procedures and their associated variables. DLLs are different from normal programs in a few key ways. A DLL does not contain a “main” function that is run once to run the entire program. Instead the DLL contains lots of functions, and another program uses the DLL by calling these functions.

A DLL contains a directory of the functions inside. Other applications can read that directory to find the function they are looking for. The functions in this directory are known as the exported functions because the DLL is said to “export” them to other programs. Not all of the functions in the DLL are exported; functions that are not exported are said to be internal. A program can only directly call the exported functions in a DLL, because it must find these functions through the directory. But the exported functions in the DLL can then call the internal functions.

DLLs are powerful because the program that uses the DLL finds and runs the code in the DLL when it is executed, not when it is compiled. Consider the case of a set of math functions for a calculator. If we simply compile the math functions into our program, then when we change the math programs (for example, to make them faster or more accurate), we have to recompile our whole program. If we know that the math functions are likely to change, we can put them into a DLL and have the calculator program find these functions when it runs. Since the calculator finds the math functions every time it runs, we can create a new DLL with the same function names but better implementations and substitute it for the old DLL. The calculator function will not care as long as the functions have the same names and take the same inputs. This allows us to upgrade or change the behavior of our calculator program without recompiling it.

The example above is a bit contrived, but what if one company makes the calculator program and another company makes the math functions? The company that makes the math functions might not have the code for the calculator and might not be able to recompile it, so building a single big application would not be acceptable. Furthermore, the company that makes the math functions might not want the company that makes the calculator to have their source code. DLLs address both of these problems. The company that makes the math functions can provide new math functions to the calculator company without needing to recompile the calculator. The company that makes the calculator can use the math functions without having access to their code.

A program uses a DLL by linking to it. This happens when the program is executed. When a program links to a DLL, it examines the DLLs directory and finds all of the exported functions it needs. There are two kinds of linking: hard-linking (also known as strong linking) and weak-linking (also known as soft linking). When a program is hard-linked to a DLL, the program must find every function it needs in the DLLs directory to run. If the DLL is missing functions, the program will not execute. When a program is weak-linked to a DLL, the program can run even if it does not find all of the functions it needs.

DLLs can also link to other DLLs. In this way a chain of DLLs can be created. For example, the company that makes the math functions might use yet another DLL by another company that just does some very basic calculations.

DLLs are not full programs; they do not run at the same time as programs, nor do they have their own memory. They are simply libraries of code. That code may share data with the host program if desired. For example, the calculator program may pass the math library the address of an internal buffer and then the math program may fill the buffer with numbers.

If multiple DLLs or programs link to one DLL, that one DLL that is being linked to is only loaded once, and may only have one set of global variables. This allows DLLs to communicate with each other. For example, several DLLs that the calculator program uses might all use one date-computing DLL that can figure out things like what day of the week it was three years ago. If that date-computing DLL has an internal variable for the current day of the week, all DLLs linking to it will see it. If one DLL changes that variable, the variable will be changed for all DLLs. In this way DLLs can communicate with each other by sharing the same data.

Plugins and the Plugin Manager are DLLs

The X-Plane plugin system is based on DLLs. The central component of this system is the X-Plane Plugin Manager (or XPLM). The XPLM is a library of code (compiled as a DLL!) that manages plugins. X-Plane links to the XPLM and all plugins link to the XPLM. The XPLM then serves as the central hub in the plugin system.

Plugins contain the code that will run inside the simulator. They export the functions that the XPLM can call, and the XPLM searches the directory of the plugin DLL to find them.

Likewise, the XPLM contains the functions that the plugin needs to change the sim, read data, create user interface, etc. The XPLM exports these functions into its DLL directory so the plugin can find them and call them.

Plugins never communicate directly with X-Plane; they always goes through the XPLM. For all practical purposes, the XPLM is the simulator to the plugin. Plugins also never talk to other plugins directly; instead, they sends messages via the XPLM. Communicating with other plugins is covered in the article “Working with Other Plugins.” The details of how the XPLM interacts with the simulator are covered in the appendix “XPLM Architecture.” We do not need to know how the XPLM and X-Plane interact to write a plugin.

 

Plugin Concepts

The Parts of the Plugin System

There are four main parts to the plug-in system:

  1. X-Plane. X-Plane is the host application. All plugins run within the memory space of X-Plane.
  2. The X-Plane Plugin Manager (XPLM). The XPLM is a DLL that acts as the central hub for all plugin activity. The XPLM communicates with X-Plane.
  3. Plugins. Plugins are DLLs that communicate with the XPLM. Plugins do not talk directly to the simulator. Plugins modify the simulator’s behavior by calling functions within the XPLM library to make the simulator do things or to change the simualtor’s data.
  4. Other libraries. Plugins can also use other libraries or DLLs that contain useful functionality.

Plugin Signatures

Each plugin has a signature. The plugin tells the XPLM its signature when it is first initialized. This signature uniquely identifies the plugin and differentiates it from all other plugins. Signatures are built based on left-to-right keyword combining, starting with the organization responsible for creating the plug-in. For example, if XYZResearch made a plug-in to measure engine performance as part of its flight diagnostics package, it might use a signature like:

XYZResearch.diagnostics.engine_performance_meter

You can use any string you wish for the plugin’s signature, but you should pick the first string based on the organization (be it commercial or for shareware) so that you don’t accidentally pick the same name as another plugin. (If the XPLM ever encounters multiple plugins installed with the same signature, it will only load one of them.)

Enabling Plugins

A plugin may be enabled or disabled. An enabled plugin will have its callbacks called (if it has any that need to be called), but a disabled callback will not. A disabled plugin cannot affect the sim since its callbacks are not called. Disabling provides a way for users to turn off a plugin that they do not want to use or that is being problematic. A dialog box in the simulator allows users to enable and disable plugins.

All plugins start out disabled when they are loaded. Then they are each enabled one by one when the sim is ready to start. Plugins are disabled before they are unloaded when the user quits the simulator. If a user previously disabled a plugin before quitting the simulator or disables all plugins on startup (by holding down the shift key while the simulator is started), plugins will start disabled and stay disabled until the user enables them.

A plugin will receive a callback when it is enabled and disabled, but does not necessarily need to do anything in response to this callback. Windows will automatically be hidden, menu items disabled, and timers paused when a plugin is disabled. Plugins that allocate other resources might want to free them when disabled. For example, if the plugin communicates with a server over the internet, it might want to disconnect from the server when disabled and reconnect when enabled.

Anatomy of a Plugin

This section describes how a plugin is set up in detail.

A plugin is a DLL with a number of functions. Among these are various callbacks that the XPLM calls to notify the plugin of events, as well as any helper functions, utilities, etc. that the plugin needs.

Callbacks in plugins come in two flavors:

  1. Required callbacks are functions that the plugin exports as a DLL. The XPLM finds these functions and calls them when appropriate. The plugin must export all five required callbacks below or else it will not load properly. You do not necessarily have to do anything in the callbacks; they can be empty functions. The XPLM finds the required callbacks by searching the DLLs directory.
  2. Registered callbacks are functions that you pass back to the XPLM to access additional simulator functionality. For example, if you create a window, you provide a callback function that will then be called when the user clicks in the window. The registered functions do not need to be exported from the DLL; instead, you pass a pointer to the function directly to the XPLM.

You can register callbacks from the required callbacks or from other callbacks. For example, you can register a callback that will be called in 5 minutes when the plugin is initialized. Then five minutes later in that callback, you can create a window and register a callback for when the window is clicked.

Programming a plugin is different from programming a regular application. In a regular application, we write a main program that is run once from start to finish. In a plugin, lots of small functions are called at different times. In this way, programming a plugin is similar to event-driven UI programming.

The Required Callbacks

There are five required callbacks that a plugin must implement, all of which are called by the XPLM:

  • XPluginStart(). This is called when the plugin is first loaded. You can use it to allocate any permanent resources and register any other callbacks you need. This is a good time to set up the user interface. This callback also returns the plugin’s name, signature, and a description to the XPLM.
  • XPluginEnable(). This is called when the plugin is enabled. You do not need to do anything in this callback, but if you want, we can allocate resources that we only need while enabled.
  • XPluginDisable(). This is called when the plugin is disabled. You do not need to do anything in this callback, but if we want, you can deallocate resources that are only needed while enabled. Once disabled, the plugin may not run again for a very long time, so you should close any network connections that might time out otherwise.
  • XPluginStop(). This is called right before the plugin is unloaded. You should unregister all of the callbacks, release all resources, close all files, and generally clean up.
  • XPluginReceiveMessage(). This is called when a plugin or X-Plane sends the plugin a message. See the article on “Interplugin communication and messaging” for more information. The XPLM notifies you when events happen in the simulator (such as the user crashing the plane or selecting a new aircraft model) by calling this function.

Registering Additional Callbacks

Many parts of the SDK API require you to register additional callbacks. A couple examples:

  • To create a menu item, you register a callback that will be called when the user clicks on the menu item. This callback can then do what the menu item says. (See the XPLMMenus header for more info.)
  • To create a window, you register a callback that will be called when the user clicks in the window. You’ll also need to register a callback to handle drawing the window. (See XPLMCreateWindowEx() for more info.)

You often want to register these additional callbacks from your XPluginStart() callback and unregister them from XPluginStop().

A typical session of the simulator might go like this:

  1. The user starts the simulator.
  2. The simulator (via the XPLM) loads the plugin and calls your XPluginStart().
  3. Your plugin creates a menu item and registers a callback for it.
  4. The simulator then calls your XPluginEnable() function to notify you that the plugin is now enabled.
  5. The user clicks your menu item, so the simulator calls your XPLMMenuHandler_f callback.
  6. Your callback function then does something—for example, it might write some data to a file.
  7. The user quits the simulator.
  8. The simulator calls your XPluginDisable() function and then your XPluginStop() function.
  9. Your plugin DLL is unloaded and the simulator quits.

How Plugins Interact with X-Plane

Plugins interact with the simulator by calling functions in the XPLM DLL. These functions are defined in the header files that start with XPLM. For instance:

  • If the plugin wants to read or write data from or to the simulator, it uses the APIs defined in XPLMDataAccess.
  • If the plugin wants to create a user interface, it uses the APIs defined in XPLMDisplay.
  • If the plugin wants to execute a command (for example, pause the sim), it uses the APIs in XPLMUtilities.

For example, suppose you want to make a plugin that implements a pause-the-sim menu item. Your plugin would simply call XPLMCommandKeyStroke() with the constant xplm_key_pause to from within your menu callback.

See the “Available APIs” section of the SDK home page for a complete list of the available APIs.

Limitations of Plugins

Plugins run synchronously inside the X-Plane process. This has a few basic important ramifications:

  • The plugin and the simulator will not be running at the same time. If the plugin is slow, the simulator’s frame rate will slow down too.
  • The plugin is in the same process as the simulator; if the plugin crashes, it will crash the whole simulator. The plugin can scribble on simulator memory.
  • You cannot use the X-Plane SDK from another process.

All of these things may be overcome by writing additional code (for example, interprocess communication or threading code), but they are not part of the basic plugin system.

Programming Idioms in the XPLM

A programming idiom is a style of coding that is used to accomplish certain goals. The plugin APIs are written in C to provide access to the widest range of languages possible. However, the design of the system is somewhat object-oriented. To accomplish this in a non-object-oriented language, we use the following idioms:

Opaque Handles

An opaque handle is a value used to identify an object created within the plug-in system. Opaque handles are usually defined as integers or void pointers. The handles are ‘opaque’ because the plugin does not know what they mean or what internal structures they represent. You should never try to do anything with an opaque handle except pass it back to the SDK APIs.

The typical life of an object goes something like this: you first create the object and receive an opaque handle. You then make function calls passing in that handle to manipulate it. Finally, you call a function to free the object, passing the handle in again. From that point on the handle is no longer valid.

Callbacks

As discussed above, a callback is a function in the code that the simulator calls to accomplish specific behaviors. For example, when the simulator needs to draw the window, it calls the draw callback you specify when you create the window.

Typically callbacks are specified when an object is created and provide unique behaviors for that object. Providing a callback is similar to overriding a virtual function; it lets you customize an object’s behavior.

Reference Values (“refcons”)

All of the APIs that take callbacks also take “reference constants.” These are pointers that will be passed back to the callback when the callback is called. This allows you to specify specific data of some kind with different uses of the callback. A few uses of refcons include:

  • For object-oriented programming languages, you can pass a pointer to an object that the callback should work on. When the callback is called, it will know what object to operate on.
  • For callbacks that provide behavior to multiple windows, you can provide a pointer to data for that specific window.
  • For callbacks that execute commands, you can specify the command using data of our choosing.

You don’t have to use refcons (you can simply pass in NULL); they are provided only for convenience. Generally if the data the callback operates on is global, you won’t need a refcon, but if multiple copies of that data exist per object, you will.

Writing Plugins

This section describes the step-by-step process of making a simple plug-in.

Creating a Plugin Project

The plugin SDK comes with a number of example plugins with associated sample projects. You can see the latest sample projects in the “Sample Code” section of the SDK home page.

Developing Plugins in C

Developing plugins in C is straight forward. Simply implement the required callback functions and write any additional callbacks. A few considerations:

  • The required callbacks must be exported from the DLL. The process of doing this is compiler-specific and OS-specific. For C and C++ on Microsoft Visual Studio, Xcode, and GCC, we provide the PLUGIN_API macro to make this easier.
  • You must include the XPLM headers to call the functions and link against XPLM_64.dll to build the DLL. You should do a hard dynamic link against XPLM_64.dll. You do not have to use the same XPLM_64.dll to link as runs in the sim.
Developing Plugins in C++

Developing plugins in C++ is similar to developing in C. One additional thing to note is that you have to be careful not name-mangle the required callbacks. Normally, a C++ compiler changes the function names into gibberish to distinguish between functions with the same name and different arguments. (The gibberish is different depending on the arguments to the function.) If the functions are name-mangled, X-Plane will not find them and will not load the plugin. To avoid this, use “extern C” syntax. If you use the PLUGIN_API macro, this is done for you.

Developing Plugins in Another High-level Language

You can develop plugins in any other high level language that supports C calling conventions. Contact us to find out about support for a given language.

Developing Plugins for Mac

Plugins on macOS are libraries. Xcode can produce these libraries very easily, and examples are provided with the SDK.

When working on Mac, we should also link against CoreFoundation.framework and XPLM.framework (as well as any other frameworks you need, like XPWidgets.framework and OpenGL.framework).

Developing Plugins for PC

Plugins on Windows are DLLs. Visual Studio can produce DLLs very easily, and examples are provided with the SDK.

Developing Cross-Platform Plugins

X-Plane runs on Mac, Windows, and Linux. All functionality in the plugin system works cross-platform. You may be able to write a plugin that can be compiled and deployed on both platforms with the same source code. Here are some of the subsystems to use and their implications:

  • X-Plane interaction. All XPLM APIs are cross-platform.
  • File Access. Use the standard C libraries to access files.
  • Directory Structures. The XPLM APIs provide cross-platform file system directory searching and provide the platform-native directory separator. (See XPLMUtilities.)
  • Graphics and UI. All graphics and UI are done via OpenGL, and are therefore fully cross-platform
  • Networking. There is, at this time, no cross-platform threading API. Contact us for some possible solutions that are in the works.
  • Threading. There is, at this time, no cross-platform threading API.

If you are developing code for one platform that could be compiled on the other, contact us for possible help building cross-platform plugins.

Analysis of a Sample Plugin

The Hello World Sample Plugin provides complete projects for Windows, Mac, and Linux—go ahead and download the one appropriate to your platform and open it in a text editor.

The sample plugin provides all the required callbacks, and it registers a handful of other callbacks. Let’s look at those in a bit more detail.

  • XPluginStart()
    • Accomplishes two things:
      • Provides information about the plugin (via the three strcpy() calls at the top of the function)
      • Creates a window, via XPLMCreateWindowEx()
        • Registers a handful of callbacks (params.drawWindowFunc and the five params.handleXXXFunc)
        • As is typical when writing plugin code, these callbacks are registered when creating an object (in this case, a window).
        • We do not provide a refcon value because our plugin will always do the same thing: it will always print “hello world.”
    • Returns true to indicate it loaded successfully. (If we were to return false, due to our window being NULL, we would be unloaded immediately.)
  • XPluginStop()
    • Cleans up the window we created (the window ID we received from XPLMCreateWindowEx()).
    • This is technically unnecessary, because the SDK would forcibly destroy any windows we failed to clean up when our plugin is unloaded, but it’s good practice.
  • XPluginEnable(), XPluginDisable(), and XPluginReceiveMessage()
    • These “stub” callbacks must be provided, even though we don’t need them to do anything.
  • The registered window drawing callback, draw_hello_world()
    • Before any drawing can take place, we must call XPLMSetGraphicsState(). (Failing to do so means you’ll draw in whatever state the OpenGL driver was in last, which may be different between runs of the simulator, or even frame to frame.
    • We query the SDK for the window’s bounds, in case it was moved or resized since last frame. (Since the window is styled like the default X-Plane 11 windows, it can be moved freely, and resized within the limits we set in our call to XPLMSetWindowResizingLimits() when we created the window.)
    • XPLMDrawString() does the final drawing of the text; note that we could have used raw OpenGL code to implement this, but there’s no need to since the SDK can handle text drawing.

Guidelines for Plugin Design

Writing X-Plane plugins is different from writing regular applications. Plugins are guests inside X-Plane and misdesigned plugin can ruin simulator performance.

Plugins always operate synchronously with respect to each other; no parallelization or multithreading is provided. If we need threading or a separate process to maintain simulator performance while running, we must implement this ourselves. Xplane handles each plugin in a serial fashion, it never calls into more than one plugin at the same time. It calls them in a round robin fashion.

The following is a list of guidelines for designing plugins.

Understand The Plugin’s Resource Consumption

The most important first step to building a plugin is to understand what the costs of the plugin running will be. Consider:

  • Does my plugin have to do any computation that takes so long that it will degrade simulator performance by holding the sim off? Remember the simulator makes it through all of its operations in 50 ms at 20 fps. If we hold the simulator off by another 15 ms, we’ll slow the simulator to 15 fps.
  • Does my plugin do computation that happens so frequently and takes so long that it takes a percentage of the CPU? For example, a 2 ms calculation done every 10 ms will consume 20% of the CPU immediately. To prevent a 20% slowdown of X-Plane, the task must run less frequently or take less time each time it runs.
  • Does my plugin do I/O that takes a long time to complete and/or must be done frequently?

Plugins share resources with the simulator. X-Plane normally uses all available CPU, memory, and graphics bandwidth in its operation (although it will be bounded by only one, varying from machine to machine). Any non-I/O resource consumption from the plugin directly takes away from the simulator.

Structure Plugins as State Machines and Event Handlers

The plugin will be made up of a series of callbacks; any long processing will detract from simulator performance. If possible structure the plugin as a state machine and/or a series of event handlers. This allows you to control program flow without the code running for long uninterrupted periods.

Drawing and UI Should Represent State

With conventional programs we can usually draw to the screen whenever it is convenient for us. For example, we can draw to the screen immediately when we receive a mouse click or finish computing a value.

With plugins this is not the case. The plugin can only draw in response to a draw request from the simulator. These callbacks occur at high frequency. For this reason, code that would normally draw in a conventional program needs to record data that will cause the draw handler to draw differently. The whole screen will be completely redrawn every sim frame, so the draw handler needs to keep drawing that data until we want it to disappear.

There are some advantages to programming like this. You do not need to worry about redrawing the user interface as events happen. It will be continually redrawn. The drawing code runs all the time, so the plugin will draw no slower for changing its graphics on a regular basis or animating.

Minimize Time Spent in Any One Callback

While the callback is running, no other callback can run and the simulator cannot run. Try to minimize the time spent in callbacks to keep the simulator running rapidly. Break up slow segments of code.

Minimize Busy Work

When possible base tasks on user inputs rather than timers because user events come less frequently (and only when the user is doing something). Try to use the longest timer periods that are acceptable. Having a callback run too frequently will waste CPU, X-Plane’s most valuable resource. Don’t schedule a callback to run every time the sim redraws unless we really need to run every time the sim draws (for example to set up the camera). When possible, base callbacks on real amounts of time. Disable callbacks that aren’t needed.

Poll for Data Only When Needed

Don’t read data from the sim repeatedly if a cached copy will work. Reading data from the sim is very fast, but within one callback the data almost definitely cannot change.

Cache Information to Achieve the Above Goals

Save values that are expensive to compute to keep callbacks fast. In particular, make sure you don’t compute values unnecessarily in the draw callback, since it will run very very frequently. Cache values and recompute them when they would change, rather than recomputing them every time they are needed.

I/O – Use Non-Blocking/Async/Overlapped I/O or Use a Separate Thread

If the plugin spends the majority of its time doing I/O (file, network, or otherwise), use APIs that do not wait for the I/O to complete before they return. Or create a separate thread that can call the I/O routines.

Use Non-Blocking or Overlapped I/O or a Thread

If the plugin does a lot of I/O, consider using non-blocking or overlapped or asynchronous I/O calls to keep callbacks fast. Blocking in a callback for I/O blocks the entire simulator, hurting frame rate and wasting CPU cycles.

Another strategy is to use thread-blocking I/O and run a separate worker thread.

Basic Plugin Reference

Architecture

Plugins are implemented as DLLs. Plugins link against the plugin-manager (which is also a DLL) to call functions in the plugin API SDK that in turn manipulate the simulator. Plugins implement five callbacks via exported functions, and also can provide additional function pointers for registering callbacks via API calls.

Plugin Build Environments

Mac plugins are libraries with the required plugins exported. They hard link against the XPLM.framework and other libraries if necessary. The main symbol is unused.

PC plugins are DLLs with the required plugins exported. The thread attach functions etc. are unused. They hard link against the XPLM_64.dll and other libraries if necessary.

The Required Callbacks

There are five callbacks you must implement in the plugin via exported DLLs.

XPluginStart
PLUGIN_API int XPluginStart(char * outName, char * outSignature, char * outDescription);
  • Description
    • This function is called by X-Plane right after the plugin’s DLL is loaded.
    • Do any initialization necessary for the plugin. This includes creating user interfaces, installing registered callbacks, allocating resources, etc.
    • Copy null-terminated C strings of less than 256 characters each into the three buffers passed in.
    • If you return success (1), the plugin will receive additional callbacks. If you return failure (0), the plugin will be unloaded immediately with no further callbacks. In the case of failure, the plugin will be in the disabled state after this call.
  • Arguments
    • outName: a pointer to a buffer. Fill this buffer with the human-readable name of the plugin.
    • outSignature: a pointer to a buffer. Fill this buffer with the plugin’s (globally unique) signature. By convention, start this plugin with the organization name to prevent collisions.
    • outDescription: a pointer to a buffer. If the plugin loads successfully, fill this buffer with a human-readable description of the plugin; if the plugin fails to load, fill it with a description of what went wrong.
  • Return value: 1 if the plugin loaded successfully, otherwise 0
XPluginStop
PLUGIN_API void XPluginStop(void);
  • Description:
    • This function is called by X-Plane right before the DLL is unloaded. The plugin will be disabled (if it was enabled) before this routine is called.
    • Unregister any callbacks that can be unregistered, dispose of any objects or resources, and clean up all allocations done by the plugin. After you return, the plugin’s DLL will be unloaded.
  • Arguments: None
  • Return value: None
XPluginEnable
PLUGIN_API int XPluginEnable(void);
  • Description:
    • This function is called by X-Plane right before the plugin is enabled. Until the plugin is enabled, it will not receive any other callbacks and its UI will be hidden and/or disabled.
    • The plugin will be enabled after all plugins are loaded unless the plugin was disabled during the last X-Plane run (and this information was saved in preferences) or all plugins were disabled by the user on startup. If the user manually enables the plugin, this callback is also called. XPluginEnable() will not be called twice in a row without XPluginDisable() being called.
    • This callback should be used to allocate any resources that the plugin maintains while enabled. If the plugin launches threads, start the threads. If the plugin uses the network, begin network communications.
    • You should structure the resource usage of the plugin so that the plugin has minimal costs for running while it is disabled by allocating expensive resources when enabled instead of when loaded.
  • Arguments: None
  • Return value: 1 if the plugin started successfully, otherwise 0
XPluginDisable
PLUGIN_API void XPluginDisable(void);
  • Description:
    • This function is called by X-Plane right before the plugin is disabled. When the plugin is disabled, it will not receive any other callbacks and its UI will be hidden and/or disabled.
    • The plugin will be disabled either before it is unloaded or after the user disables it. It will not be unloaded until after it is disabled.
    • Deallocate any significant resources and prepare to not receive any callbacks for a potentially long duration.
  • Arguments: None
  • Return value: None
XPluginReceiveMessage
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMessage, void * inParam);
  • Description:
    • This function is called by the plugin manager when a message is sent to the plugin. You will receive both messages that are specifically routed to your plugin and messages that are broadcast to all plugins.
    • Specific messages are sent from X-Plane and are described in the plugin messaging documentation. The parameter passed varies depending on the message. Plugins may also define their own private messages to send. If you receive a message you do not recognize, you should just ignore it.
    • Note: in older versions of the SDK, inMessage was declared as type “long.” The message has been, and always will be, a 32-bit signed integer under 32 and 64 bits for all platforms. The change of the C type from long to int is to make the headers safe for 64-bit compilers; int is 32 bits on all compilers that are compatible with X-Plane plugins, but the long data type is 32 or 64 bits depending on ABI and platform and compiler, and is thus not safe to use.
  • Arguments:
    • inFrom: the ID of the plugin that sent the message.
    • inMessage: an integer indicating the message sent.
    • inParam: a pointer to data that is specific to the message.
  • Return value: None
Comments Off on Developing Plugins

SDKRawData

This package contains some of the raw data used to generate the SDK. It is probably not of general interest, but may be useful to programmers working with lots of datarefs, or adapting plugins to other APIs.

Parsable Data References – A parsable text file can be found in X-Plane/Resources/plugins/dataRefs.txt.

Background

Parts of the X-plane SDK are generated automatically from data, rather than hand-coded. We have a master list of data references, which are used to build glue files that go into X-plane to export the data references, as well as the HTML documentation and also generates code for the data ref tester plugin.

The headers to the SDK are originally written in XML; a parser converts them into C headers, Delphi interfaces and HTML web pages.

Data Ref Format

The data ref file is a unix-line-ending file with one line for each data ref. Each line contains up to 5 tab separated items. (The first three are always present, the forth and fifth may be missing or contain no text.) Those items are:

  1. The full text of the data ref.
  2. The data ref’s type. This will be one of: int, int[x], float, float[x], or char[x], where x is a number indicating the dimensions of an array.
  3. the letter ‘y’ for a writable data ref, or ‘n’ for a non-writable one.
  4. A description of the data ref’s type (e.g. “degrees”).
  5. A description of the dataref.
  6. The description may contain tabs or spaces, but no other items will.

XML Format

The XML files contain a number of nested tags. Each tag is either a container or an an attribute. A container contains only other containers or attributes. The text between attribute tags is its value, plain text in between containers is ignored. (This will make sense when inspecting the XML.)

A few warnings: the XML system is limited to what we needed for the XPSDK API and it is very C-centric. In particular, data types are given as C data types and translated into Pascal on the fly.

The following describes the containers and what they contain.

api tag

This represents an entire header file. It contains zero or more component tags, as well as the following attributes:

  • cout – the relative path of the C header to create.
  • put – the relative path of the Pascal interface to create.
  • hout – the relative path of the HTML doc file to create.
  • name – the header’s name.
  • include – the name (with .h extension) of a header to include.
  • desc – a description of the whole API.
  • apitoken – the name of the macro used to prefix the functions for export. If this is blank or missing, the apitoken is XPLM_DLL.
  • apilib – the name of the DLL that implements the functions for this API. If this is blank or missing, the DLL is XPLM.DLL.

component tag

This represents one group of functions, enums, etc. It can contain the typedef, enum, callback, function, struct, define, bulkenum, cdirect, or pdirect tags. It contains the following properties:

  • name – the component’s human-readable name.
  • desc – a description of the component.

typedef tag

This represents a typedef, or aliasing of one type to another. It contains the following attributes:

  • name – the name of the new type.
  • type – the name of the original type that ‘name’ is now an alias for.
  • desc – a description of this new type.

enum tag

This represents a named set of enumeration values. Enumerations contain one enumitem container for each value as well as the following attributes:

  • name – the name of the new enumerated type.
  • desc – a description of the enumeration.
  • type – the type that this enumeration is based on. If this attribute is missing, then the enumeration is anonymous; the ‘name’ parameter is for human-use only and not a valid type in the code.

enumitem tag

This represents a single enumerated value and has the following attributes:

  • name – the symbolic name of the enumeration item.
  • value – the value of the attribute, a decimal integer.
  • desc – a description of the enumerated value.

callback tag

This represents the definition of a pointer to a function for use as a callback. It contains zero or more param containers, one for each parameter, as well as the following attributes:

  • name – the callback function’s name.
  • type – the callback function’s return type or ‘void’ for a procedure.
  • desc – a description of the function.

function tag

This represents a function or procedure in the API. It contains zero or more param containers as well as the following attributes:

  • name – the function’s name.
  • type – the function’s return type or ‘void’ for a procedure.
  • desc – a description of the function.

param tag

This represents a single argument to a function or callback. The order of these containers within a function or callback determines the call order. This attribute containers the following tags:

  • type – the type of the argument (a C type).
  • name – the argument’s name in the public headers.
  • mode – the mode of argument passing. This can be one of the following constants:
  • value – the parameter is passed by value.
  • inref – the parameter is passed in by pointer but NULL may be passed. The parameter is not modified.
  • inrefreq – the parameter is passed in by pointer but may not be NULL. The parameter is not modified.
  • outref – the parameter is passed by pointer but NULL may be passed; if not NULL a value is returned via this pointer.
  • outrefreq – the parameter is passed by pointer and a value is returned through it.
  • ioref – the parameter is passed in by pointer but NULL may be passed, otherwise the value is used and then modified.
  • iorefreq – the parameter is passed by pointer and the value is used, then it is modified.

If the mode tag is left off of a parameter, a mode of value is assumed. The mode tag must be used or not used for all param containers within a single function or callback container, it can not be used for some but not all params.

struct tag

This container represents a structured type. It contains the folling attributes:

  • name – the name of the structure.
  • desc – a description of the structure.
  • type – the type of an item in the structure.
  • tag – the name of that item in the structure.

The type and tag attributes may be specified more than once in alternating order to build up the entire struct; the order they occur in is the struct order.

define tag

This represents an enumerated value done via a #define. These are used for one-time enumerated value of unspecified type (as opposed to enums). It contains the following attributes:

  • name – the symbolic name of the defined symbol.
  • value – the numeric value in decimal or hex (0xXX notation).
  • desc -a description of the define.

bulk tag

This tag contains a bulk enumeration. This is one whose values start at 0 and increase sequentially. It contains the following attributes:

  • name – the name of the enum (the type that clients use).
  • desc – a description of the enum
  • data – a comma separated list of enum symbols, in order starting at an item whose value is zero. All whitespace should be ignored.

cdirect tag

This container directly contains raw C code to be inserted into the header.

pdirect tag

This container directly contains raw Pascal code to be inserted into the header.

Comments Off on SDKRawData