X-Plane
X-plane is a flight simulator available for Windows, Linux and macOS. One of the nice things about X-Plane is it supports community modding. You can add new aircrafts, new scenery, and create plugins to modify its behavior and read its state in real time.
The SDK lets you read and write to and from X-Plane with the XPLMDataAccess API.
In this blog post I create a plugin to read flight data from X-Plane and send it in real time to MAPPS for data synchronization and analysis.
Prepare your system
Download The X-Plane SDK
Download the X-Plane SDK from https://developer.x-plane.com/sdk/plugin-sdk-downloads/ unzip it somewhere and make a note of where you stored it at. In my case I put my copy in the G:\dev\XPlane\XPSDK303
folder. This folder contains a subfolder called G:\dev\XPlane\XPSDK303\SDK
Download a Windows SDK
The hello world example says to download the Windows 8.1 SDK from Microsoft. This probably works but I already had the Windows 10 SDK installed on my system, so I used that instead.
Install The Universal CRT Component
Make sure you have the Universl CRT component installed. Up until now, my Visual Studio 2017 installation had only been used for .NET development. I did not have the Universal CRT installed. This gave me the following error when trying to compile the X-Plane examples:
> Cannot open include file 'ctype.h': no such file or directory
To add it to my system I started up “Visual Studio Installer” and pressed the “Modify” button This brings up the “Modifying…” dialog box. Select the “Individual components” tab and search for “universal crt”
Once you see “Windows Universal CRT SDK” make sure it’s checked and press the “Modify” button.
Create a Skeleton Project
In this section I’m creating a skeleton project for X-Plane plugins. The steps taken here can be used as a starting point for X-Plane plugin development.
The hello world example says to use Visual Studio 2017. It probably works with other versions, but I have VS2017 installed. I have not tried this with VS2019 or VS2022 yet.
Create The Project
The first thing to do is to create a new Visual studio solution. Open Visual Studio and use the File > New > Project...
menu to create an Empty Visual C++ project.
I set the name to “MyPlugin” and pressed “OK”
Configure The Project
X-Plane plugins are DLLs and your project need to know where to find the SDK and its files.
General Properties
First, open the Project Property Pages dialog. Right click on the project in the Solution Explorer and select “Properties” from the popup menu.
In the Property Pages dialog, select Configuration Properties > General
on the left and set the following properties * Windows SDK Version : 8.1 or greater * Target Name: win * Target Extension: .xpl * Configuration Type: Dynamic Library (.dll)
Your properties should look something like this:
Press “Apply” to accept the change.
What you’re doing here is telling Visual Studio to create a DLL with the name ‘win.xpl’. This is important. X-Plane looks for DLLs with the name win.xpl
in its plugins folder.
C++ and Linker Properties
You need to set a number of Preprocessor flags. Select Configuration Properties > C/C++ > Preprocessor
on the left to get to the Preprocessor Definitions. Click the drop-down widget to <Edit..>
the preprocessor flags. and add the following preprocessor flags to it
WINVER=0x0601
_WIN32_WINNT=0x0601
_WIN32_WINDOWS=0x0601
WIN32
NDEBUG
_WINDOWS
_USRDLL
SIMDATA_EXPORTS
IBM=1
XPLM200=1
XPLM210=1
XPLM300=1
XPLM301=1
XPLM302=1
XPLM303=1 _CRT_SECURE_NO_WARNINGS
press OK to accept the changes.
In order to build the DLL, the compiler and linker need to know where to find the X-Plane SDK files.
To make this project a little more flexible, I’m using a User Macro to prefix the Include and Library folders needed for compilation and linking.
Create a macro that points to the location of the X-Plane SDK on your computer.
Select Common Properties > User Macros
on the left and press the “Add Macro” button. In the “Add User Macro” Dialog box set the following * Name: XPLANE_SDK * Value: the location where you placed the SDK. * Set the macro as an environment variable in the build environment: Checked
Set the include directories by selecting Configuration Properties > C/C++ > General
on the left to get to the ‘Additional Include Directories’ property. Click the drop-down widget to <Edit..>
and add the following paths to the list of include directories.
$(XPLANE_SDK)/SDK/CHeaders/XPLM $(XPLANE_SDK)/SDK/CHeaders/XPWidgets
press OK to accept the changes.
Set the library path by selecting Configuration Properties > Linker > General
on the left to get to the ‘Additional Library Directories’ property. Add the following to it
$(XPLANE_SDK)\SDK\Libraries\Win
Set the library files by selecting Configuration Properties > Linker > Input
on the left to get to the ‘Additional Dependencies’ property. Click the drop-down widget to <Edit..>
and add the following libraries to the list.
Opengl32.lib
odbc32.lib
odbccp32.lib
XPLM_64.lib XPWidgets_64.lib
Skeleton File
Add a skeleton file by right-clicking on the “Source Files” folder of your project in the Solution Explorer.
Choose Add > New Item
and add C++ File.
Paste the following code into your new file.
#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include <string.h>
#if IBM
#include <windows.h>
#endif
#if LIN
#include <GL/gl.h>
#elif __GNUC__
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif
#ifndef XPLM300
#error This is made to be compiled against the XPLM300 SDK
#endif
// Called when the plugin is first loaded.
// Use to allocate permanent resource and register any
// other callbacks you need
int XPluginStart(
PLUGIN_API char * outName,
char * outSig,
char * outDesc){
return 1;
}
// called when the plugin is enabled. You don't
// need to do anything in this callback
int XPluginEnable(void) {
PLUGIN_API return 1;
}
// called when the plugin is disabled. You don't
// need to do anythig in this plugin
void XPluginDisable(void) {
PLUGIN_API
}
// called right before the plugin is unloaded. Do
// cleanup here ( unrigister callbacks, release resources,
// close files, etc )
void XPluginStop(void){
PLUGIN_API
}
// called when a plugin or X-Plane sends the pulgin a message
void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam){
PLUGIN_API }
At this point you have an X-Plane plugin that doesn’t really do anything.
Note that there are five functions in the above code. These are callback functions required by X-Plane. You can read more about them on the developer site here
Fill out the skeleton
The XPLMDataAccess API has a lot of data available. There’s an extensive list of Datarefs here
I found a number of interesting properties at sim/flightmodel/position/
- latitude double 660+ no degrees The latitude of the aircraft - longitude double 660+ no degrees The longitude of the aircraft - elevation double 660+ no meters The elevation above MSL of the aircraft - groundspeed float 660+ no meters/sec The ground speed of the aircraft - indicated_airspeed float 660+ yes kias Air speed indicated – this takes into account air density and wind direction - indicated_airspeed2 float 810+ yes kias Air speed indicated – this takes into account air density and wind direction - true_airspeed float 660+ no meters/sec Air speed true – this does not take into account air density at altitude!
There are a lot more properties available. For now, I’m only interested in five of them: Latitude, Longitude, Elevation, Ground Speed and True Airspeed
Data Setup
In order to load the values I’m interested in, we need to add the following headers to the C++ files
#include "XPLMProcessing.h"
#include "XPLMDataAccess.h"
#include "XPLMUtilities.h"
And add some static XPLDataRef
variables to store values
static XPLMDataRef planeLatitude;
static XPLMDataRef planeLongitude;
static XPLMDataRef planeEl;
static XPLMDataRef planeGroundspeed;
static XPLMDataRef planeTrueAirspeed;
For now I just want to write data out to a file so add an ofstream variable at file scope
std::ofstream myfile;
In order read data from X-Plane, we define a callback function that it calls in the flight loop. Add the following function to the C++ file.
float MyCallback(
float inElapsedSinceLastCall,
float inElapsedTimeSinceLastFlightLoop,
int inCounter,
void * inRefcon)
{// read out data
double latitude = XPLMGetDatad(planeLatitude);
double longitude = XPLMGetDatad(planeLongitude);
double elevation = XPLMGetDatad(planeElevation);
float groundspeed = XPLMGetDataf(planeGroundspeed);
float trueAirspeed = XPLMGetDataf(planeTrueAirspeed);
std::stringstream ss;"0," << latitude << "," << longitude << "," << elevation << "," << groundspeed << "," << trueAirspeed << "\n";
ss <<
myfile << ss.str();// Return 1.0 to indicate that we want to be called again in another second.
return 1.0;
}
This function uses XPLMGetDatad()
and XPLMGetDataf()
to pull double and float values from the XPLMDataRef variables we created earlier. You may be wondering how X-Plane knows to set these variables. We’ll get to that next.
The XPluginStart()
callback is used to setup your plugin. In my case I want to setup the data reference variables and create an output file to log the values. But first, we need to set a couple of values to tell X-Plane about our plugin. Change the function to look like this
int XPluginStart(
PLUGIN_API char * outName,
char * outSig,
char * outDesc){
"MyPlugin");
strcpy(outName, "eyesdx.examples.myplugin");
strcpy(outSig, "A simple plugin for use with MAPPS"); strcpy(outDesc,
These values get loaded into X-Plane and used to identify the plugin at runtime.
References to the data I want are obtained by calling XPLMFindDataRef()
with the path of the data we’re interested in:
// Find the data refs by name
"sim/flightmodel/position/latitude");
planeLatitude = XPLMFindDataRef("sim/flightmodel/position/longitude");
planeLongitude = XPLMFindDataRef("sim/flightmodel/position/elevation");
planeElevation = XPLMFindDataRef("sim/flightmodel/position/groundspeed");
planeGroundspeed = XPLMFindDataRef("sim/flightmodel/position/true_airspeed"); planeTrueAirspeed = XPLMFindDataRef(
I also open the log file for writting with this call
"C:\\Temp\\xplane.log"); myfile.open(
Cleanup
The XPluginStart()
callback is used to teardown your plugin. I unregister my callback and close the output file here
void XPluginStop(void){
PLUGIN_API
XPLMUnregisterFlightLoopCallback(MyCallback, NULL);
myfile.close(); }
The Plugin code
The complete program looks like this:
#include <iostream>
#include <fstream>
#include <string.h>
#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include "XPLMProcessing.h"
#include "XPLMDataAccess.h"
#include "XPLMUtilities.h"
#if IBM
#include <windows.h>
#endif
#if LIN
#include <GL/gl.h>
#elif __GNUC__
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif
#ifndef XPLM300
#error This is made to be compiled against the XPLM300 SDK
#endif
// References to flight data
std::ofstream myfile;static XPLMDataRef planeLatitude;
static XPLMDataRef planeLongitude;
static XPLMDataRef planeElevation;
static XPLMDataRef planeGroundspeed;
static XPLMDataRef planeTrueAirspeed;
// my custom callback
static float MyCallback(
float inElapsedSinceLastCall,
float inElapsedTimeSinceLastFlightLoop,
int inCounter,
void * inRefcon);
// Called when the plugin is first loaded.
// Use to allocate permanent resource and register any
// other callbacks you need
int XPluginStart(
PLUGIN_API char * outName,
char * outSig,
char * outDesc){
"MyPlugin");
strcpy(outName, "eyesdx.examples.myplugin");
strcpy(outSig, "A simple plugin for use with MAPPS");
strcpy(outDesc,
"C:\\Temp\\xplane.log");
myfile.open("start" << std::endl;
myfile <<
// Find the data refs by name
"sim/flightmodel/position/latitude");
planeLatitude = XPLMFindDataRef("sim/flightmodel/position/longitude");
planeLongitude = XPLMFindDataRef("sim/flightmodel/position/elevation");
planeElevation = XPLMFindDataRef("sim/flightmodel/position/groundspeed");
planeGroundspeed = XPLMFindDataRef("sim/flightmodel/position/true_airspeed");
planeTrueAirspeed = XPLMFindDataRef(
// register callback
XPLMRegisterFlightLoopCallback(
MyCallback,1.0, // calling interval
NULL);
return 1;
}
// called when the plugin is enabled. You don't
// need to do anything in this callback
int XPluginEnable(void) {
PLUGIN_API return 1;
}
// called when the plugin is disabled. You don't
// need to do anythig in this plugin
void XPluginDisable(void) {
PLUGIN_API //fflush(outfile);
myfile.flush();
}
// called right before the plugin is unloaded. Do
// cleanup here ( unrigister callbacks, release resources,
// close files, etc )
void XPluginStop(void){
PLUGIN_API
XPLMUnregisterFlightLoopCallback(MyCallback, NULL);
myfile.close();
}
// called when a plugin or X-Plane sends the pulgin a message
void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam){
PLUGIN_API
}
float MyCallback(
float inElapsedSinceLastCall,
float inElapsedTimeSinceLastFlightLoop,
int inCounter,
void * inRefcon)
{// read out data
float elapsed = XPLMGetElapsedTime();
double latitude = XPLMGetDatad(planeLatitude);
double longitude = XPLMGetDatad(planeLongitude);
double elevation = XPLMGetDatad(planeElevation);
float groundspeed = XPLMGetDataf(planeGroundspeed);
float trueAirspeed = XPLMGetDataf(planeTrueAirspeed);
"0," << latitude << "," << longitude << "," << elevation << "," << groundspeed << "," << trueAirspeed << "\n";
myfile << elapsed << // Return 1.0 to indicate that I want to be called again in another second.
return 1.0;
}
to try it out, build it in Release | x64 mode and copy the resulting win.xpl and win.pdb files to your X-Plane plugins folder. The exact location depends on your install. My copy of X-Plane was from Steam and is located in the steamapps/common/X-Plane 11/Resources/plugins/
folder.
You’ll need to create a MyPlugin/64
subfolder and copy the win.xpl and win.pdb files there.
Sending Flight Data TO MAPPS
I’ve written about sending data to MAPPS in a previous article. I’m only going to write about X-Plane changes here. The complete code for the plugin and the MAPPS network adapter configuration file can be found on GitHub
Setup UDP
I’m choosing to send data to MAPPS over UDP. I setup the socket in XPluginStart()
by adding the following code:
// startup Winsock
"call WSA Startup" << std::endl;
myfile << auto result = WSAStartup(MAKEWORD(2, 2), &wsaData);
"callED WSA Startup: " << result << std::endl;
myfile << if(result != NO_ERROR)
{std::cerr << "WSAStartup() failed" << std::endl;
"WSAStartup() failed " << result << std::endl;
myfile << return -1;
}
// Create the UDP socket
"call socket()" << std::endl;
myfile <<
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (sock == INVALID_SOCKET) {
std::cerr << "socket() failed" << std::endl;
"socket() failed " << sock << " " << WSAGetLastError() << std::endl;
myfile <<
WSACleanup();return -2;
}
//setup address structure
char *)&address, 0, sizeof(address));
memset((
address.sin_family = AF_INET;
address.sin_port = htons(PORT);"call InetPton()" << std::endl;
myfile << s_addr);
InetPton(AF_INET, SERVER, &address.sin_addr."callED InetPton()" << std::endl; myfile <<
MyCallback() is repsonsible for reading the data. The data string sent to the log file can also be sent to MAPPS through the UDP socket with sendto()
std::stringstream ss;
"0," << latitude << "," << longitude << "," << elevation << "," << groundspeed << "," << trueAirspeed << "\n";
ss << auto result = sendto(sock, ss.str().c_str(), ss.str().length(), 0, (sockaddr*)&address, sizeof(address));
When the plugin stops, we turn off networkin by adding WSACleanup()
to the XPluginStop()
function
void XPluginStop(void){
PLUGIN_API
XPLMUnregisterFlightLoopCallback(MyCallback, NULL);
myfile.close();
closesocket(sock);
WSACleanup(); }