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
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.