Getting Started with Plug-in Development

This topic provides a startup help for developing Communicator plug-ins for the use with the process integration mode of MERLIC. If you develop a Communicator plug-in for the first time or if you are not sure how to start, you can use this guide to get information about the first steps and the general workflow for the plug-in development up to building and starting the plug-in.

The easiest way to develop a custom Communicator plug-in is to use one of the provided example plug-ins as a basis and customize it as required to your use case. The general workflow for this is described in the first two sections of this topic. The last sections provides basic information about how to build and start the Communicator plug-in.

Using an Example Plug-in as Basis

Customizing the Code of the Plug-in

Building and Providing the Plug-in for the Communicator

Using an Example Plug-in as Basis for a Custom Plug-in

You can use one of our example Communicator plug-ins as basis for your plug-in and customize the existing code as required. This way, you do not have to start from scratch and save a lot of time.

  1. Go to the example directory of the Communicator plug-ins:
    • If MERLIC was installed with administrator rights, the examples are located by default in the directory "%PROGRAMFILES%\MVTec\MERLIC-5.5".
    • If MERLIC was installed without administrator rights, the examples are located by default in the directory "%LOCALAPPDATA%\Programs\MVTec\MERLIC-5.5".
  2. Copy the folder of one of the example plug-ins, e.g., "event-logger".
  3. Rename the .cpp file of the example plug-in, e.g., "event-logger.cpp".
  4. Adjust the respective "CMakeList.txt" file:
    • File name of the .cpp file
    • Name of the target
    • (Description)
  5. Check if all includes required for your plug-in are defined. For Communicator plug-ins, the following includes are mandatory:
  6. Start to customize the .cpp file as described in the following sections.

Customizing the Code of the Plug-in

API Functions of the Plug-in

Check the implementation of the plug-in functions in your plug-in. Some of the functions are required for each Communicator plug-in, i.e., MVInfo, MVInit_V2(), MVOpen, MVClose, MVStart, and MVStop. Therefore, you can already find an implementation of those in your copied plug-in. You just need to customize the code for your plug-in.

In addition, you can make use of the optional functions MVExpose and MVValidate which are required if you want to define plug-in parameters that can be configured via the graphical user interface of the MERLIC RTE Setup and if you want to provide a proper validation of the parameter changes.

Expand the following drop-down sections to get more detailed information about the function declarations and individual functions that are required for each Communicator plug-in. The sections show the respective code excerpts based on the example plug-in "event-logger" and describe which parts of the code need to be customized when developing your plug-in. The drop-down sections for the optional functions also give more detailed information about their basic structure and for which use cases they should be implemented.

See also the topic Life Cycle of a Plug-in Instance for specific information about the life cycle of a plug-in instance, especially regarding the implementation and use of the plug-in API functions MVStart, MVStop, MVOpen, and MVClose. You can get information how to ensure that your plug-in is valid and compatible for the use with the Communicator and MERLIC. It also contains information about the internal processes when starting the Communicator and describes the steps that are performed when an instance of a plug-in is used, e.g., which functions are called for certain situations.

Required function declarations

Make sure to keep the following function declarations for the API functions symbols, which are expected by the Communicator to be present in the plug-in shared library, in your .cpp file. They are required for each Communicator plug-in.

extern "C" MVUserExport MVCode_t MVInfo(MVPluginInfo_t* pMVInfo);
extern "C" MVUserExport MVCode_t MVInit_V2(MVClass_V2_t* MVClass);

If the plug-in is written in a language different from C, these functions need to be given C linkage, e.g., by marking them as extern "C" in the case of plug-ins written in C++. All further API functions need not have C linkage as the MVInit_V2() function is expected to set up pointers to the appropriate functions.

MVInfo and MVInit_V2()
MVInfo

In MVInfo, you just have to set the capability of the plug-in, i.e., the value of the placeholder <CAPABILITY> in the code. You can choose between "monitor", "control" or both capabilities, i.e., eMVCapabilities_Monitor, eMVCapabilities_Control, or "eMVCapabilities_Monitor | eMVCapabilities_Control".

