Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Method to Implement Plug-In Functionality

0.00/5 (No votes)
29 Jan 2009 1  
This article deals with the ability to support multiple communication protocols through the use of Plug-In Libraries
PluginDemo.JPG

Introduction

One of the key requirements of a project that I am involved with, is the ability to support multiple communication protocols through the use of Plug-In Libraries. A search of the usual web locations turned up little in practical examples of the design of such a system for an application. This article will attempt to fill that void and explain the approach that was taken to meet the project requirement.

Background

The project involved was a generic utility control system, which was divided into two major sections; 1) The core application which provides a GUI, and access to several system resources such as serial communications, events, timers, and a database, and 2) A series of libraries used to support the communication protocols of the devices to be controlled. These devices can be from several different vendors, and typically do not ‘speak’ the same command language.

It was decided early in the project to use Dynamically Linked Libraries (DLLs) to provide the plug-in capability.

Design Issues

The first design issue that needed to be addressed was the composition of the interface between the core application and the plug-in library. The functions that comprise this interface need to be decoupled from the specifics of the devices that will be controlled. This will allow the interface to be generic, and able to be used with devices that use any communication protocol. These functions should represent the “least common denominator” of all of the possible commands that will be made to the controlled devices. A partial list of these commands (and those that will be used for example) would be:

  • plugin_PluginInit
  • plugin_DeviceReset
  • plugin_DeviceWriteData
  • plugin_DeviceReadData

The plug-in commands will be implemented as standard DLL functions, and any plug-in conforming the interface design will contain these functions.

The next design issue that needed to be addressed was the resource access (services) that would be made available to the plug-in, by the core application. The list of service functions provided by the core application needs to be complete in that all service functions must be available to any command function in the plug-in library.

The simplest approach to provide the plug-in access to the service functions would have been to use standard callback functions, however this would have the potential of requiring multiple callback function addresses to be passed in the plug-in command function calls. To simplify this, the plug-in will be passed a structure containing all function pointers of all of the required service functions when the plug-in is loaded and initialized. A partial list of these service functions (and those that will be used for example) would be:

  • cbSerialOpen
  • cbSerialClose
  • cbSerialRead
  • cbSerialWrite

This list of service functions will be implemented as standard DLL callback functions, however the pointers to these functions will be sent to the plug-in DLL in the call to plugin_PluginInit.

Note the function pointer names and the function names cannot be the same.

Implementation

The attached project demonstrates this plug-in method for a simple Win32 based application. The solution includes separate projects for the core application and a single plug-in DLL.

The Library Header File

In the plug-in header file (plugin.h), you declare the interface to the plug-in DLL. The functions declared here represent the commands that the core application will send to the devices. In the example project these functions are trivial, returning void and accepting only a HWND. In a real example, these function signatures will reflect the types of data that need to be sent to the plug-in, and the types of data or status codes to be returned.

extern "C" __declspec(dllexport) void plugin_PluginInit(HWND, tServiceLookup*);
extern "C" __declspec(dllexport) void plugin_DeviceReset(HWND);
extern "C" __declspec(dllexport) void plugin_DeviceWriteData(HWND);
extern "C" __declspec(dllexport) void plugin_DeviceReadData(HWND);

Note the additional tServiceLookup* parameter for the PluginInit call, this is the structure that is used to pass the service function pointers to the plug-in.

Also in the plug-in header file, the function pointer types to both the service functions and command functions are defined, as well as the plug-in function pointers themselves.

// Typedefs for the service functions (function pointers)
// In a more meaningful implementation, the signatures here would
// reflect valid data being passed to the service function as arguments.
typedef void (*svcSerialOpen)();
typedef void (*svcSerialClose)();
typedef void (*svcSerialRead)();
typedef void (*svcSerialWrite)();
 
// Typedefs for the plugin command functions (function pointers)
// In a more meaningful implementation, the signatures here would
// reflect valid data being passed to the command functions as arguments.
typedef void (*pluginInitFunct)( HWND, tServiceLookup* );
typedef void (*pluginFunct)( HWND );
 
// Plugin Function pointers
pluginInitFunct   PluginInit;
pluginFunct       DeviceReset;
pluginFunct       DeviceReadData;
pluginFunct       DeviceWriteData;

