Chapter 1. Interfacing plugin to Arkamatrix engine

Arkamatrix plugin is a Windows DLL which exports 2 functions with stdcall convention. When the plugin is loaded, Arkamatrix engine (hereafter, the engine) calls the GetPluginInfo function defined as follows:

function GetPluginInfo(MainProgramBlockInfo: PMainProgramBlockInfo): PPluginInfo; stdcall;

When calling this function on plugin initialization, engine passes it a pointer to a MainProgramBlockInfo structure, defined as follows:

TMainProgramBlockInfo = packed record
  PluginRequestProc: TPluginRequestProc;
end;

The PluginRequestPrc function in this structure is the function that plugin will call when it needs engine to do something - e.g. find a remote client to connect to, or send a request to remote client. Interface of such calls will be discussed later.

To introduce itself to engine, plugin must return a pointer to TPluginInfo structure defined as follows:

TPluginInfo = packed record
  szInfoSize: DWORD;

  InterfaceVersion: DWORD;

  PluginID: TIDString;
  PluginName: TPluginName;
  PluginDescription: array[0..512] of char;
  PluginVersion: DWORD;
  PluginURL: TURLString;

  AuthorName: array [0..32] of char;
  AuthorEmail: array [0..64] of char;

  ProgramRequestProc: TProgramRequestProc;

  VCLSyncRequests: array[0..TMaxVCLSyncRequests] of longbool;

  ReservedPtr1: pointer;
  ReservedPtr2: pointer;
  ReservedPtr3: pointer;
  ReservedPtr4: pointer;
  ReservedPtr5: pointer;

end;

Here is the thorough description of each field in the above structure:

InterfaceVersion: DWORD;
set this to PLUGIN_INTERFACE_VERSION.

PluginID: TIDString; // an array of chars
any __hardcoded__ value, it will be used
to mark resources shared by this plugin

Delphi users can use Ctrl+Shift+G to generate this value
DO NOT CHANGE it when new plugin version is released.
This ID is remembered on Arkamatrix servers to specify the
type of each certain resource


PluginName: TPluginName; // an array of chars
e.g. 'FooBar Chat'

PluginDescription: array[0..512] of char;

PluginVersion: DWORD;

PluginURL: TURLString;
// an array of chars
AuthorName: array [0..32] of char;
AuthorEmail: array [0..64] of char;

if either or all above values are specified, those specified will be used on engine GUI to represent the plugin. E.g. AuthorName and AuthorEmail will be used to show how to contact the plugin author, and PluginIURL will be used to let user know where to look for plugin updates.

ProgramRequestProc: TProgramRequestProc;
this function is called by main program whenever it wants plugin to do something. This function must be implemented in the plugin.

VCLSyncRequests: array[0..TMaxVCLSyncRequests] of longbool;
this array tells main program which requests to plugin are to be done in context of the main thread. Such main thread synchronization is vital if the plugin is written on Delphi and uses VCL components in request processing routines.

Usage:

VCLSyncRequests[PROGRAM_REQ_TYPE_EXTERNAL_REQUEST]:=true;
VCLSyncRequests[PROGRAM_REQ_TYPE_FREE_PEER_REPLY_DATA]:=false;

NOTE: If your plugin is not written using Delphi VCL, and is using thread-safe code, you can safely make it

FillChar(VCLSyncRequests, sizeof(VCLSyncRequests), 0);

If you decide to use call synchronization, be sure to ask for sync only when you really need it, i.e. when processing this request involves unavoidable reference to VCL objects. If your processing routine doesn't touch VCL when processing a certain request, set VCLSyncRequests for this RequestType to false, to avoid main thread waiting for the request processing to complete and possibly blocking other tasks.

Real life example:

