X-Plane has a well-documented, accessible API, making it relatively easy to write plugins for the simulator. In this post I’m going to demonstrate how a basic plugin boilerplate can look like, and how to build it on different platforms.
Let’s get started with some basics: X-Plane plugins are dynamically linked libraries, which are loaded by X-Plane at runtime. They have to provide an API as defined by the X-Plane SDK, so X-Plane can access the plugin via defined entry points. The API documentation explains all this, and also provides examples for basic and more advanced plugins. For this post I am going to use a very barebone plugin which does nothing except existing in X-Plane.
The Furious Five
Any X-Plane plugin has to implement these five callbacks — together they constitute the most basic API any plugin must implement:
XPluginStart
is called to initialize the plugin (e.g. when X-Plane starts)XPluginStop
is called when the plugin is stopped (e.g. when X-Plane shuts down)XPluginEnable
is called whenever the plugin gets enabled (e.g. via the plugin admin, or after having initialized the plugin)XPluginDisable
is called whenever the plugin gets disabled (e.g. when X-Plane’s configuration menu is opened)XPluginReceiveMessage
is called when a message is sent to the plugin — either by X-Plane itself, or by a third-party plugin
A plugin can have and register more callbacks, but these five are the mandatory minimum. An absolute barebone plugin could therefore look like this:
1#include <CHeaders/XPLM/XPLMPlugin.h>
2
3
4PLUGIN_API int XPluginStart(char *outName, char *outSig, char *outDesc)
5{
6 strcpy(outName, "DaemoPlug");
7 strcpy(outSig, "net.daemotron.plugin");
8 strcpy(outDesc, "The uber cool plugin that does absolutely nothing.");
9 return 1;
10}
11
12
13PLUGIN_API void XPluginStop(void)
14{
15
16}
17
18PLUGIN_API void XPluginDisable(void)
19{
20
21}
22
23
24PLUGIN_API int XPluginEnable(void)
25{
26 return 1;
27}
28
29
30PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void *inParam)
31{
32
33}
Of course this code could be written more compactly, but in a fully working plugin, all these API functions will have to be implemented in order to make the plugin actually do something.
Organizing Files
There is no best practice on how to organise files of a plugin project. Here I’m just showing you how I do organise things, since it fits well with my way of working. I use git for source code management, and I keep each plugin in its dedicated git repository. For building my plugins, I use CMake. A typical plugin boilerplate using these tools looks like this:
1+--+ Project Root Directory
2 |
3 +--+ SDK/
4 |
5 +--+ src/
6 | |
7 | +-- collisions
8 | |
9 | +-- main.c
10 |
11 +-- .gitignore
12 |
13 +-- CMakeLists.txt
- The
SDK
folder contains the X-Plane SDK as downloaded from Laminar’s Developer Site. - The
src
folder contains my source code files. I usually call the main filemain.c
, where the Furious Five are implemented. - The file
src/collisions
holds symbol names the linker needs to know about. - In the
.gitignore
file, I exclude the SDK and thebuild
directory from git, and eventual folders created by an IDE or editor - The
CMkaeLists.txt
file contains the build instructions for the plugin
The content of the src/collisions
linker script looks like this:
1{
2 global:
3 XPluginStart;
4 XPluginStop;
5 XPluginEnable;
6 XPluginDisable;
7 XPluginReceiveMessage;
8 local:
9 *;
10};
Building The Plugin On Windows
With ~60%, Windows is the most-used operating system among X-Plane users1, so let’s start with building the plugin for this platform. Again there are various options which compilers could be used. I picked the MinGW-w64 distribution provided by WinLibs. On my system, I have both distributions, the Win64 and Win32 variant. The Win32 variant is only needed when building plugins for older versions of X-Plane (prior to 10.20). In this example I’m going to focus on the 64 bit variant only — X-Plane 11 and newer only work with 64 bit plugins.
For this example, I used the GCC 12.2.0 + LLVM/Clang/LLD/LLDB 15.0.6 + MinGW-w64 10.0.0 (UCRT) - release 3 package in Win64 flavour. Installing is straightforward: simply download the package and extract it with 7zip. I usually keep my MinGW installations directly on C:\
, so this one lives at C:\mingw64
on my system. To make the tools accessible on the command line, you’ll need to add C:\mingw64\bin
to your PATH
environment variable. That’s it already, installation done.
CMake comes with its own installer, so setting it up on Windows is easy and straight forward. In addition I use cmder as console emulator on Windows — IMO much more comfortable than the default command line, but of course the normal command line will do as well.
Before we can build the plugin, we will need to create some build instructions, helping CMake understand what we expect it to do. Here’s a simple CMakeLists.txt
file I used to compile the example plugin:
1cmake_minimum_required (VERSION 3.2)
2
3set(CMAKE_C_COMPILER "gcc")
4set(CMAKE_CXX_COMPILER "g++")
5
6project (DaemoPlug)
7
8# Detect SDK
9get_filename_component(SDK_PATH "./SDK/" ABSOLUTE)
10message("-- Detecting X-Plane SDK path")
11
12if(NOT EXISTS ${SDK_PATH})
13 message(FATAL_ERROR "Missing SDK folder: ${SDK_PATH}")
14endif(NOT EXISTS ${SDK_PATH})
15
16# Copy symbol collisions file
17configure_file("src/collisions" "collisions" COPYONLY)
18
19# Global compiler flags
20set (GCC_DEFAULT_FLAGS -std=c17 -Wall -Wunreachable-code -pipe -Wextra -Wshadow -Wfloat-equal -pedantic -fvisibility=hidden -O2 -fmessage-length=0 -D_FORTIFY_SOURCE=2 -fstack-protector -funwind-tables -fasynchronous-unwind-tables -W -DXPLM200 -DXPLM210)
21set (GXX_DEFAULT_FLAGS -std=c++17 -Wall -Wunreachable-code -pipe -Wextra -Wshadow -Wfloat-equal -pedantic -fvisibility=hidden -O2 -fmessage-length=0 -D_FORTIFY_SOURCE=2 -fstack-protector -funwind-tables -fasynchronous-unwind-tables -W -DXPLM200 -DXPLM210)
22
23# Include files
24include_directories ("./src")
25include_directories ("${SDK_PATH}")
26include_directories ("${SDK_PATH}/CHeaders/XPLM")
27include_directories ("${SDK_PATH}/CHeaders/Wrappers")
28include_directories ("${SDK_PATH}/CHeaders/Widgets")
29
30file(GLOB_RECURSE SOURCES "src/*.c")
31
32if(WIN32)
33 cmake_policy(SET CMP0015 NEW)
34 set (CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
35 set (CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
36 link_directories("${SDK_PATH}/Libraries/Win")
37 add_library(WIN_RELEASE_64 SHARED ${SOURCES})
38 target_compile_options(WIN_RELEASE_64 PRIVATE ${GCC_DEFAULT_FLAGS} -s -c -fno-stack-protector -static-libgcc -static-libstdc++ -DXPLM200 -DIBM)
39 target_link_libraries(WIN_RELEASE_64 XPLM_64 XPWidgets_64 -static-libgcc -static-libstdc++)
40 set_target_properties(WIN_RELEASE_64 PROPERTIES LINK_FLAGS "-s -Wl,--version-script=collisions")
41 set_target_properties(WIN_RELEASE_64 PROPERTIES PREFIX "")
42 set_target_properties(WIN_RELEASE_64 PROPERTIES SUFFIX "")
43 set_target_properties(WIN_RELEASE_64 PROPERTIES LIBRARY_OUTPUT_DIRECTORY "DaemoPlug/64/")
44 set_target_properties(WIN_RELEASE_64 PROPERTIES OUTPUT_NAME "win.xpl")
45endif()
This CMake file instructs CMake to
- use
gcc
as compiler - detect the SDK path
- define global compiler flags, including the X-Plane API versions we want to use (
-DXPLM200
) - define include directories including our source folder and the SDK headers
- consider all
*.c
files insrc/
as source files - explicitly use the 64 bit mingw compilers on Windows
- build a shared library linked against the Windows-specific libraries in the SDK, and name it
win.xpl
You can amend all this to your needs — you can add the XPLM300
, XPLM301
and XPLM303
APIs if you want to address only X-Plane 11.50 and newer, or use the Fat Plugins (XPLM 3.0 API) format instead of the Fat Plugins (XPLM 2.1 API) format used in the example above.
To build the plugin, issue these instructions on a command line in the project folder:
1mkdir build
2cd build
3cmake -G "MinGW Makefiles" ../
4mingw32-make
Does It Work?
Now we have build our plugin, it’s time to see if it actually loads in X-Plane. in X-Plane/Resources/plugins
create a folder DaemoPlug
(or whatever you’d like to call it), and a subfolder 64
. Copy the freshly created win.xpl
file into that subfolder and launch X-Plane. In X-Plane’s log file, you should find a line similar to this one:
1Loaded: D:\X-Plane 12/Resources/plugins/DaemoPlug/64/win.xpl (net.daemotron.plugin).
Also in the Plugin Admin window X-Plane will show you the plugin:
Building on Mac and Linux
To build the plugin on Mac or Linux, the respective build tools have to be installed. For Mac, this would be Xcode (or at least the Xcode command line tools), and on Linux, at least GCC and GNU make would be required (Ubuntu provides all required tools in the build-essential package).
Also currently our CMakeLists.txt only covers building on Windows. Adding these parts to the bottom of the file will enable builds on Linux and Mac:
1if(LINUX)
2 add_library(LIN_RELEASE_64 SHARED ${SOURCES})
3 target_compile_options(LIN_RELEASE_64 PRIVATE ${GCC_DEFAULT_FLAGS} -m64 -fPIC -DLIN)
4 set_target_properties(LIN_RELEASE_64 PROPERTIES LINK_FLAGS "-Wl,--version-script=collisions")
5 set_target_properties(LIN_RELEASE_64 PROPERTIES PREFIX "")
6 set_target_properties(LIN_RELEASE_64 PROPERTIES SUFFIX "")
7 set_target_properties(LIN_RELEASE_64 PROPERTIES LIBRARY_OUTPUT_DIRECTORY "DaemoPlug/64/")
8 set_target_properties(LIN_RELEASE_64 PROPERTIES OUTPUT_NAME "lin.xpl")
9endif()
10
11if(APPLE)
12 cmake_policy(SET CMP0042 NEW)
13 set (CMAKE_SYSTEM_NAME Darwin)
14 set (CMAKE_CXX_COMPILER g++)
15 set (CMAKE_OSX_ARCHITECTURES "x86_64;arm64")
16 add_library(MAC_RELEASE_64 SHARED ${SOURCES})
17 target_compile_options(MAC_RELEASE_64 PRIVATE ${GCC_DEFAULT_FLAGS} -nostdinc++ -I/Library/Developer/CommandLineTools/usr/include/c++/v1 -DXPLM200 -DAPL)
18 target_link_libraries(MAC_RELEASE_64 "-F${SDK_PATH}/Libraries/Mac" "-framework XPLM" "-framework XPWidgets" "-framework OpenGL" "-nodefaultlibs" "-lc++" "-lc++abi" "-lm" "-lc")
19 set_target_properties(MAC_RELEASE_64 PROPERTIES LINK_FLAGS "-m64 -fvisibility=hidden")
20 set_target_properties(MAC_RELEASE_64 PROPERTIES PREFIX "")
21 set_target_properties(MAC_RELEASE_64 PROPERTIES SUFFIX "")
22 set_target_properties(MAC_RELEASE_64 PROPERTIES LIBRARY_OUTPUT_DIRECTORY "DaemoPlug/64/")
23 set_target_properties(MAC_RELEASE_64 PROPERTIES OUTPUT_NAME "mac.xpl")
24endif()
Building the plugin on these systems is similar to Windows, with just one difference — they use (g)make
instead of mingw32-make
:
1mkdir build
2cd build
3cmake ../
4make
Source: X-Plane Usage Data ↩︎