eyesDxWindowsCC++

X-Plane 11 Data and MAPPS

2024/02/07

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 Visual Studio Installer This brings up the “Modifying…” dialog box. Select the “Individual components” tab and search for “universal crt”

Visual Studio Installer Individual Components 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.

Create Empty 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:

Set Configuration type to DLL 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. preprocessor definitions 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

xplane preprocessor definitions 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

User Macros 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

Include Directories 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

Library Directory 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

preprocessor definitions  

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.

New 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
PLUGIN_API int XPluginStart(
    char *      outName,
    char *      outSig,
    char *      outDesc){

    return 1;
}

// called when the plugin is enabled.  You don't
// need to do anything in this callback
PLUGIN_API int  XPluginEnable(void) {
    return 1;
}

// called when the plugin is disabled.  You don't
// need to do anythig in this plugin
PLUGIN_API void XPluginDisable(void) {
}

// called right before the plugin is unloaded. Do
// cleanup here ( unrigister callbacks, release resources,
// close files, etc )
PLUGIN_API void XPluginStop(void){
}

// called when a plugin or X-Plane sends the pulgin a message
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam){
}

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;
    ss << "0," << latitude << "," << longitude << "," << elevation << "," << groundspeed << "," << trueAirspeed << "\n";
    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

PLUGIN_API int XPluginStart(
    char *      outName,
    char *      outSig,
    char *      outDesc){
    strcpy(outName, "MyPlugin");
    strcpy(outSig, "eyesdx.examples.myplugin");
    strcpy(outDesc, "A simple plugin for use with MAPPS");

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
    planeLatitude = XPLMFindDataRef("sim/flightmodel/position/latitude");
    planeLongitude = XPLMFindDataRef("sim/flightmodel/position/longitude");
    planeElevation = XPLMFindDataRef("sim/flightmodel/position/elevation");
    planeGroundspeed = XPLMFindDataRef("sim/flightmodel/position/groundspeed");
    planeTrueAirspeed = XPLMFindDataRef("sim/flightmodel/position/true_airspeed");

I also open the log file for writting with this call

    myfile.open("C:\\Temp\\xplane.log");
Cleanup

The XPluginStart() callback is used to teardown your plugin. I unregister my callback and close the output file here

PLUGIN_API void XPluginStop(void){

    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
PLUGIN_API int XPluginStart(
    char *      outName,
    char *      outSig,
    char *      outDesc){
    strcpy(outName, "MyPlugin");
    strcpy(outSig, "eyesdx.examples.myplugin");
    strcpy(outDesc, "A simple plugin for use with MAPPS");



    myfile.open("C:\\Temp\\xplane.log");
    myfile << "start" << std::endl;

    //  Find the data refs by name
    planeLatitude = XPLMFindDataRef("sim/flightmodel/position/latitude");
    planeLongitude = XPLMFindDataRef("sim/flightmodel/position/longitude");
    planeElevation = XPLMFindDataRef("sim/flightmodel/position/elevation");
    planeGroundspeed = XPLMFindDataRef("sim/flightmodel/position/groundspeed");
    planeTrueAirspeed = XPLMFindDataRef("sim/flightmodel/position/true_airspeed");

    // 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
PLUGIN_API int  XPluginEnable(void) {
    return 1;
}

// called when the plugin is disabled.  You don't
// need to do anythig in this plugin
PLUGIN_API void XPluginDisable(void) {
    //fflush(outfile);
    myfile.flush();
}

// called right before the plugin is unloaded. Do
// cleanup here ( unrigister callbacks, release resources,
// close files, etc )
PLUGIN_API void XPluginStop(void){

    XPLMUnregisterFlightLoopCallback(MyCallback, NULL);
    myfile.close();
}

// called when a plugin or X-Plane sends the pulgin a message
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam){
}


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);

    myfile << elapsed << "0," << latitude << "," << longitude << "," << elevation << "," << groundspeed << "," << trueAirspeed << "\n";
    // 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
    myfile << "call WSA Startup" << std::endl;
    auto result = WSAStartup(MAKEWORD(2, 2), &wsaData);
    myfile << "callED WSA Startup: " << result << std::endl;
    if(result != NO_ERROR)
    {
        std::cerr << "WSAStartup() failed" << std::endl;
        myfile << "WSAStartup() failed " << result << std::endl;
        return -1;
    }


    // Create the UDP socket
    myfile << "call socket()" << std::endl;
    sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock == INVALID_SOCKET) {
        std::cerr << "socket() failed" << std::endl;
        myfile << "socket() failed " << sock << " " << WSAGetLastError() << std::endl;
        WSACleanup();
        return -2;
    }

    //setup address structure
    memset((char *)&address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_port = htons(PORT);
    myfile << "call InetPton()" << std::endl;
    InetPton(AF_INET, SERVER, &address.sin_addr.s_addr);
    myfile << "callED InetPton()" << std::endl;

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;
    ss << "0," << latitude << "," << longitude << "," << elevation << "," << groundspeed << "," << trueAirspeed << "\n";
    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

PLUGIN_API void XPluginStop(void){

    XPLMUnregisterFlightLoopCallback(MyCallback, NULL);
    myfile.close();
    closesocket(sock);

    WSACleanup();
}

About Me

Greg Gallardo

I'm a software developer and sys-admin living in the US. I code with C++, C#, Java, Swift, Python, JavaScript and TypeScript. I also maintain Windows and Linux systems on-site and Linode, AWS and Azure

Github

Mastodon

YouTube

About you

IP Address: 34.239.154.201

User Agent: CCBot/2.0 (https://commoncrawl.org/faq/)

Language: en-US,en;q=0.5

Latest Posts