Just for the record (for the search engines since this topic comes up as the first result when searching for free pascal pidgin plugin) I have ported the minimal example found here:
http://developer.pidgin.im/wiki/CHowTo/BasicPluginHowtoto Free Pascal. Its actually much easier than I initially thought, after spending some time digging through the various headers and understanding the mechanism behind the PURPLE_INIT_PLUGIN macro and the global info variable I have successfully ported the example to FPC.
My exmple consists of a library testplugin which will implement all the functionality and is using the unit purple which defines the stuff found in the various libpurple headers. Here is the unit purple:
{ purple.pas
A minimalistic header translation for making libpurple plugins.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
}
unit purple;
{$mode objfpc}{$H+}
interface
const
LIBPURPLE = 'libpurple';
PURPLE_PLUGIN_MAGIC = 5;
PURPLE_MAJOR_VERSION = 2;
PURPLE_MINOR_VERSION = 10;
PURPLE_PRIORITY_DEFAULT = 0;
type
PGList = ^TGList;
TGList = packed record
data: Pointer;
next: PGlist;
prev: PGList;
end;
GBoolean = LongBool; {$warning FIXME, need int sized bool!}
TPurplePluginType = (
PURPLE_PLUGIN_UNKNOWN := -1, // Unknown type.
PURPLE_PLUGIN_STANDARD := 0, // Standard plugin.
PURPLE_PLUGIN_LOADER, // Loader plugin.
PURPLE_PLUGIN_PROTOCOL // Protocol plugin.
);
TPurplePluginPriority = Integer;
PPurplePluginUiInfo= Pointer; {$warnig define me!}
PPurplePluginInfo = ^TPurplePluginInfo;
PPurplePlugin = ^TPurplePlugin;
TPurplePlugin = packed record
native_plugin: GBoolean; // Native C plugin.
loaded: GBoolean; // The loaded state.
handle: Pointer; // The module handle.
path: PChar; // The path to the plugin.
info: PPurplePluginInfo; // The plugin information.
error: PChar;
ipc_data: Pointer; // IPC data.
extra: Pointer; // Plugin-specific data.
unloadable: GBoolean; // Unloadable
dependent_plugins: PGList; // Plugins depending on this
_purple_reserved1: Pointer;
_purple_reserved2: Pointer;
_purple_reserved3: Pointer;
_purple_reserved4: Pointer;
end;
TPurplePluginInfo = packed record
magic: Integer;
major_version: Integer;
minor_version: Integer;
plugintype: TPurplePluginType;
ui_requirement: PChar;
flags: LongInt;
dependencies: PGList;
priority: TPurplePluginPriority;
id: PChar;
name: PChar;
version: PChar;
summary: PChar;
description: PChar;
author: PChar;
homepage: PChar;
{ If a plugin defines a 'load' function, and it returns FALSE,
then the plugin will not be loaded.}
load: function(plugin: PPurplePlugin): GBoolean; cdecl;
unload: function(plugin: PPurplePlugin): GBoolean; cdecl;
destroy: procedure(plugin: PPurplePlugin); cdecl;
ui_info: Pointer;
extra_info: Pointer;
{ Used by any plugin to display preferences.
If #ui_info has been specified, this will be ignored.}
prefs_info: PPurplePluginUiInfo;
{ This callback has a different use depending on whether this
plugin type is PURPLE_PLUGIN_STANDARD or PURPLE_PLUGIN_PROTOCOL.
If PURPLE_PLUGIN_STANDARD then the list of actions will show up
in the Tools menu, under a submenu with the name of the plugin.
context will be NULL.
If PURPLE_PLUGIN_PROTOCOL then the list of actions will show up
in the Accounts menu, under a submenu with the name of the
account. context will be set to the PurpleConnection for that
account. This callback will only be called for online accounts.}
actions: function(plugin: PPurplePlugin; context: Pointer): PGList; cdecl;
_purple_reserved1: Pointer;
_purple_reserved2: Pointer;
_purple_reserved3: Pointer;
_purple_reserved4: Pointer;
end;
var
PluginInfo: TPurplePluginInfo = ();
function purple_plugin_register(Plugin: PPurplePlugin): GBoolean; cdecl; external LIBPURPLE;
function purple_init_plugin(Plugin: PPurplePlugin): GBoolean; cdecl;
implementation
{ This re-implements the stuff that is behind the PURPLE_INIT_PLUGIN macro.
In C the macro would define the function and export it, here we only
define it and the library must export it (because we can't export it
from within a unit directly). }
function purple_init_plugin(Plugin: PPurplePlugin): GBoolean; cdecl;
begin
Plugin^.info := @PluginInfo;
Result := purple_plugin_register(Plugin);
end;
begin
end.
and here is my hello world plugin using this unit:
library testplugin;
{$mode objfpc}{$H+}
uses
purple;
function OnLoad(Plugin: PPurplePlugin): GBoolean; cdecl;
begin
writeln('purple has called our load callback :-)');
Result := True;
end;
function OnUnload(Plugin: PPurplePlugin): GBoolean; cdecl;
begin
writeln('purple has called our unload callback :-)');
Result := True;
end;
exports
purple_init_plugin;
begin
with PluginInfo do begin
magic := PURPLE_PLUGIN_MAGIC;
major_version := PURPLE_MAJOR_VERSION;
minor_version := PURPLE_MINOR_VERSION;
plugintype := PURPLE_PLUGIN_STANDARD;
priority := PURPLE_PRIORITY_DEFAULT;
id := 'core-hello_world';
name := 'Hello World';
version := '1.0';
summary := 'Hello World Plugin';
description := 'This is a Hello-World Plugin written in Free Pascal';
author := 'Bernd Kreuss';
homepage := 'http://example.com/';
load := @OnLoad;
unload := @OnUnload;
end;
end.
This code is of course totally incomplete but it serves as an explanation how the plugin interface is supposed to work. In the official C example I linked at the beginning they are defining this ominous info variable and then they have this even more obscure macro PURPLE_INIT_PLUGIN at the very end of the file where no code is executed at all.
The structure of the info variable which they initialize in this horribly sloppy way is actually represented as a struct in the C headers and I properly defined it in TPurplePluginInfo and initialize it at the beginning when the library is loaded.
The macro PURPLE_INIT_PLUGIN actually expands to a complete function definition and export of this function. I am defining this function in purple.pas and export it from within the main unit.
The following is what is happening when Pidgin loads a plugin:
When Pidgin is started and libpurple loaded it will scan the plugin folder for all .so (or .dll) files and will probe each of them. Probing works like this:
It will load the library
It will check whether there is a function purple_init_plugin()
It will call the function purple_init_plugin()
If any of the above fails then it is not a purple plugin and it will immediately unload the library again.
When our library is loaded then the RTL will immediately execute the part between begin and end *before* anything else happens. This is where we initialize our info record.
Then shortly after this libpurple will call our exported purple_init_plugin() where we give it the pointer to our info variable and call the register function, now libpurple can read everything it needs to know about our plugin from this record.
The plugin will show up in the list of plugins and if we activate it then it will call our OnLoad() function and if we deactivate it it will call the OnUnload() function.
The above code is of course totally incomplete, there are a lot more external purple functions and type definitions that need to go into purple.pas to make it do anything useful but this is the first version after a few hours of experimenting that actually worked, it is a cleaned up snapshot of the code at the exact moment when I exclaimed "Eureka!". It can serve as a starting point for further experimentation.
___
I imported the entire Pidgin source code as a project into Eclipse CDT to be able to easily navigate and follow the definitions in the various header files. The pidgin sources also contain a "null protocol" plugin which is the hello world equivalent of a protocol plugin and meant as an example for implementing a protocol, this will be the next step I am going to investigate.