The service function lookup table structure is defined in the same file.

// Service function lookup structure
typedef struct
{
    svcSerialOpen        serialopenfunc;
    svcSerialClose       serialclosefunc;
    svcSerialRead        serialreadfunc;
    svcSerialWrite       serialwritefunc;
}
tServiceLookup;

The Library Implementation File

In the plug-in implementation file (plugin.cpp), a file scope variable of the tServicesLookup* type is declared. This structure will serve as the table of service functions available to the plug-in, provided by the core application.

// The service function pointer structure (file scope)
tServiceLookup* services;

This table is allocated and initialized in the PluginInit function. Since this structure has file scope, it is available to all of the command functions defined in the library implementation file.

// Create the DLL copy of the services table
services = new tServiceLookup;

if( services )
{
    // Copy the values from the Core App copy to the DLL copy
    services->serialopenfunc = svc->serialopenfunc;
    services->serialclosefunc = svc->serialclosefunc;
    services->serialreadfunc = svc->serialreadfunc;
    services->serialwritefunc = svc->serialwritefunc;
}

The reader can see in the example how the service function pointers are called in the individual command functions.

The Core Application File

In the core application file (CoreApp_main.cpp), you define the service functions.

During the initialization phase of the core application, you will load the plug-in as a normal DLL using the LoadLibrary system call. If the library is successfully loaded, the command function pointers are set using the GetProcAddress system calls. With both calls, apply error checking to make sure that the library is loaded and all of the function pointers are initialized. The final initialization step for the plug-in is to create a tServiceLookup structure and initialize it with the address of the service functions. This structure is to be used in the call to PluginInit.

//*****************************************************************************
//         Set the function pointers of the plugin command functions          *
//                                                                            *
//*****************************************************************************

// Plugin (DLL) Instance variable
HINSTANCE      hLib;

// Load the plug-in DLL
hLib = LoadLibrary( L"plugin.DLL" );
if( hLib == NULL )
{
    MessageBox( hWnd, L"Could Not Load Plugin", L"DLL ERROR", MB_ICONEXCLAMATION );
    break;
}

PluginInit      =(pluginInitFunct)GetProcAddress((HMODULE)hLib, "plugin_PluginInit");
DeviceReset     =(pluginFunct)GetProcAddress((HMODULE)hLib, "plugin_DeviceReset");
DeviceReadData  =(pluginFunct)GetProcAddress((HMODULE)hLib, "plugin_DeviceReadData");
DeviceWriteData =(pluginFunct)GetProcAddress((HMODULE)hLib, "plugin_DeviceWriteData");

if((PluginInit            == NULL) || \
   (DeviceReset           == NULL) || \
   (DeviceReadData        == NULL) || \
   (DeviceWriteData       == NULL))
{
    // Could not load functions
    MessageBox( hWnd, L"Could Not Load Functions", L"DLL ERROR", MB_ICONEXCLAMATION );
    FreeLibrary((HMODULE)hLib);
    break;
}


//*****************************************************************************
//    Set the function pointers of the core application service functions     *
//                                                                            *
//*****************************************************************************

// Initialize the callback system
callbacks.serialopenfunc = (svcSerialOpen)cbSerialOpen;
callbacks.serialclosefunc = (svcSerialClose)cbSerialClose;
callbacks.serialreadfunc = (svcSerialRead)cbSerialRead;
callbacks.serialwritefunc = (svcSerialWrite)cbSerialWrite;

Once the plug-in is loaded and initialized, the command functions may be called as needed. On program exit, remember to cleanup any allocated memory and call FreeLibrary on the plug-in DLL instance handle.

Summary

The preceding discussion, and attached example, included only one plug-in DLL. It would be simple for the reader to implement code to “swap out” plug-in DLLs as needed, or wrap the individual plug-ins in separate objects that could be created and used as needed, depending on the applications requirements.

The example was trivial, in that the plug-in did nothing other than post a MessageBox to show the calls as they were being made. A practical implementation would include meaningful data passed in as arguments to the command and service functions. The author hopes that this article has shown enough information for the reader to accomplish this.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here