Plugin Development |
Plugins are .NET 3.5 DLLs that can be loaded and executed by Virtual Radar Server. They can hook VRS events to add to existing behaviour or they can override VRS objects to create new behaviour.
It is recommended that you have a look at InterfaceFactory to familiarise yourself with the class factory mechanism used by Virtual Radar Server to create objects and the VirtualRadar.Interface namespaces (there are several below VirtualRadar.Interface) to see the interfaces that are declared by Virtual Radar Server.
You may also want to look at ISingletonT to familiarise yourself with the mechanism used to access singleton instances of objects and IBackgroundThreadExceptionCatcher to review the interface that objects, including plugins, can implement to pass exceptions raised on background threads up to the GUI thread for reporting and logging.
Plugins are .NET DLLs that are copied into their own folder within Virtual Radar Server's Plugins folder. The root Plugins folder is created by the Virtual Radar Server installer. Each plugin has one public class that implements IPlugin.
Each plugin DLL must have a filename that starts with VirtualRadar.Plugin. - so for example, VirtualRadar.Plugin.Example.dll is a valid filename for a plugin whereas Example.dll or VirtualRadar.PluginExample.dll are not.
A manifest file, whose filename is the same as the DLL but with an extension of XML (see PluginManifest), must be present in the same folder as the plugin DLL. The manifest file can be used to restrict the loading of the plugin to certain versions of Virtual Radar Server. This can be especially important if your plugin replaces standard implementations of interfaces as the interfaces are likely to change from one release of Virtual Radar Server to another, particularly in the early part of the application's lifetime.
When Virtual Radar Server first starts up it looks in all of the folders below its Plugins folder. If it finds a file that matches the naming convention above then it loads it and looks for the single public class that implements IPlugin. It then creates a single instance of that class and calls methods on it over the lifetime of the application.
The first method that is called is RegisterImplementations(IClassFactory). This is called just after the program has registered its own implementations of all of the interfaces in VirtualRadar.Interface.dll. It is at this point that the plugin can replace the standard implementations of interfaces with its own classes. At this point the program will not have created any of the objects except for the ILog singleton object, so if the plugin replaces an implementation with its own then that implementation will be used by the main application. The GUI does not exist when this function is called so the method should not make any GUI calls.
The next method that is called is Startup(PluginStartupParameters). This is called on a background thread while the splash screen is on display so the method should not make any GUI calls. It is called after the application has created and initialised all of the singletons and main objects used by the application. By this point the program is potentially processing messages coming in from BaseStation and requests coming from browsers, but without showing anying on screen. The plugin can load its own configuration here (see IPluginSettingsStorage) and begin doing some useful work here.
Once Virtual Radar Server has finished initialising itself and has started the GUI it then calls GuiThreadStartup. This is called on the main GUI thread but before the GUI is shown to the user. You can use this method to do any initialisation that must be performed on the GUI thread, or to start your own modeless display. Please don't create a modal display, it will block access to the Virtual Radar Server GUI.
If your plugin class implements IBackgroundThreadExceptionCatcher then the splash screen will hook your class up to the standard mechanism for logging and reporting exceptions on your background thread via the GUI thread.
The user can bring up a list of running plugins at any time to view their status and optionally configure them. If the plugin indicates that it has an options screen then ShowWinFormsOptionsUI will be called whenever the user wants to configure the plugin.
Finally when the user closes the main screen the program calls Shutdown to give the plugin a chance to clean up after itself before the program quits. By this point the BaseStation listener will have stopped receiving messages but many of the other objects will still be active and waiting to be disposed. You will be called on a background thread so you should not make any GUI calls.
You must have Visual Studio 2010 installed so that you can compile the plugin. The free Express version will be fine.
When you add the project remember that the DLL that it builds must be start with VirtualRadar.Plugin..
The plugin must target .NET Framework 3.5 (the full profile, not the client profile).
The project must link to InterfaceFactory.dll and VirtualRadar.Interface.dll. You can link to the versions of these files that ship with Virtual Radar Server, you do not have to build your own copies.
As a post-build step you may want to copy the plugin DLL and the XML manifest file into the appropriate folder within the Plugins folder for your Virtual Radar Server installation. It is probably alright to just set the build folder to be your plugin's folder but when you ship the product remember not to ship any libraries that are shipped with Virtual Radar Server that might also have been copied into your plugin folder by the compiler.
Virtual Radar Server runs as a 32-bit application, even on 64-bit systems. If you are using third-party libraries and have a choice of 32-bit or 64-bit then use the 32-bit flavour.
If you want to use the SQLite ADO.Net data adapter or the IQueryable Toolkit then always link to the versions that ship with Virtual Radar Server and do not ship your own versions.
Prior to 1.1.0 plugins could not make use of VRS's text translation mechanism, they were responsible for doing their own translations. This has now changed.
All of the UI text for a plugin should be put into a single RESX file. The name of the file isn't important but for the sake of the example we'll call it PluginStrings.resx. Each string is identified with a key, e.g. the key for the text "Click OK to continue" might be ClickOK. When Visual Studio compiles the RESX file it will create a class named after the RESX file with all of the keys as string properties. You can refer to these properties directly from within the code of your plugin - e.g.:
... MessageBox.Show(PluginStrings.ClickOK, PluginStrings.OperationFinished); ...
To localise the text for forms and UI elements you will need to add a reference to the VirtualRadar.Localistion library. The library contains the Localiser class, which contains methods that can look for RESX keys in the text of controls and replace the keys with the translated text at runtime. Most of these methods will look for string keys within :: delimiters. Any text within a pair of :: will be considered to be a key in the RESX file, anything outside of the :: will be left alone.
So for example, assume your PluginStrings.resx file has the following entries:
EnterAirport = "Please enter airport name" Generate = "Generate File" GenerateFile = "Generate Airport File"
The form has a title whose text is "::GenerateFile::", a label whose text is "::EnterAirport:::", and a button whose text is "::Generate::". Note that EnterAirport has three colons after it - the last colon is not a part of the key identifier and will be left in the label text after translation.
In the load event for the form you create an instance of the Localiser class and ask it to translate all of the text on the form (don't put the call to the Localiser into the constructor, Visual Studio will try to run it when you edit your form in the designer):
protected override void OnLoad(EventArgs e) { var localiser = new Localiser(typeof(PluginStrings)); localiser.Form(this); }
After the Form method has been called the title will be "Generate Airport File", the label will be "Please enter airport name:" and the button will be "Generate Airport File".
To supply translations for the text you create another RESX file which has the same name as the English RESX file but with the ISO code for the region and language appended. For example, the French translation for PluginStrings.resx would be called PluginStrings.fr-FR.resx, the German file would be PluginStrings.de-DE.resx and so on.
The RESX file should contain the same keys as the English file. If any keys are present in the English file but missing from the translated file then the English text will be used. Any keys present in the translated file but not present in the English file will be inaccessible to the plugin.
The English RESX file will be compiled into the plugin DLL but the translation RESX files are not. They are compiled into DLLs that are named after the plugin and are compiled into directories named after the region / language code. For example, if you have French and Russian versions of your RESX files and your plugin DLL is called PluginExample.dll then after compilation you will have two sub-folders under the build folder, one called fr-FR and the other ru-RU, and within each you will have a DLL called PluginExample.resources.dll that contains all of the translated RESX files.
When you install the plugin the translation DLLs must go into the language folders directly underneath the Virtual Radar application folder. They do not live under the plugin folder. The language folders contain translation DLLs for Virtual Radar Server and all of the plugins so it is important that you choose a name for your plugin DLL that will not clash with other plugins or with Virtual Radar Server itself.
The program can be started with the command-line parameter '-culture:xx-YY' to switch the GUI thread's culture over to another country for testing. For example, starting the program with '-culture:de-DE' will start the program with the German culture active.
Virtual Radar Server uses InnoSetup for its installer but it's up to you how you package the plugin. At the very minimum you will need to install two files on the end-user's system - the DLL and the XML manifest file. These must go into the folder VRS-APPLICATION-FOLDER\Plugins\NAME-OF-YOUR-PLUGIN. The name of your plugin's folder can be anything you like. It can contain more than one plugin if you like. It's entirely up to you.
If you have localised resource DLLS then they must go into the folder VRS-APPLICATION-FOLDER\XX-YY where XX-YY is the culture name. They do not go into your plugin's folder.
Any third party libraries that you depend upon, and which are not already shipped with Virtual Radar Server, should go into the same folder as your plugin.
Here is an example InnoSetup install script for one of the Virtual Radar Server plugin installers. The script uses relative paths, it is in a folder which is a sibling of the plugin project's folder:
#define Plugin "{app}\Plugins\BaseStationDatabaseWriter" [Setup] AppName=Database Writer VRS Plugin AppVerName=Database Writer VRS Plugin 1.0.0 DefaultDirName={pf}\VirtualRadar DefaultGroupName=Virtual Radar InfoBeforeFile=VersionHistory.rtf LicenseFile=License.txt MinVersion=5.0,5.01.2600sp2 OutputBaseFileName=DatabaseWriterPluginSetup [Messages] WizardInfoBefore=Version History InfoBeforeLabel=What has changed? [Files] ; License Source: "..\LICENSE.txt"; DestDir: "{#Plugin}"; Flags: ignoreversion; ; Application files Source: "..\VirtualRadar.Plugin.BaseStationDatabaseWriter\bin\x86\Release\VirtualRadar.Plugin.BaseStationDatabaseWriter.dll"; DestDir: "{#Plugin}"; Flags: ignoreversion; ; Manifest file Source: "..\VirtualRadar.Plugin.BaseStationDatabaseWriter\VirtualRadar.Plugin.BaseStationDatabaseWriter.xml"; DestDir: "{#Plugin}"; Flags: ignoreversion; ; Resource files - note that .NET expects these to be in a folder directly below the application startup folder, not in our plugins folder Source: "..\VirtualRadar.Plugin.BaseStationDatabaseWriter\bin\x86\Release\de-DE\VirtualRadar.Plugin.BaseStationDatabaseWriter.resources.dll"; DestDir: "{app}\de-DE"; Flags: ignoreversion;
I hope that I've covered enough to get you started on writing a plugin but if there are any areas that are unclear, or if you find that the application as it stands does not give you enough leeway to do what you want, then please feel free to get in touch. I can be reached via the website, the forum or at the feedback email address at the bottom of this page.