Writing Simba Plugins

Plugin overview

Plugins for Simba can be written in virtually every language. (Make sure to read Caveats though)

Simba Plugin ABI

First of all, it is important to know in what way you should represent your functions to Simba. Using the newest ABI is recommended. Currently, all three ABIs are supported; but older ones may be deprecated in subsequent releases.

Plugins with ABI < 2 are not exported to Lape.

Simba ABI Version 0

This is the first Simba plugin ABI. Any plugin that does not export GetPluginABIVersion is treated as this ABI.

In this ABI GetTypeInfo passes the arguments as a native pascal String rather than a PChar. This is error prone; and versions > 0 use PChar.

The calling convention for all functions is expected to be stdcall.

Simba ABI Version 1

In this ABI GetTypeInfo passes the arguments as a PChar.

The calling convention for all functions (except GetPluginABIVersion) is expected to be stdcall.

Simba ABI Version 2

The calling convention for all functions is expected to be cdecl on 32 bit platforms; on platforms where cdecl does not exist, the native calling convention is expected.

Functions

GetPluginABIVersion

This function should return the ABI version of the plugin. The calling convention for this function is always cdecl on 32 bit and the native calling convention if cdecl does not exist.

SetPluginMemManager

procedure SetPluginMemManager(MemMgr : TMemoryManager);

This function should be implemented when one is writing a plugin in Free Pascal. Using the Simba memory manager it is possible to easily pass arrays and other managed structures from the script to the plugin. It is however wise to also store the old memory manager of your plugin and restore it when OnDetach is called.

For other languages, this function doesn’t really help - however you should realise that it is not possible to pass pascal strings and arrays directly to the script in a safe manner. Doing so will require some hacks (it is possible), but might also lead to memory leaks if implemented wrong or used wrong in scripts.

Warning

Simba does not guarantee that this function will be called only once; make sure you don’t overwrite your old memory manager when this function is called twice.

OnAttach

procedure OnAttach(info: Pointer);

This method is called when the plugin is loaded. Currently info will always be zero, but it might be used to pass information in the future.

OnDetach

procedure OnDetach();

This method is called just before the plugin is beeing freed. If you changed your memory manager to Simba’s; you should now revert to your old one.

GetFunctionCount

function GetFunctionCount: integer;

The first function, GetFunctionCount returns how many functions are to be imported.

GetFunctionInfo

function GetFunctionInfo(x: Integer; var ProcAddr: Pointer; var ProcDef: PChar): integer;

Simba will then call GetFunctionInfo and GetFunctionCallingConv N amount of times (where N is the result of GetFunctionCount) with x increased by one every time. Obviously, each function must be mapped to a specific value of x.

For GetFunctionInfo, the value of ProcAddr should be set to the address of the procedure to be called; and ProcDef should contain the definition (in Pascal types) of the function.

GetFunctionCallingConv

Warning

This function is deprecated as of ABI >= 2

function GetFunctionCallingConv(x: integer): integer; stdcall;

GetFunctionCallingConv returns the calling convention for the specific function. Currently, the only two support conventions are stdcall (0) and register (1).

GetTypeCount

GetTypeInfo

Exporting functions to scripts

To let Simba know what functions you want to export to a script, your plugin needs to implement the following functions: GetFunctionCount and GetFunctionInfo. Refer to their sections

Exporting types to scripts

Warning

TODO

TTarget_Exported

Warning

TODO

Caveats

If you’re writing a plugin in a language other than Free Pascal, you’ll not be able to share arrays and strings with Simba in an easy manner. (It is possible to “craft” pascal-type strings and arrays)

Pascal Arrays

Say we have an array of foo called bar. bar[0] holds the first element of the array. bar - Sizeof(Pointer) contains the length of the array, and bar - Sizeof(Pointer) * 2 contains the reference count of the array. If you want to share an array with Simba, make sure the reference is count is high enough so that Simba/Free Pascal won’t try to free it for you.

Pascal Strings

Warning

I believe pascal strings are very similar to pascal arrays, but I am not completely sure.

Sharing Arrays and Strings with a FPC Plugin

To share arrays and strings in a nice way with a FPC plugin, you need to create a function called SetPluginMemManager as shown above and make sure it is exported properly. Simba will try to call this function when loading the plugin and will pass the plugin its own memory manager. Use FPC’s SetMemoryManager to change your own memory manager to Simba’s memory manager. When OnDetach is called, make sure you reset your memory manager. See SetPluginMemManager.

Sample FPC Plugin

{ Example based upon the SPS Plugin }
library project1;

{$mode objfpc}{$H+}

{$macro on}
{$define callconv:=
    {$IFDEF WINDOWS}{$IFDEF CPU32}cdecl;{$ELSE}{$ENDIF}{$ENDIF}
    {$IFDEF LINUX}{$IFDEF CPU32}cdecl;{$ELSE}{$ENDIF}{$ENDIF}
}

uses
  classes, sysutils, math
  { you can add units after this };

var
  OldMemoryManager: TMemoryManager;
  memisset: Boolean = False;


  type T3DIntegerArray = array of array of array of integer;

function HelloPlugin(s: String): String; callconv
begin
  result := s;
end;

function GetPluginABIVersion: Integer; callconv export;
begin
  Result := 2;
end;

procedure SetPluginMemManager(MemMgr : TMemoryManager); callconv export;
begin
  if memisset then
    exit;
  GetMemoryManager(OldMemoryManager);
  SetMemoryManager(MemMgr);
  memisset := true;
end;

procedure OnDetach; callconv export;
begin
  SetMemoryManager(OldMemoryManager);
end;

function GetTypeCount(): Integer; callconv export;
begin
  Result := 1;
end;

function GetTypeInfo(x: Integer; var sType, sTypeDef: PChar): integer; callconv export;
begin
  case x of
    0: begin
        StrPCopy(sType, 'T3DIntegerArray');
        StrPCopy(sTypeDef, 'array of array of array of integer;');
       end;

    else
      x := -1;
  end;

  Result := x;
end;

function GetFunctionCount(): Integer; callconv export;
begin
  Result := 1;
end;

function GetFunctionInfo(x: Integer; var ProcAddr: Pointer; var ProcDef: PChar): Integer; callconv export;
begin
  case x of
    0:
      begin
        ProcAddr := @HelloPlugin;
        StrPCopy(ProcDef, 'function HelloPlugin(s: String): String;');
      end;

    else
      x := -1;
  end;

  Result := x;
end;

exports GetPluginABIVersion;
exports SetPluginMemManager;
exports GetTypeCount;
exports GetTypeInfo;
exports GetFunctionCount;
exports GetFunctionInfo;
exports OnDetach;

begin
end.