If we are an experienced programmer, we may already be familiar with some of the material presented in this chapter. If we have not done modular programming using DLLs, we introduce the concepts and also provide step-by-step examples of writing a very basic plugin.
Plugins are executable code that is run inside X-Plane. Plugins allow the extension of the flight simulator's capabilities or 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.
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 buildling 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 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 many several 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 >
< 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 >
Ther 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>
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.
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 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 >
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.
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 diabled 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.
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.
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 we when events happen in the simulator (such as the user crashing the plane or selecting a new aircraft model) by calling this function.
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 we that we are 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 >
< Diagram : pausing the sim from a menu command as above >
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.
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 we customize an object's behavior.
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.
< 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.
< Listing: XPluginStop from HelloWorld >
When we destroy our window, we pass the opaque handle (in this case an XPLMWindowID) that we received from XPLMCreateWindow.
< Listing: stub callbacks from HelloWorld >
The reference section of this manual explains how to use these callbacks in detail.
< 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 drawing, it is drawing over whatever it 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.
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.
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.
Another strategy is to use thread-blocking I/O and run a separate worker thread.
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.
PLUGIN_API int XPluginStart ( char * outName, char * outSignature, char * outDescription );Arguments:
outName - a pointer to a buffer. Fill in this buffer with the human-readable name of the plugin.Return Value:
outSignature - a pointer to a buffer. Fill in this buffer with the plug-ins 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.Description:
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 );Arguments:
The XPluginStop function is called by X-Plane right before wer 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 plugins DLL will be unloaded.
PLUGIN_API void XPluginEnable ( void );Arguments:
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 );Arguments:
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 disabled 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, long inMessage, void * inParam );Arguments:
inFrom - the ID of the plugin that sent the message.Return Value:
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.