Plug-ins with the "monitor" capability can receive "events" that are provided by MERLIC whereas plug-ins with the "control" capability are allowed to send actions to MERLIC using either the MV_QueueAction() or MV_QueueAction_WithInfo() function. If you want to allow your plug-in to receive "events" and send "actions", you have to set the capability to "eMVCapabilities_Monitor | eMVCapabilities_Control".

Define the capability of the plug-in:

MVCode_t MVInfo(MVPluginInfo_t* pMVInfo)
{
pMVInfo->ApiMajor = MV_API_MAJOR;
pMVInfo->ApiMinor = MV_API_MINOR;
pMVInfo->ApiPatch = MV_API_PATCH;
pMVInfo->Flags = <CAPABILITY>;
return MV_CODE_OK;
}
MVInit_V2()

In MVInit_V2(), you have to set the function pointers to the remaining plug-in API functions to make them known to the Communicator. In our example plug-ins, the mandatory plug-in functions MVOpen, MVStart, MVStop, and MVClose are used. In addition, the function pointers to the optional functions MVExpose and MVValidate are set in MVInit_V2().

To customize the MVInit_V2() function for your plug-in, it is sufficient to set up the pointers for the optional functions MVExpose and MVValidate depending on whether you want to implement the respective function or not. The function MVExpose is required if you want to define parameters for the plug-in and to enable the configuration of your plug-in parameters via the MERLIC RTE Setup. The function MVValidate is required if you also want to provide a proper validation of the parameter changes. Please see also the sections for MVExpose and MVValidate for more information.

Set up MVExpose and MVValidate functions:

MVCode_t MVInit_V2(MVClass_V2_t* MVClass)
{
MVClass->MVOpen = MVOpen;
MVClass->MVStart = MVStart;
MVClass->MVStop = MVStop;
MVClass->MVClose = MVClose;
MVClass->MVExpose = <nullptr|MVExpose>;
MVClass->MVValidate = <nullptr|MVValidate>;
return MV_CODE_OK;
}

It is possible to use arbitrary names for the functions. However, you have to make sure to set up the function pointer members of the MVClass_V2_t data structure accordingly as shown in the example code below:

Example: Set up the functions with arbitrary names

MVCode_t MVInit_V2(MVClass_V2_t* MVClass)
{
MVClass->MVOpen = OpenPlugin;
MVClass->MVStart = StartPlugin;
MVClass->MVStop = StopPlugin;
MVClass->MVClose = ClosePlugin;
MVClass->MVExpose = ExposeConfiguration;
MVClass->MVValidate = ValidateChanges;
return MV_CODE_OK;
}

The name of the MVInit_V2() function indicates the major version number of the Communicator API that is used. This mechanism allows a plug-in to support multiple, otherwise incompatible, major versions of the Communicator API in the future by implementing MVInit_Vx for each supported major version and setting up the function pointers to either the same or different implementations as required.

MVOpen and MVClose
MVOpen

In MVOpen, a "user data" structure can be created which aggregates the collective state of one plug-in instance. The use of such a user data structure is the recommended way to ensure that the plug-in can manage some internal state which is then accessed and modified by each of the plug-in API functions as well as any additional threads that the plug-in might create. Typically, the data structure can be allocated in MVOpen and is then associated with the plug-in handle via MV_Plugin_SetUserData() to make sure that it is available for the entire existence of the plug-in instance. The pointer to the user data can then be retrieved from the plug-in handle in each of the other plug-in API functions via MV_Plugin_GetUserData(), e.g., in MVClose to free up the memory again.

Using the user data mechanism ensures that the same plug-in can be instantiated multiple times and that the internal state of the plug-in instances does not interfere with one another as would be the case if globals or singletons were used instead. In case global state cannot be avoided due to requirements from third-party dependencies and a plug-in cannot be instantiated safely multiple times, it is recommended to detect a repeated instantiation and return from MVOpen with a return code which is different from MV_CODE_OK.

If there are no restrictions that keep you from using this user data mechanism, you can customize the functions of the example plug-in by adjusting the type of the user data structure as required. In our example plug-ins, the pointer to the user data is called *ud.

Adjust the user data struct:

