C++ Export Plugin

Imagine the scenario where Dewesoft data can be analyzed and post-processed in any software you want. Of course, we already support quite a lot of export formats, but not nearly all of them. With the help of the C++ Export Plugin, you can extend Dewesoft to support whichever format comes to your mind.

Imagine the scenario where Dewesoft data can be analyzed and post-processed in any software you want. Of course, we already support quite a lot of export formats, but not nearly all of them. With the help of the C++ Export Plugin, you can extend Dewesoft to support whichever format comes to your mind. The output of the C++ Export Plugin is compiled into an external library with .exp extension and will be automatically located and loaded on startup by Dewesoft. 

A C++ Export Plugin example that will be shown throughout this tutorial can be obtained using the VSIX installer and will be further explained in the next pages.

In order to start using C++ Plugin, you must have Visual Studio 2017 or Visual Studio 2019 IDE installed on your system. Some of the reasons we have chosen Visual Studio are its functionalities, powerful developer tooling, like IntelliSense code completion and debugging, fast code editor, easy modification/customization and many more. 

Dewesoft plugin for Visual Studio development can be found on the Dewesoft webpage under Visual Studio 2017/2019 Development Tool. Note that you have to be logged in to access the C++ Plugin section. After downloading, just double-click the VSIX file and the installer will guide you through the installation process.

Once VSIX plugin is downloaded and installed you will be able to create the Dewesoft C++ plugin using the New project window and selecting the DewesoftX C++ Export Plugin project. The template can be found with the help of the Search text box (right top corner) in the online tab.

The new project window is accessed in File tab -> New -> Project.


Before we jump to the programming part, we first need to explain how exporting in Dewesoft works. There are two types of exports, channel-based, and value-based. 

  • Channel-based export - writes all data (and times) from one channel. Once all the samples are written, we start writing another channel.
  • Value-based export - writes the N-th sample for each channel where N represents the time from start to finish. When all N-th samples are written, N is increased by the time step of the channel with the highest synchronous rate.

Export type can be defined inside get_ExportType() function.

The main difference between these two types of export is the sequence of functions that are called from start to finish of the export. The sequence of function calls and their brief explanation can be found in the tables below.


Value-based sequence of calls

 

 

Channel-based sequence of calls 

 

 

 



As already mentioned, this example can be obtained using the MUI Plugin Wizard. If you are not familiar with the basic settings MUI Plugin Wizard provides, you can read more about them in Basic custom plugin development in C++ under Example: New C++ Plugin

To create the same example as we are using in this tutorial, you have to select "Dewesoft Test Export Example" in the combo box, as seen in the picture.

This example shows a complete example of how to create a text file, which can later be imported into the Dewesoft. It will also serve as a solid foundation for your very own custom export. Once the project is created, you should be able to see the following project structure.

Most of the work is done in the selected files:

  • ui folder - user interface and its actions are defined there
  • example_writer files - endpoint where all export functionality should be added

Once the plugin is created and you stumble upon Error MSB8036: The Windows SDK version sdk_version was not found. You should retarget your solution to use the one you have installed. You can do this by right clicking on your solution node in Solution explorer and selecting Retarget solution.

Once the Retarget solution window is opened, you can select "Latest installed version" item.

To simplify export as much as possible, the user can only change one export setting - delimiter character. For that reason, the MUI library has been added to the custom exports. In general, this library enables you to create a custom user interface for interacting with users. If you are not yet familiar with the workflow of the MUI library, you can read about it in Basic custom plugin development in C++. In our case, the user can choose between three characters (tab, comma, and semicolon).

Every time Dewesoft is closed, the settings of the export plugin are reset. To avoid this we can store these settings and reload them next time when Dewesoft opens. Functions for reading and writing settings are already prepared in the example.

void ExampleWriter::write_ini_file(
{
    tortellini::ini ini
    ini["UI"]["DELIMITER"] = data_delimiter; // here we store the delimiter setting

    update_location_of_dll();
    std::ofstream out((location_of_dll_folder + "example_export.ini").c_str());
    out << ini;
}

In our case, we only have one setting to save. To save (write) the setting, the following line of code is used where the first bracket (eg. UI) represents the group and the second bracket (eg. Delimiter) represents id for setting inside the group. User can change the data_delimiter value by changing the selected item in ComboBox.

To read the setting value, we use the following function, where we need the same group and unique id for each value. If the value is not stored at the moment of reading (eg. first-time running of application), we specify the default value for the variable. In our case, that will be the comma (",") character. 

void ExampleWriter::read_ini_file()
{
    tortellini::ini ini;
    update_location_of_dll();
    std::ifstream in((location_of_dll_folder + "example_export.ini").c_str());

    in >> ini;
    data_delimiter = ini["UI"]["DELIMITER"] | ","; // here we are reading the stored delimiter setting
}

Before we continue, we need to understand the format of the exported target format. In our simple case, our format requires value-based data as well as single-value channels. Therefore, we will set the export type to etValueBased inside get_ExportType function.

void ExampleWriter::get_ExportType(ExportTypes* Value)
{
    *Value = etValueBased;
}

Custom export in this example generates only one file, consisting of the header and the data. File name is obtained in put_filename function, the file stream is created in the StartExport function. Data will be written later. To generate the header part of the file we simply output all the available metadata about the datafile, separated with the new line.

void ExampleWriter::WriteInfoString(BSTR Description, BSTR Value)
{    
    write_to_output_file(bstr_to_str(Description) + " - " + bstr_to_str(Value), true);
}

This is done in all functions that provide export metadata.

The header content can be seen in the picture below.

As per the data part of the file, the format requires the first row to contain channel names, while all other rows contain values (each row contains N values, where N is the number of channels). To output the channel names, we use SetChannel function, where we also check for export sample rate to be the same for all channels (which is also a requirement). If the sample rates do not match, the export will exit and nothing will be exported.

To export the data, we use WriteValue function, its body can be seen in the code below. This function is called for each sample, which is written to the file and is later followed with the delimiter or new line (if we just wrote to the last channel). write_to_output_file was implemented in the example and will add string content to the output file.

void ExampleWriter::WriteValue(float Value)
{
    write_to_output_file(std::to_string(Value));
    sample_counter++;
    if (sample_counter % number_of_channels == 0)
    {
        write_to_output_file("", true);
        sample_counter = 0;
    }
    else
        write_to_output_file(get_data_delimiter_char());
}

If we now take a look at the data part of the output file, we can see that the data is organized and supports the Dewesoft format. As you can see, the delimiter is semicolon (";") character because it was selected in the combo box as a default delimiter.

For the purpose of this Pro tutorial, we did not have to modify any Dewesoft internals.  But if we wanted to, for example, export any other channel type or stop the export we would need to send a notification to the Dewesoft. For example, if we wanted to export array channels, we notify Dewesoft by returning True when calling the evSupportsArray function. This needs to be done inside DewesoftBridge::OnEvent function. There are many other events that can be used for notifying or sending info to the Dewesoft but they will not be covered in this tutorial.

Of course, the whole point of exporting the data is to be able to get the data in another form (to be able to open it in another software), but we will just open the exported data file again in Dewesoft. Exporting from Dewesoft and importing into Dewesoft does not make much sense, but for the purpose of this pro tutorial, this should wrap the whole thing into a complete example.

In order to test this, you need the TxtImport plugin. Once downloaded and added to the addons folder, you should be able to see it under Analyse and Import.

After selecting newly exported file, you should set correct settings (panel on the left). The delimiter should be the same as the one you exported with! In our case, that was semicolon (";").

After clicking "Import" button, the final result can be seen inside Dewesoft.