Documentation

 

How to write a plugin

Pre-requisites

You will need Visual Studio installed, ideally 2013 or better. If you Google Visual Studio Community Edition you will find links to the free version of Visual Studio, which will more than suffice for this.

You will also need to have Virtual Radar Server installed on the build machine. This is because you are going to be linking to some of the VRS libraries.

VRS plugins need to be .NET assemblies. You can use any .NET language to write the plugin but VRS was written in C#, so that's what I'm using here.

Runtime Environment

We are eventually going to configure Visual Studio so that it compiles the plugin into a sub-directory under an installation of Virtual Radar Server, which makes it an awful lot easier to test.

However, Windows will (by default) not let Visual Studio write into the Program Files folder heirarchy unless you either have UAC turned off or you're running Visual Studio as administrator, neither of which are recommended.

So to get around this we'll start by creating a VRS installation that Visual Studio can write to.

Open up Windows Explorer and create a folder somewhere. Call it VRS-Plugin-Dev. For the sake of this document I'll assume that you've created the folder C:\VRS-Plugin-Dev.

In Windows Explorer go to C:\Program Files (x86)\VirtualRadar and copy everything from there into your VRS-Plugin-Dev folder.

Double-click the VirtualRadar.exe file in VRS-Plugin-Dev and make sure that it loads OK. You'll need to stop any running instances of VRS before you do this. It should share the same configuration folder as your installed copy of VRS.

Shut down VRS before continuing.

Plugin Project

In Visual Studio go to File | New | Project.... On the new project screen that pops up select Class Library as the project type and ensure that the .NET framework dropdown is set to .NET Framework 3.5:

VS2013 New Project Dialog

If you're writing in C# then you should get something like this:

Initial project screenshot

32-bit or 64-bit Assembly?

Up until version 2.4 of Virtual Radar Server all of the libraries and plugins were built as 32-bit assemblies. Previous iterations of this page had instructions at this point on how to set your plugin project up to build a 32-bit assembly.
 

As of version 2.4 this is no longer the case. The main Virtual Radar Server application is still a 32-bit assembly because it ships with some libraries that are 32-bit only, but there is a custom 64-bit build available and if you want your plugin to work with either build then it needs to be built for Any CPU.
 

Visual Studio will have created your project with the configuration set up for an Any CPU build so you don't need to do anything special.

References

You will need to add references to the Virtual Radar Server libraries that every plugin will need. They are:

  • InterfaceFactory.dll
  • VirtualRadar.Interface.dll

To do this right-click the References node and choose Add Reference...

In the dialog that appears click Browse on the left-hand sidebar.

Then click the Browse... button in the bottom-right. You'll be shown a file picker window.

Navigate in there to C:\Program Files (x86)\VirtualRadar.

Control-click InterfaceFactory.dll and VirtualRadar.Interface.dll, and then click Add:

Windows explorer dialog

Click OK in the Visual Studio Reference Manager dialog. You should end up with the two libraries showing under References in your project:

Visual Studio references

Normally when Visual Studio includes a reference to a library it will configure the project so that the library is copied into the project's build folder. We do not want it to do this - when the plugin runs it'll be loaded by Virtual Radar Server and these libraries will already have been loaded. We don't have to ship the libraries with the plugin.

Right-click the InterfaceFactory.dll reference and pick Properties.

Reference properties context menu

In the dialog or pane that appears change Copy Local to False.

Reference properties

Repeat for the VirtualRadar.Interface.dll library reference.

Build Folder

When you first create a project Visual Studio will configure it to build into the bin folder in the project directory. We want to change this so that it builds into the Plugins folder in the VRS-Plugin-Dev folder that we created earlier. That way the installation of VRS in VRS-Plugin-Dev will automatically pick up our plugin.

Right-click the project node and choose Properties in the context menu that appears.

Project properties

Scroll down a bit on the Build screen until you get to the Output section:

Build output section