static MVCode_t MVOpen(MVPlugin_t handle)
{
// Create the user data struct
<USER_DATA_STRUCT_TYPE>* ud = new <USER_DATA_STRUCT_TYPE>;
// ...and associate it with the plug-in handle.
if (MV_Plugin_SetUserData(handle, static_cast<void*>(ud))
return MV_CODE_OK;
}
MVClose

In MVClose, the pointer to the user data is retrieved to free up the memory again. For this, you have to adjust the data type for the user data pointer as defined in MVOpen.

Adjust the user data struct:

static MVCode_t MVClose(MVPlugin_t handle)
{
// Retrieve the pointer to the user data from the plug-in handle and
// free up the memory.
<USER_DATA_STRUCT_TYPE>* ud = nullptr;
if (MV_Plugin_GetUserData(handle, reinterpret_cast<void**>(&ud)) != MV_CODE_OK)
delete ud;
return MV_CODE_OK;
}
MVStart and MVStop

MVStart and MVStop are invoked when a plug-in instance is started and stopped, respectively. In between, the plug-in is considered to be running and is allowed to queue actions (in case of "control" capability) and to receive events (in case of the "monitor" capability). The same plug-in may be started and stopped multiple times and its configuration may be changed while it is not running. Hence, MVStart is typically the place where you can read the configuration options using MV_Plugin_GetConfig() and MV_PluginConfig_GetUserParameter().

MVStart

To access the internal state of the plug-in instance, you can recall the pointer to the user data. In the code snippet below, it is called *ud. You can then use and modify the field of <USER_DATA_STRUCT_TYPE> in the custom implementation.

In addition, you can remove the code that defines how the example plug-in reacts to "events" ( in case, you are using the "event-logger" plug-in as basis ) and add the desired implementation of your custom plug-in instead.

In the implementation of your plug-in, the plug-in should be able to process events as fast as MERLIC is sending them and must be able to handle dropped events. If a plug-in is processing events slower than MERLIC is sending them, events are queued which may lead to delays. If the queue is full, additional events will be dropped. This may happen if MERLIC is running in continuous mode and the plug-in takes longer to process events than MERLIC needs for a single iteration, or when a plug-in is triggering single executions faster than another plug-in is handling the results. Therefore, this issue should already be considered during the development of the plug-in.

Adjust the user data struct and insert your code:

static MVCode_t MVStart(MVPlugin_t handle)
{
// Retrieve the pointer to the user data from the plug-in handle.
<USER_DATA_STRUCT_TYPE>* ud = nullptr;
if (MV_Plugin_GetUserData(handle, reinterpret_cast<void**>(&ud)) != MV_CODE_OK)
// Start the event thread and store its handle in the user data struct.
ud->mEventThread = std::thread([handle] {
bool stop_event_thread = false;
while (!stop_event_thread)
{
MVEvent_t event;
MVCode_t status = MV_TryGetEvent(handle, MV_TIME_INFINITE, &event);
// Insert your implementation here
MV_Event_Clear(&event);
} // end event polling thread
});
return MV_CODE_OK;
}

For plug-ins with the capability eMVCapabilities_Monitor, i.e., plug-ins that handle "events", it is quite typical to use the user data to store the handle to a thread which continuously polls for new events, i.e., an "event loop". In order to be able to terminate such an event loop thread again, a special event of type eMVEvent_Shutdown will be enqueued when a plug-in is stopped. This guarantees to be the last event that the plug-in will receive before it is restarted and thus it is possible to break out of the event loop at that point.

In the "event-logger" example, this is done by setting "stop_event_thread" to "true":

Terminate the event loop in case of the Shutdown event:

{
MV_Log(handle, eMVSeverity_Info, format_msg("Event '%s' received; terminating event loop.", EventToString(event_type)));
stop_event_thread = true;
break;
}
MVStop

In case an event loop thread has been started in MVStart, you have to join that thread again in MVStop. MVStop is called by the Communicator at the same time the Shutdown event is enqueued, so there is no need to take any additional action to stop the thread. In case your plug-in manages additional resources from third-party libraries, such as an I/O context or a database connection, it may be necessary to terminate those explicitly in MVStop.

Adjust the type of the user data struct:

static MVCode_t MVStop(MVPlugin_t handle)
{
// Retrieve the pointer to the user data from the plug-in handle.
<USER_DATA_STRUCT_TYPE>* ud = nullptr;
if (MV_Plugin_GetUserData(handle, reinterpret_cast<void**>(&ud)) != MV_CODE_OK)
// Rejoin the event thread. It should be in the process of terminating after
// having received the shutdown event.
if (ud->mEventThread.joinable())
ud->mEventThread.join();
return MV_CODE_OK;
}
MVExpose and MVValidate
MVExpose

This function must be implemented if you want to define parameters for the plug-in and enable the configuration of these parameters via the graphical user interface of the MERLIC RTE Setup.

You have to define the set of plug-in parameters and their meta data ("user parameter description") for the configuration and use the family of MV_PluginUserParameterDescription_*() API functions to expose the user parameter description. If only predefined values should be allowed for the parameters, you also have to impose the required constraints via MV_PluginUserParameterDescription_ImposeConstraint().

This function is optional. However, if it is not implemented, the configuration of the plug-in is not possible, neither via the MERLIC RTE Setup nor manually via JSON files because no parameters have been defined for the plug-in.

Define the user parameter description:

{
// Insert your implementation here
return MV_CODE_OK;
}
MVValidate

This function can be implemented if you want to provide a validation for more complex cases of parameter changes that are made in the MERLIC RTE Setup, e.g., if the allowed value range of a parameter depends on other parameter values. MVValidate also enables you to provide feedback to the user about the state of the parameters, e.g., to highlight invalid user parameter values or show specific error messages. This data structure is called "validation result".

For the implementation you have to use the API functions MV_PluginConfig_* and MV_PluginConfigValidation_*.

Implement the validation of parameter changes and feedback of the parameter state:

{
// Insert your implementation here
return MV_CODE_OK;
}

Building and Providing the Plug-in for the Communicator

After the implementation of a plug-in, you have to build the plug-in and make sure that the plug-in is available for the Communicator. The following section gives an overview of how to do this. However, to get more detailed information on the build process and how to provide a plug-in for the Communicator, see the topic Building an Example Plug-in.

Building the Plug-in

With CMake, the build system artifacts (e.g. Makefiles) are generated automatically according to the respective platform, toolchain, and generator.

To build the release version of the plug-in, you can use the following commands:

cmake -B <BUILD DIR> -S <PLUG-IN SOURCE DIR> -DCMAKE_PREFIX_PATH=<MERLIC INSTALL DIR> -DCMAKE_BUILD_TYPE=Release
cmake --build <BUILD DIR> --config Release

To get more detailed information on how to build a plug-in and additional arguments, see the section How to Build a Plug-in in the topic Building an Example Plug-in.

Providing the Plug-in for the Communicator

To provide a plug-in for the Communicator, the following prerequisites must be fulfilled:

  • The plug-in is placed in a predefined plug-in directory, e.g., "%PROGRAMFILES%\MVTec\MERLIC-5.5\bin\x64-win64" by default.
  • The file name of the corresponding dynamic library starts with the prefix "pmV", e.g., "pMVevent-logger.dll".

Therefore, you have to make sure to either copy the plug-in to the predefined plug-in directory or to add the directory in which the plug-in is stored to the list of plug-in directories. In addition, you have to make sure to add the required prefix to the file name of the dynamic library. For more detailed information, see the section Providing the Plug-in for the Communicator in the topic Building an Example Plug-in.

As soon as your plug-in is stored in the defined plug-in directory with the required prefix, you can start using the plug-in with the Communicator. For more information on how to start a Communicator plug-in, see the topic "Starting the Communicator and Plug-ins" in the Communicator Manual.

MVPlugin_t
struct _MVPlugin_t * MVPlugin_t
Opaque data type which represents a plug-in instance handle.
Definition: mv_plugin_control_def.h:76
MVPluginConfig_t
struct _MVPluginConfig_t * MVPluginConfig_t
Opaque data type which makes the plug-in instance's configuration accessible.
Definition: mv_plugin_control_def.h:85
MV_TryGetEvent
MVLibExport MVCode_t MV_TryGetEvent(MVPlugin_t handle, int32_t timeout, MVEvent_t *pEvent)
Attempts to retrieve a new event from this plug-in's event queue.
MV_API_MINOR
#define MV_API_MINOR
Minor API version; used to initialized MVPluginInfo_t::ApiMinor.
Definition: mv_version.h:26
MVEvent_t
struct _MVEvent_t * MVEvent_t
Opaque data type which holds all information related to a single event.
Definition: mv_event_def.h:144
_MVPluginInfo_t
Data structure which is populated by MVInfo with basic information about the plug-in.
Definition: mv_plugin_info.h:36
MVPluginConfigValidation_t
struct _MVPluginConfigValidation_t * MVPluginConfigValidation_t
Opaque data type which stores the result of a configuration parameter validation.
Definition: mv_plugin_config_def.h:242
_MVPluginInfo_t::Flags
uint32_t Flags
Plug-ins should set this to a bitmask of the values in MVPluginCapabilities_t, reflecting the restric...
Definition: mv_plugin_info.h:52
MVPluginUserParameterDescription_t
struct _MVPluginUserParameterDescription_t * MVPluginUserParameterDescription_t
Opaque data type which stores the plug-in-provided description of user configuration parameters.
Definition: mv_plugin_config_def.h:230
_MVPluginInfo_t::ApiMinor
uint32_t ApiMinor
Minor version of API used to compile the plug-in; plug-ins should set this field to MV_API_MINOR.
Definition: mv_plugin_info.h:43
_MVClass_V2_t
Data structure holding function pointers to plug-in API functions.
Definition: mv_class_v2.h:38
eMVSeverity_Info
@ eMVSeverity_Info
Info severity.
Definition: mv_plugin_control_def.h:30
MV_API_PATCH
#define MV_API_PATCH
Patch API version; used to initialized MVPluginInfo_t::ApiPatch.
Definition: mv_version.h:27
MV_CODE_OK
#define MV_CODE_OK
The command was processed successfully.
Definition: mv_error_def.h:77
mv_plugin_api_v2.h
API version 2 definition and header includes.
MVCode_t
uint32_t MVCode_t
Type alias used for return codes.
Definition: mv_error_def.h:59
MV_TIME_INFINITE
#define MV_TIME_INFINITE
Infinite timeout: function blocks indefinitely until an event is received.
Definition: mv_event_def.h:121
_MVPluginInfo_t::ApiMajor
uint32_t ApiMajor
Major version of API used to compile the plug-in; plug-ins should set this field to MV_API_MAJOR.
Definition: mv_plugin_info.h:39
MV_Plugin_GetUserData
MVLibExport MVCode_t MV_Plugin_GetUserData(MVPlugin_t handle, void **pUserData)
Gets the plug-in's user data.
MV_API_MAJOR
#define MV_API_MAJOR
Major API version; used to initialized MVPluginInfo_t::ApiMajor.
Definition: mv_version.h:25
MV_Event_Clear
MVLibExport void MV_Event_Clear(MVEvent_t *pEvent)
Destroys the event.
eMVEvent_Shutdown
@ eMVEvent_Shutdown
Shutdown
Definition: mv_event_def.h:87
MV_Plugin_SetUserData
MVLibExport MVCode_t MV_Plugin_SetUserData(MVPlugin_t handle, void *pUserData)
Sets the plug-in's user data.
MV_CODE_INTERNAL_ERROR
#define MV_CODE_INTERNAL_ERROR
A severe error occurred that caused the internal state of the vision system to become inconsistent.
Definition: mv_error_def.h:88
_MVPluginInfo_t::ApiPatch
uint32_t ApiPatch
Patch version of API used to compile the plug-in; plug-ins should set this field to MV_API_PATCH.
Definition: mv_plugin_info.h:47
mv_version.h
Current version of the Communicator Plug-in API.
MV_Log
MVLibExport void MV_Log(MVPlugin_t handle, MVSeverityLevel_t severity, const char *message)
Logs a message of given severity.