Plugin basics for SDR#

Goal


SDR Sharp (SRD#) is a piece of software which is used as a radio receiver. It is designed for easy tuning and listening to the frequencies provided by a selected source (whether it's a file, internet radio, or SDR hardware). Figure 1: SDR# in actionIt also makes use of the plugin system to enable its users to extend its functionalities. The plugins are programmed in Microsoft C# language and work on Microsoft .NET 3.5 platform. As I didn't find many references and tutorials on writing the plugins, I have decided to write a simple introductory tutorial, which would describe how to begin writing such a plugin, how to debug it and finally, how to run it.


Creating the solution


Current SDR# was compiled against .NET 3.5 platform and x86 architecture, so when creating the solution, some settings have to be set properly. Otherwise, the plugin won't load.



  1. To set the correct framework, go to the project's options and under "Target framework" choose ".NET 3.5"

  2. To set the correct architecture, you can choose "x86" on the "Configuration Manager" window

  3. You might need to remove some of the features available only for newer frameworks, like tasks support.

  4. Under the project properties, "Build" tab, the checkbox "Allow unsafe code" should be checked. This will be explained later in the article.


Figure 2: Plugin class layoutWhen all this is done, the solution and the project are ready for the SDR# build. You will know the things are ready when you don't get any build errors.


The plugin class layout displays the initial ISharpPlugin implementation initializing all the other classes. The settings control can have the access to the processor class so that it can change the processor settings through its UI inputs. The processor, on the other hand, can have access to the front-end control to output any processed data.


Entrypoint


The entry point that I will be using is the ISharpPlugin interface. This interface provides the control, which is displayed on the SDR# left side plugins list. It is used for the following purposes:



  • Initializing the GUI control for settings (if any)

  • Initializing the processors. The processor is a class which has access to the received data.

  • Loading and saving plugin options

  • Performing finalizations


public class PluginEntrypoint : ISharpPlugin
{
    #region Members

    private SettingsControl _settingsControl;
    private BasicProcessor _basicProcessor;
    private FrontEndControl _frontEndControl;

    #endregion

    #region ISharpPlugin implementation

    public void Close()
    {
    }

    public string DisplayName
    {
        get { return "Basic Plugin"; }
    }

    public UserControl GuiControl
    {
        get { return _settingsControl; }
    }

    public bool HasGui
    {
        get { return true; }
    }

    public void Initialize(ISharpControl control)
    {
        _frontEndControl = new FrontEndControl(control);
        _basicProcessor = new BasicProcessor(control, _frontEndControl);
        _settingsControl = new SettingsControl(control, _basicProcessor);

        control.RegisterStreamHook(_basicProcessor, Radio.ProcessorType.FilteredAudioOutput);
        control.RegisterFrontControl(_frontEndControl, PluginPosition.Bottom);

        _basicProcessor.Enabled = true;
    } 

    #endregion
}


As you can see, the Initialize method creates the control instance which is then automatically displayed on the SDR# plugins settings list situated on the application's left side. The data processor initialization will be explained in the upcoming chapter.


ISharpControl object


ISharpControl object allows the control to perform various types of SDR# main application control. Some of the most important features are:



  • Tuning different application settings using many settable properties

  • Triggering an event if a property has been changed from the main program to reflect the state of plugin

  • Registering front-end controller

  • Registering and unregistering stream hooks

  • Starting and stopping the radio


Receiving the data


A processor is a class that implements one of the available processor interfaces. The implementation needs to use ISharpControl's RegisterStreamHook method to register the processor as the data receiver. If this step is not done, the processor's Process method won't be called.


The processors are intended classes for any and all data calculations. They should also be responsible for any data presentation techniques. The control mentioned in the entry point section can have access to the processors' instances, so that in the event of changing the control's input values, those values can be propagated to the processor. Although in my opinion the processor itself shouldn't use the settings control for outputs, this is not in any case forbidden.


The following processor types are available:



  • IRealProcessor - the data is represented as a stream of float numbers

  • IIQProcessor - the data is represented as a stream of complex numbers

  • IRdsBitStreamProcessor


public unsafe class BasicProcessor : IRealProcessor
{
    #region Constructor

    public BasicProcessor(ISharpControl control, FrontEndControl frontEndControl)
    {
        // frontEndControl can be used to access the display control
    }

    #endregion

    #region IRealProcessor implementation

    public void Process(float* buffer, int length)
    {
        // This is the hookup point which gets executed upon data arrival
    }

    public double SampleRate { get; set; }
    public bool Enabled { get; set; }

    #endregion
}


To enable the processing capabilities, the processor needs to be registered as a stream hook and then enabled (check the previous code listing for details). If you debug the plugin (as explained in the last chapter), the breakpoint in the Process method should get hit, but only when the SDR# is receiving (when it is started). One more thing to note here is the unsafe keyword in the class definition. The application author decided to use C-like pointers in the C# application, which is deemed unsafe in the managed world, mostly because such code has the direct access to the memory.


Display control


Plugin developers can also dedicate a part of the application's main screen for their plugin's purposes. The approach is the same as with the settings control, but the activation procedure is a bit different. To register the front-end plugin, the ISharpControl's RegisterFrontControl method is used. It accepts two parameters, the control implementation (parameter type is .NET native UserControl) and an enum value defining the position where the control will be docked. The communication can then be done using the events or some other pattern.


Installing the plugin


Installing the plugin into the SDR# program consists of two steps.



  1. Copy the DLL library to the SDR# folder

  2. Add the registration code to "Plugins.xml" file


The code that needs to be added is actually a key-value pair describing the plugin entry point The key needs to be any unique key that does not already appear in the file. The value is consisting of the fully qualified class name (which means it contains the full namespace in front of the class name), and the DLL name without the extension. These two values are separated by a comma character. Example:



Debugging the plugin code


Debugging the plugin code requires some preparation. First, the symbol file (PDB file) needs to be copied alongside the DLL. The post-build event can be set to use xcopy for copying both the DLL and PDB file to SDR# folder. The best way to set up debugging is to run an external process when the debug is pressed. To do this, go to the project properties, Debug tab, and set the "Start external program" to run the SDR# executable. When you press debug button in Visual Studio, the SDR# program should be run and the debugger should be immediately attached. Any breakpoints set in your library code should be hit properly (breakpoints should be red and not white with exclamation mark). This is the far better solution than attaching to the running process because any initialization breakpoints will be hit this way, which would be skipped otherwise.


Conclusion and future research


During the making of this simple plugin, I learned a lot of SDR# internal plugin functionalities. As there is still much of the code unknown to me, I will write more articles in the future, including calculating the FFT, differently exposed classes in the available SDR# libraries and so on.


The SDR#plugin system seems excellent if you wish to build an additional simple functionality into the application, but if the needed functionality is complex, it might be better for you to make an external standalone application and reroute the data from SDR# into your application. For complex calculations, you might also consider using the GNU Radio.

SDR#, .Net, C#, plugins