- 1 Developing Plugins
- 1.1 About Plugins
- 1.2 What a Plugin is and What Plugins Can Do
- 1.3 Plugins and DLLs
- 1.4 Plugins and Callbacks
- 1.5 Plugin Concepts
- 1.6 Anatomy of a Plugin
- 1.7 Programming Idioms in the XPLM
- 1.8 Where To Go From Here
- 1.9 Writing Plugins
- 1.10 Creating a Plugin Project
- 1.11 Writing a Basic Plugin
- 1.12 Guidelines for Plugin Design
- 1.12.1 Understand The Plugin's Resource Consumption
- 1.12.2 Structure Plugins as State Machines and Event Handlers
- 1.12.3 Drawing and UI Should Represent State
- 1.12.4 Minimize Time Spent in Any One Callback
- 1.12.5 Minimize Busy Work
- 1.12.6 Poll for Data Only When Needed
- 1.12.7 Cache Information to Achieve the Above Goals
- 1.12.8 I/O - Use Non-Blocking/Async/Overlapped I/O or Use a Separate Thread
- 1.12.9 Use Non-Blocking or Overlapped I/O or a Thread
- 1.13 Basic Plugin Reference
- 1.14 Additional Reference
This chapter gives an introduction to the plug-in system and covers the basics of how to set up plugins. If we are already familiar with DLLs and modular programming we may want to skip some of the background material presented.
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 chapter 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 in this chapter. 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. We may not need a plugin for what we are trying to accomplish, and a plugin may not be able to accomplish what we are trying to do.
Plugins are executable code that are run inside X-Plane. Plugins allow the extension of 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
- 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.
- Write data to the simulator, changing its actions or behavior. For example, a simulator can change the weather patterns based on some model.
- 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.
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. Another program uses the DLL by calling these functions.
A DLL contains a directory of the functions inside. Other applications can read the table 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 call the exported functions in a DLL directly, because it must find these functions through the directory. But the exported functions in the DLL can then call the internal functions.
< Diagram : a DLL with an exported function that calls an internal function >
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.
< Diagram : a DLL vs. a monolithic app>
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.
< Diagram : one DLL using another DLL >
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.
< Diagram : one DLL sharing data >
Plugins and the Plugin Manager are DLLs
The X-Plane plugin system is built based on DLLs. The central component of this system is the X-Plane Plugin Manager (or XPLM). The XPLM is a library of code that manages plugins. This plugin is also a DLL. X-Plane links to the XPLM and the plugin (as well as all other plugins) link to the XPLM. The XPLM then serves as the central hub in the plugin system.
< Diagram : the XPLM talking to the sim and plugins >
The plugin contains the code that will run inside the simulator. We export the functions that we want the XPLM to call and the XPLM searches the directory of the DLL to find them.
< Diagram : the XPLM calling a function in a plugin >
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 directory so the plugin can find them and call them.
< Diagram : a plugin calling the XPLM >
The plugin never communicates directly with X-Plane; it always goes through the XPLM. For all practical purposes, the XPLM is the simulator to the plugin. The plugin also never talks to another plugin directly; instead it sends messages via the XPLM. Communicating with other plug-ins is covered in the chapter "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.
< Diagram : the XPLM as proxy for plugin/x-plane com and interplugin com>
Plugins and Callbacks
A callback is a function in the plugin DLL that the XPLM calls to notify the plugin of actions in the simulator, to allow us to accomplish the work that the plugin does, or to provide functionality for the user interface. Callbacks come in two flavors:
The 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 or else it will not load properly. We do not have to do anything in the callbacks; they can be empty functions.
The XPLM finds the required callbacks by searching the DLLs directory.
< Diagram : the XPLM calling a required callback >
The registered callbacks are functions that we pass back to the XPLM to access additional simulator functionality. For example, if we create a window, we 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 we pass the pointer to the function directly to the XPLM.
< Diagram : the XPLM calling a registered callback >
We can register callbacks from the required callbacks or from other callbacks. For example, we can register a callback that will be called in 5 minutes when the plugin is initialized. Then five minutes later in that callback we can create a window and register a callback for when the window is clicked.
< Diagram : registering a callback from a required callback >
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. If we are used to event-driven UI programming, programming a plug-in is similar.
Now that we understand DLLs and callbacks, we will take a closer look at some of the concepts of the X-Plane plug-in system.
The Parts of the Plugin System
There are four main parts to the plug-in system:
X-Plane (the flight simulator). X-Plane is the host application. All plugins run within the memory space of X-Plane.
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.
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.
Other libraries. Plugins can also use other libraries or DLLs that contain useful functionality.
< Diagram - anatomy of the plug-in system >
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:
We may use any string we wish for the plugins signature, but we should pick the first string based on the organization (be it commercial or for shareware) so that we don't accidentally pick the same name as another plugin.
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 callback 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.
The 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 setup in detail.
A plugin is a DLL witih a number of functions. Among these functions are the required callbacks, which must be present, any additional callbacks the plugin will register, and any helper functions, utilities, etc. that the plugin needs.
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. We should allocate any permanent resources we need and do initialization, and register any other callbacks we need. This is a good time to set up the user interface. We also return the plugin's name, signature, and a description to the XPLM.
XPluginEnable. This is called when the plugin is enabled. We do not need to do anything in this callback, but if we want, we can allocate resources that we only need while enabled.
XPluginDisable. This is called when the plugin is disabled. We do not need to do anything in this callback, but if we want, we can deallocate resources that are only needed while enabled. Once disabled, the plugin may not run again for a very long time, so we should close any network connections that might time out otherwise.
XPluginStop. This is called right before the plugin is unloaded. We 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 chapter on "Interplugin communication and messaging" for more information. The XPLM notifies us 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
For many parts of the simulator, we must register additional callbacks. A few examples:
- If we make a menu item, we register a callback that will be called when the user picks the menu item. This callback can then do what the menu item says.
- If we make a window, we register a callback that will be called when the user clicks in the window. We also register callbacks when the window needs to be drawn.
- If we make a timer, we register a callback that will be called every time the timer expires.
For the most part, we will register the additional callbacks from the XPluginStart callback and unregister them from XPluginStop.
A typical session of the simulator might go like this: the user starts the simulator. The simulator (via the XPLM) loads the plugin and calls the XPluginStart. We create a menu item and register a callback for it. The simulator then calls the XPluginEnable function to notify us that the plugin is now enabled. The user picks the menu item, so the simulator calls the callback. We then do something, for example, write some data to a file. The user quits the simulator. The simulator calls the XPluginDisable function and then the XPluginStop function. The DLL is unloaded and the simulator quits.
< Diagram : call sequence with callbacks as above >
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.
- If the plugin wants to read or write data from or to the simulator, it uses the function calls in XPLMDataAccess.h.
- If the plugin wants to create a user interface, it uses the function calls in XPLMDisplay.h.
- If the plugin wants to create a timer, it uses the functions calls in XPLMProcessing.h.
- If the plugin wants to execute a command (for example, pause the sim), it uses the functions in XPLMUtilities.h.
For example, lets say we want to make a plugin that implements a pause-the-sim menu item. The plugin would call XPLMDoCommandKey with the constant xplm_key_pause to pause the simulator. We would do this from the menu callback.
< Diagram : pausing the sim from a menu command as above >
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.
- We cannot use the XPlane 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:
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. We 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: first we create the object and receive an opaque handle. We then make function calls passing in that handle to manipulate it. Finally, we call a function to free the object, passing the handle in again. From that point on the handle is no longer valid.
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 we specify for 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 us 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 us to specify specific data of some kind with different uses of the callback. A few uses of refcons include:
- For object-oriented programming languages, we 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, we can provide a pointer to data for that specific window.
- For callbacks that execute commands, we can specify the command using data of our choosing.
We don't have to use refcons, they are provided for convenience. Generally if the data the callback operates on is global, we won't need a refcon, but if multiple copies of that data exist per object, we will.
Where To Go From Here
The rest of this chapter describes how to program basic plugins, giving examples. The details of individual aspects of plugins are covered in later chapters.
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 as well as plugin 'stationary' projects that may be copied and used as examples.
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 CodeWarrior and Microsoft Visual Studio and MS .net we provide some macros to make this easier.
- We must include the XPLM headers to call the functions and link against XPLM.DLL to build the DLL. We should do a hard dynamic link against XPLM.DLL. We do not have to use the same XPLM.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 we must 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 the "extern C" syntax. If we use the PLUGIN_API macro, this is done for us.
Developing Plugins in Another High-level Language
We 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 the Macintosh are CFM shared libraries. CodeWarrior can produce these libraries very easily, and examples are provided with the SDK. If we are working on OS X and need access to Mach-O functionality, we will need to use the CFBundle APIs.
When working on Mac, we should also link against CarbonLib, MSL_All_Carbon.lib, and XPLM.DLL as well as any other DLLs needed. If we want to call OpenGL directly, link against the OpenGL stub libraries provided with Apple's OpenGL SDK.
Developing Plugins for PC
Plugins on Windows are DLLs. MS Dev Studio or MS Dev Studio.net can produce DLLs very easily, and examples are provided with the SDK.
Developing X-Platform Plugins
X-Plane runs on Mac and PC. All functionality in the plug-in 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.
- Graphics and UI. All graphics and UI are done via OpenGL.
- Networking. There is, at this time, no cross-platform threading API. Contact us for some possible solutions that are in the works. If you write using Winsock 1 (e.g. the Berkeley compatible APIs) we will eventually be able to run these on Mac OS X using BSD).
- 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 x-platform plugins.
Writing a Basic Plugin
A longstanding tradition in computer programming is to have the first program you write for a new language, operating system, or development environment say "Hello World". Plugins will be no exception. We will write a program that puts "hello world" in a Window on the screen.
Writing the Required Callbacks
To write a plugin we must provide the required callbacks. In our case, we do not need to handle plugin messaging or enabling and disabling. We do need to create our window upon startup and and destroy it at shutdown.
Our XPluginStart function must accomplish two things: we must provide information about our plugin and we must create our window. We do so as follows:
< Listing: XPluginStart for HelloWorld >
A few things to note in this example: when we create a window we provide three functions that will be called for the window. These are registered callbacks. We almost always register callbacks in association with creating an object (like a window). In this case we do not provide a refcon value because our plugin will always do the same thing: it will always print "hello world".
Also note that we fill in our plugin signature and return true. We return true to indicate that we successfully loaded; if we do not call true, we will be unloaded immediately.
Our XPluginStop function only has to clean up the window we created.
< Listing: XPluginStop from HelloWorld >
Writing XPluginEnable, XPluginDisable and XPluginReceiveMessage
We must provide all of the required callbacks even if they do nothing. The following listing shows empty stub listings for these callbacks.
< Listing: stub callbacks from HelloWorld >
The reference section of this manual explains how to use these callbacks in detail.
Providing the Registered Callbacks for the Window
If we try to compile this plugin now, it will not link. This is because we have not provided the required callbacks. We will provide them now:
< Listing: stub mouse and key callbacks plus a draw callback for HelloWorld >
In this trivial example, we do nothing in response to mouse or keyboard events. This means we will not be able to draw this window, nor type into it. We do handle the draw message, which is called every time the sim needs it to be redrawn. See the chapter on graphics for more details.
In this case we make three calls. The first finds the window's area. In our case the window's area is always fixed, but in general it is a good idea to find the current window location in case it has been moved. The second call erases that area to a translucent gray. The X-Plane screen is always drawn from back to front every sim frame. This means that every time the window is drawn, it is drawing over whatever is behind it. We use the alpha channel to make a translucent window. Finally, we draw the 'hello world' string onto the window. The routines to draw translucent gray boxes and draw strings are both provided in the XPLMGraphics API. We could also use our own OpenGL code for this method.
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 way we can 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. We 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 we don't have to 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
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
Macintosh plugins are CFM shared libraries with the required plugins exported. They hard link against the XPLM DLL 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 DLL and other libraries if necessary.
The Required Callbacks
There are five callbacks we must implement in the plugin via exported DLLs.
PLUGIN_API int XPluginStart ( char * outName, char * outSignature, char * outDescription );
- outName - a pointer to a buffer. Fill in this buffer with the human-readable name of the plugin.
- outSignature - a pointer to a buffer. Fill in this buffer with the plug-in's signature.
- outDescription - a pointer to a buffer. Fill in this buffer with a pointer to a human-readable description of the plugin or a reason for not being able to load.
- A "1" is returned if the plug-in loaded successfully, otherwise a "0" will be returned if it did not.
- The XPluginStart 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. OutName should be the name of the plugin as users know it. OutSignature should be a string that is globally unique to the plug-in. By convention, start this plugin with the organization name to prevent collisions. OutDescription should be a description of the plugin if we are able to load successfully, or a description of why the plugin could not load if we could not.
- Return 1 if the plugin was able to load successfully, or 0 if we could not. If we return success, the plugin will receive additional callbacks. If we return 0, the plugin will be unloaded immediately with no further callbacks.
- The plugin will be in the disabled state after this call.
PLUGIN_API void XPluginStop ( void );
- The XPluginStop 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, resources, and clean up all allocations done by the plugin. After we return, the plugin's DLL will be unloaded.
PLUGIN_API void XPluginEnable ( void );
- The XPluginEnable 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.
- 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.
- We 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.
PLUGIN_API void XPluginDisable ( void );
- The XPluginDisable 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 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.
- The XPluginStop callback will be called before the DLL is unloaded but after XPluginDisable.
PLUGIN_API void XPluginReceiveMessage ( XPLMPluginID inFrom, int inMessage, void * inParam );
- inFrom - the ID of the plugin that sent the message.
- inMessage - a long integer indicating the message sent.
- inParam - a pointer to data that is specific to the message.
- The XPluginReceiveMessage function is called by the plugin manager when a message is sent to the plugin. We will receive both messages that are specifically routed to the plugin and messages that are broadcast to all plugins.
- Specific messages are sent from X-Plane and are described in the plug-in messaging documentation. The parameter passed varies depending on the message. Plugins may also define their own private messages to send. If we receive a message that we do not recognize then we 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.
The enable, disable, and receive message callbacks are discussed in greater detail in the chapter on plugin management and communication. Individual subsystems are described in later chapters.