Change the Output path to the VRS-Plugin-Dev folder that you created before and then tack on \Plugins\SamplePlugin (change the SamplePlugin bit to whatever you've called your plugin):

Build folder

It doesn't matter if the folder does not exist, Visual Studio will create it.

Change the Configuration dropdown from Active (Debug) to Release and set the output path to the same folder.

Close the properties window.

Debug Settings

If you were to try to build and run the project now Visual Studio will complain that class libraries cannot be started. We need to configure Visual Studio so that when we press Ctrl+F5 it will run the copy of Virtual Radar Server that we put into the C:\VRS-Plugin-Dev folder, which in turn will hopefully load and run our plugin. Once this has been set up then we should also be able to set breakpoints in our plugin from Visual Studio and debug it, if required.

As before you need to start by right-clicking the SamplePlugin project and pick Properties. This time instead of selecting the Build tab, select the Debug tab. You should get a window like this:

Debug properties window

Select the Start external program option and enter the full path to VirtualRadar.exe in your VRS-Plugin-Dev folder:

Debug properties start action

Select Release in the Configuration dropdown and repeat for the release build.

Close the properties window.

DLL File Name

Virtual Radar Server will only look for plugin code in DLL files with names that start with VirtualRadar.Plugin. Visual Studio will have created a project that builds a file called SamplePlugin.dll. We need to change the filename.

Once again you need to right-click the SamplePlugin project node and pick Properties. This time go to the Application tab:

Application properties

Change the Assembly name so that it starts with VirtualRadar.Plugin.:

Application assembly name

Note that it does not have an extension.

Close the properties window.

Manifest File

The manifest file is an XML file that lives in your plugin folder. It has to have the same name as your plugin's DLL, but with an XML extension. You use the file to tell VRS whether the plugin will be able to work with the version of VRS that is running.

Bear in mind that the project is now configured to create a DLL called VirtualRadar.Plugin.SamplePlugin.dll, so we will need to create an XML file called VirtualRadar.Plugin.SamplePlugin.xml.

Right-click the project node and choose Add | New Item.... In the dialog that pops up click on Data and XML File (do not choose Application Manifest File, that's something else entirely).

In the Name field enter the name of your project's assembly with an XML extension:

Add manifest XML dialog

Click Add. You should now see the XML file in the project.

By default Visual Studio does not copy XML files to the build folder. We want it copied in, so we need to change that. Right-click the XML file in the solution explorer and choose Properties. In the properties pane that appears change the Copy to Output Directory setting to Copy if newer:

Manifest properties

Double-click the XML file to open it if it's not already open. It probably looks something like this:

<?xml version="1.0" encoding="utf-8" ?>

Change it to this:

<?xml version="1.0" encoding="utf-8"?>
<PluginManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <MinimumVersion>2.4.0</MinimumVersion>
  <MaximumVersion>2.4.9999</MaximumVersion>
</PluginManifest>

That manifest will tell VRS that it should only load the plugin if Virtual Radar Server's version is at least 2.4.0 and at most 2.4.9999. You will need to change the minimum and maximum version numbers if you are running a different version of VRS.

You can remove the MinimumVersion and MaximumVersion entries entirely if you want VRS to always try to load the plugin, and just show an error message if the plugin can't be loaded because something in VRS that it relies upon has changed since it was built.

Save the file and close the editor tab.

Plugin.cs

Plugin.cs is the heart of the plugin, it holds all of the code that Virtual Radar Server will call when it loads your plugin. You can have other classes in your plugin, and your plugin can load any libraries that it needs to, but you must always have a Plugin class so that VRS can talk to you.

When you first created the project Visual Studio will have created an empty class for you and called it Class1.cs. Right-click the Class1.cs node in the solution explorer and choose Rename. Change the name to Plugin.cs. When Visual Studio asks if you want to rename all references to Class1, click Yes.

Double-click Plugin.cs. It probably looks something like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SamplePlugin
{
    public class Plugin
    {
    }
}

Add a reference to VirtualRadar.Interface to the module:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VirtualRadar.Interface;

Change the public class Plugin line to read public class Plugin : IPlugin

    public class Plugin : IPlugin
    {
    }

The IPlugin interface is what Virtual Radar Server will look for when it loads your plugin. You can only have one public class in your plugin that implements IPlugin.

Right-click IPlugin and choose Implement Interface | Implement Interface. Visual Studio will add a whole bunch of stubs to your class. All of the stubs will just throw the NotImplemented exception so you'll need to flesh them out.

Properties

Property Description
HasOptions Return true if your plugin has an options screen, false if it does not. Out example does not have an options screen so we'll return false.
Id Return a short string that identifies your plugin. It must be unique, VRS will not load two plugins that have the same identifier. We'll call our plugin "sample.plugin".
Name Return the name that VRS will show in the plugin list for your plugin. We'll return "Sample Plugin" in the example.
PluginFolder You don't touch this property, VRS will fill it in at run-time with the plugin's location on disk. All you need to do is provide the property so that VRS can write to it. If you're writing the plugin in C# or VB.NET then you can just provide an automatic property for this:
public string PluginFolder { get; set; }
Status This is the status that VRS will show against the plugin in the plugin list. When the status changes we need to tell VRS that it's changed by raising the StatusChanged event. See the code sample for StatusChanged for a full implementation of the property and the event.
StatusDescription This is the longer bit of text that VRS shows under the status in the plugin list. You need to tell VRS when the property has changed by raising the StatusChanged event - see the StatusChanged code sample for a full implementation.
Version This is the version number that VRS will show against your plugin in the plugin list. Return anything you like. We'll return "1.0" in the example.

StatusChanged

You communicate the status of your plugin to the user via two properties, Status and StatusDescription. VRS will listen to the StatusChanged event that you implement and update the display with the content of those two properties whenever it is fired.

Typically you would write a function to raise StatusChanged and then have the property setters for Status and StatusDescription call that function whenever the property changes. That way you don't have to remember to raise the event, you just set the property and it all happens automatically.

        private string _Status;
        public string Status
        {
            get { return _Status; }
            set {
                if(value != _Status) {
                    _Status = value;
                    OnStatusChanged(EventArgs.Empty);
                }
            }
        }

        private string _StatusDescription;
        public string StatusDescription
        {
            get { return _StatusDescription; }
            set {
                if(value != _StatusDescription) {
                    _StatusDescription = value;
                    OnStatusChanged(EventArgs.Empty);
                }
            }
        }

        public event EventHandler StatusChanged;

        private void OnStatusChanged(EventArgs args)
        {
            var statusChanged = StatusChanged;
            if(statusChanged != null) {
                statusChanged(this, args);
            }
        }

Functions

VRS will call the functions on the Plugin class at different points in the life-cycle of the plugin. Each function is expected to either perform a particular task or do nothing. I'll list them out in the order in which they are called, although this probably won't be the order in which Visual Studio created them when it created the stubs.

Function Description
RegisterImplementations

This is called early on in Virtual Radar Server's load process, once all of the plugins have loaded but before any of the programs main classes have been instantiated. Do not make any calls to Factory.Singleton from here, particularly for interfaces that inherit from ISingleton, you could break things for other plugins.

Virtual Radar Server uses a class factory. Instead of instantiating classes directly with the new keyword you will see references to Factory.Singleton.Resolve<ISomeInterface>(); peppered throughout the source. Those references are instantiating new objects via the class factory, the code asks for an implementation of an interface and the class factory creates one and returns it. In this way the classes don't have direct dependencies on each other, which makes life easier when it comes to writing unit tests.

It also makes it easy to swap classes out at run-time. All you need to do is implement the interface and register your implementation with the class factory. This lets plugins stick their fingers into almost any aspect of Virtual Radar Server.

Almost all of the interfaces that VRS uses are declared in VirtualRadar.Interface (which you'll remember you had to add a reference to when you created the plugin project). When VRS loads it calls a static function in each of its libraries, and those static functions tell the class library how to instantiate the interfaces that the library has classes for.

If you want to completely swap out the default implementation of an interface then this is where you can tell VRS to ignore the default implementation and to use yours instead. Examples of plugins that do this are the Disable Audio plugin (which replaces the implementation of IAudio with a stub class that does nothing) and the Disable UPnP plugin (which replaces the implementation of IUniversalPluginAndPlayManager with a stub that does nothing).

Another reason why you might want to swap out an implementation of an interface is if you want to extend the default implementation. The Feed Filter plugin implements a wrapper for the IListener implementation and uses that to extend the functionality of IListener, in this case to filter out messages from receivers.

Our example will not swap out any of the VRS interfaces so our version is going to be empty. If you want to see examples of this function in use then take a look at the source for the FeedFilter, DisableAudio and DisableUPnP plugins on GitHub.

Startup

This is called fairly late on in the startup process. By this point the web site will be up, the feeds will be connecting, the log can be written, almost everything is good to go. Note that the function is called on a background thread so you cannot perform any GUI calls here.

It is here that you should load your options (if you have any), inject yourself into the web site, hook events, start timers or do whatever setup you need to do to get the plugin running.

To help with this the function is passed an object of type PluginStartupParameters. The source for this can be found on GitHub.

Our example will display the local time and date in the plugin options list (just for something to do) so we have code in our sample to start a timer and periodically update Status and StatusDescription.

GuiThreadStartup

This is called after the plugins have started up. Unlike Startup it is not called on a background thread, it is called on the GUI thread. If your plugin needs to make calls from the GUI thread during startup (for example it needs to create a modeless window) then this is the place to do that.

Our example doesn't need to do any GUI startup so the method is empty. Very few plugins need to do GUI startup.

Shutdown

This is called after the user has told VRS that they want to close the program. It is your opportunity to do any cleanup before the program closes, e.g. close database connections, flush caches, cleanly dispose of resources etc.

Note that this function, like most other Plugin functions, is called on a background thread.

Plugins are shut down fairly early on in the shutdown process. At the point that Shutdown is called you should still have access to the website objects, database objects, feed objects etc.

In our example we just destroy the timer that we created in Startup. Technically we don't need to do that as the Timer will be destroyed by Windows when it cleans up the VRS process, but it's nice to be tidy.

ShowWinFormsOptionsUI

Unlike the other functions this one can be called more than once during the lifetime of the plugin. It is called every time the user clicks the Options button in the plugin list. If you are returning false from the HasOptions property then this function should never be called.

Our plugin doesn't do anything so the method is empty.

Sample

This is our sample Plugin.cs after all of the stubs have been fleshed out. All it does is show the date and time in the status fields on the plugin list.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VirtualRadar.Interface;

namespace SamplePlugin
{
    public class Plugin : IPlugin
    {
        public bool HasOptions
        {
            get { return false; }
        }

        public string Id
        {
            get { return "sample.plugin"; }
        }

        public string Name
        {
            get { return "Sample Plugin"; }
        }

        public string PluginFolder
        {
            get;
            set;
        }

        public string Version
        {
            get { return "1.0"; }
        }

        private string _Status;
        public string Status
        {
            get { return _Status; }
            set {
                if(value != _Status) {
                    _Status = value;
                    OnStatusChanged(EventArgs.Empty);
                }
            }
        }

        private string _StatusDescription;
        public string StatusDescription
        {
            get { return _StatusDescription; }
            set {
                if(value != _StatusDescription) {
                    _StatusDescription = value;
                    OnStatusChanged(EventArgs.Empty);
                }
            }
        }

        public event EventHandler StatusChanged;

        private void OnStatusChanged(EventArgs args)
        {
            var statusChanged = StatusChanged;
            if(statusChanged != null) {
                statusChanged(this, args);
            }
        }

        // The timer that Startup will create and Shutdown will destroy
        private System.Timers.Timer _Timer;

        public void RegisterImplementations(InterfaceFactory.IClassFactory classFactory)
        {
            ;
        }

        public void Startup(PluginStartupParameters parameters)
        {
            _Timer = new System.Timers.Timer() {
                AutoReset = true,
                Interval = 500,     // half a second
                Enabled = false,
            };
            _Timer.Elapsed += Timer_Elapsed;
            _Timer.Enabled = true;
        }

        void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            var now = DateTime.Now;
            Status = String.Format("The time is now {0:T}", now);
            StatusDescription = String.Format("The date is now {0:d}", now);
        }

        public void GuiThreadStartup()
        {
            ;
        }

        public void ShowWinFormsOptionsUI()
        {
            ;
        }

        public void Shutdown()
        {
            _Timer.Dispose();
        }
    }
}

Running the Plugin

The moment of truth :) Press F5 in Visual Studio. If all goes well then Virtual Radar Server should start up and when you click Tools | Plugins you should see your plugin in the list with the status and status description lines showing a clock updating in real time.

You should also be able to set a breakpoint in the plugin code and Visual Studio should break on it. To see this happening go to the first line of the Timer_Elapsed function and press F9 to set a breakpoint. Visual Studio should hit the breakpoint within 500ms (the interval that we've set on the timer). Press F9 again to remove the breakpoint and then F5 to continue execution.

Troubleshooting

There are a number of things that can go wrong, but the ones that I keep on messing up are:

  1. Have you changed the assembly name in the project properties so that the assembly starts with VirtualRadar.Plugin.? If you haven't then VRS will ignore your DLL when it's looking for plugins to load.
  2. Do you have a manifest file, does it have the <PluginManifest> root node, does it have the same name as the DLL but with an extension of XML and has it configured to be copied to the output folder? Even if you don't have the MinimumVersion and MaximumVersion entries you still need to have the PluginManifest tag, otherwise VRS will ignore your plugin.
  3. Is your Plugin class public? VRS will only look for the IPlugin interface on public classes.
  4. Does your Plugin class implement the IPlugin interface? If it doesn't then VRS won't call it.
  5. Is VRS reporting that it couldn't load your plugin? If so then check the log (Tools | OpenVirtualRadarLog.txt), the program will log plugin load errors in there.
  6. When you created the project did you set it for .NET 3.5? If not then VRS won't be able to load it. You can change a .NET 4 project to 3.5 from the project properties screen, although you will need to remove references to .NET 4 assemblies that can no longer be loaded after you've done so. They're obvious, they have a warning mark next to them in the references list.