Programming Reference:Filter DLLs
Description
When developing a BCI2000 signal processing filter in C++, it may be useful to compile it into a DLL in addition to adding it to a signal processing module, especially if you are going to test it using the BCI2000Analysis tool. This tool has the standard set of filters built-in but new filters may not easily be added: one would need to add the filter's source file to the tool's CMakeLists.txt, and manually add code that instantiates the new filter and adds it to the filter graph as required.
As a solution to this problem, a simple filter DLL interface has been designed that allows to compile filters independently of the BCI2000Analysis tool. As a restriction, it is necessary to build both BCI2000Analysis and filter DLLs with the same compiler, compiler settings, and version. Also, a debug version of BCI2000Analysis may only use filter DLLs that have been compiled in debug mode, and the same applies for release mode builds.
To be able to deal with mismatches in compiler and build settings without crashing, the filter DLL interface contains text-based information functions that allow a host process to obtain the relevant information in a safe manner.
Filter DLL interface
The following functions are exported by a BCI2000 filter DLL. They are declared in src/shared/filters/FilterWrapperLibrary.h.
const char* InterfaceVersion()
Returns the interface version at the time when the DLL was built. In the host process, a string comparison to the value of the FILTER_WRAPPER_INTERFACE_VERSION macro will tell you whether the DLL's interface version matches the one that is expected by the host.
const char* Compiler()
Returns the name of the compiler in a form that can be matched to by the host process.
const char* BuildType()
Returns the build type, which is one of "Debug", "Release", or "RelWithDebInfo".
const char* BuildInfo()
Returns full build configuration info in the form that is visible in GUI applications under Help->About.
const char* FilterName()
Returns the filter's name for display purposes.
void ReleaseString(const char*)
Call this function on the result of any of the above functions once you are done with it.
void SetBcierr(BCIStream::OutStream*)
void SetBciout(BCIStream::OutStream*)
void SetBciwarn(BCIStream::OutStream*)
void SetBcidbg(BCIStream::OutStream*)
Set the respective stream's output to a BCI2000 BCIStream::OutStream object that has been instantiated in the host before. This way, the host may display error and debugging information originating from the filter contained in the DLL.
Specifying nullptr will suppress output from that stream.
void SetVisualizationChannel(bci::MessageChannel*)
Specify a bci::MessageChannel object to receive visualization messages from the filter contained in the DLL. Specifying nullptr will suppress visualization output.
void SetMeasurementUnits(MeasurementUnits*)
Specify a MeasurementUnits object that the filter will use to initialize basic units of measurement, such as the duration of a data block. Calling this function is crucial for proper function of the filter contained in the DLL. Such an object is typically existent in a host that uses BCI2000 filters.
void SetEnvironmentContext(Environment::Context*)
Specify a BCI2000 Environment::Context object through which the filter will receive access to its parameters and states. Such an object is part of the GenericFilter chain the filter will be part of.
GenericFilter* Instantiate(Directory::Node*)
Create an instance of the filter contained in the DLL. The argument is the filter's parent node in the filter graph, and may be obtained through its parent filter chain.
Howto: Building your own filter as a BCI2000 Filter DLL
This howto assumes that you have already created a BCI2000 filter, and added it to the signal processing filter chain. If this is not the case, you may use the NewBCI2000Module tool located in the build directory to do so. (Note that the NewBCI2000Module tool is not present in the build directory until you have built BCI2000 for the first time.)
Once you created your filter, run the NewBCI2000FilterDLL tool from the build directory. It will ask you for the full path to the filter's cpp file, which you may enter manually, or by dragging-and-dropping that cpp file from a Windows Explorer window into the program's console window, and pressing "enter".
The NewBCI2000FilterDLL tool will then create a "filterdll" subdirectory with its own CMakeLists.txt, and make sure it is included into the build.
To use it, run CMake again, and find both your filter's module, and its filter DLL in the IDE's list of projects. E.g., when your filter's module is located at src/custom/Foo, the module will appear at Custom/Foo, and its associated filter DLL at Custom/FooFilterDLL.
When building the filter DLL, a file called FooFilterDLL.dll will be created at your BCI2000 installation's tools/FilterDLLs directory, ready to be picked up by a filter DLL host such as BCI2000Analysis.
Debugging a filter DLL
To debug a filter contained in a DLL, you have two options:
Setting breakpoints in the IDE
Start up the host (e.g., BCI2000Analysis), and attach a debugger. In the debugger IDE, navigate to your filter's cpp file, and set breakpoints as you see fit. Then, activate the DLL in the host (e.g., by adding a filter DLL node and choosing your filter), and run the filter chain (e.g., by clicking "Run analysis"). Execution should stop when any of the breakpoints is hit.
Using a SuggestDebugging statement
In your filter's cpp file, include Debugging.h, and add a line
SuggestDebugging << "Foo";
anywhere in your filter's functions. When the filter DLL is activated and the SuggestDebugging statement is executed, it will display a message box. You may then attach a debugger to the host, and click "yes" in the message box to drop into the debugger.