VCLSyncRequests[PROGRAM_REQ_TYPE_EXTERNAL_REQUEST]:=true;
VCLSyncRequests[PROGRAM_REQ_TYPE_FREE_EXT_REQ_DATA]:=true;
VCLSyncRequests[PROGRAM_REQ_TYPE_NETWORK_RESPONSE_ARRIVED]:=true;
VCLSyncRequests[PROGRAM_REQ_TYPE_PEER_REQUEST_ARRIVED]:=true;
// all those above in our sample case could use VCL - so sync is needed.
VCLSyncRequests[PROGRAM_REQ_TYPE_FREE_PEER_REPLY_DATA]:=false;
// here we simply free the memory - so no need to sync and make main thread hang
VCLSyncRequests[PROGRAM_REQ_TYPE_SHOW_RESOURCES]:=true;
VCLSyncRequests[PROGRAM_REQ_TYPE_SHUTDOWN_REQUEST]:=true;
VCLSyncRequests[PROGRAM_REQ_TYPE_SHUTDOWN]:=true;
VCLSyncRequests[PROGRAM_REQ_TYPE_REPORT_RESOURCES]:=true;
VCLSyncRequests[PROGRAM_REQ_TYPE_QUERY_RESOURCE]:=true;
// in all those above we might take some GUI-related action, and our GUI is VCL-based
// Therefore - set them to true.
VCLSyncRequests[PROGRAM_REQ_TYPE_QUERY_RESOURCE_FREE_MEMORY]:=false;
// here we simply free the memory - so no need to sync and make main thread hang

In general you are advised to try to avoid directly using VCL in request processing, unless it is really quick. E.g. presenting a dialog box while processing a request would block engine completely until user clicks a button on the box. And if e.g. PostMessage to your main form, and then process this message in your form message queue, main thread will not be blocked, and so will not be other plugins.

If you still decide to use synchronization on a certain request, try to make request processing as quick as possible.

ReservedPtr1: pointer;
ReservedPtr2: pointer;
ReservedPtr3: pointer;
ReservedPtr4: pointer;
ReservedPtr5: pointer;
reserved pointers

Example

var
  PluginRequest: TPluginRequestProc;
  PluginInfo: TPluginInfo;

begin
  with PluginInfo do begin
    szInfoSize:=sizeof(PluginInfo);
    InterfaceVersion:=PLUGIN_INTERFACE_VERSION;
    PluginName:='Hyper Chat';

    PluginDescription:='This plugin lets you chat with other Hyper Chat users in Arkamatrix network.';

    AuthorName:='Your name here';
    AuthorEmail:=yourmail@yourdomain.com';

    PluginID:='8D120041-348F-4068-948A-5FFA25C32A92';
    // IMPORTANT - make a unique PluginID for each plugin you make.
    // In Delphi you can use Ctrl+Shift+G to generate a new ID
    // It will look like ['{70FC1E2F-7FFD-49D5-86DD-958EB0A3FB7A}']
    // so don't forget to remove [ ] and { } braces.
    //
    // New plugin versions must keep the old PluginID, but there must be no
    // different plugins with the same PluginID

    PluginVersion:=$100;
    PluginURL:='http://www.yourdomain.com/something/something_else.htm';

    VCLSyncRequests[PROGRAM_REQ_TYPE_EXTERNAL_REQUEST]:=true;
    VCLSyncRequests[PROGRAM_REQ_TYPE_FREE_EXT_REQ_DATA]:=true;
    VCLSyncRequests[PROGRAM_REQ_TYPE_NETWORK_RESPONSE_ARRIVED]:=true;
    VCLSyncRequests[PROGRAM_REQ_TYPE_PEER_REQUEST_ARRIVED]:=true;
    VCLSyncRequests[PROGRAM_REQ_TYPE_FREE_PEER_REPLY_DATA]:=false;
    VCLSyncRequests[PROGRAM_REQ_TYPE_SHOW_RESOURCES]:=true;
    VCLSyncRequests[PROGRAM_REQ_TYPE_SHUTDOWN_REQUEST]:=true;
    VCLSyncRequests[PROGRAM_REQ_TYPE_SHUTDOWN]:=true;
    VCLSyncRequests[PROGRAM_REQ_TYPE_REPORT_RESOURCES]:=true;
    VCLSyncRequests[PROGRAM_REQ_TYPE_QUERY_RESOURCE]:=true;
    VCLSyncRequests[PROGRAM_REQ_TYPE_QUERY_RESOURCE_FREE_MEMORY]:=false;

    VCLSyncRequests[PROGRAM_REQ_TYPE_STARTUP_LOGGED_ON]:=true;

    ProgramRequestProc:=@ProgramRequest;
    // a pointer to plugin-implemented function used by program to contact plugin
    // it will be discussed in Chapter 2
  end;
end;

OK, your plugin filled in and returned the structure to the engine. The engine has been loaded and ready to work. Let's see what happens next.

Chapter 2 >>