开发者

Writing Delphi/FreePascal DLL to be called from gcc application

开发者 https://www.devze.com 2023-03-08 08:44 出处:网络
I need to make parts of my Win32 Delphi app available to another company\'s Linux gcc program. Throughput and deployment requirements make any s开发者_开发百科ort of remote service unsuitable so I\'m

I need to make parts of my Win32 Delphi app available to another company's Linux gcc program.

Throughput and deployment requirements make any s开发者_开发百科ort of remote service unsuitable so I'm looking at using FreePascal to build a .SO (Linux equivalent of a DLL) that the gcc app can call.

It's a long time since I used C/C++ and never on Linux so I'm a little unsure as to how best to structure the DLL/SO interface for compatibility with a gcc caller.

Here's a representation of my data structures

TFoo = record
  x, y : double;
  a : smallint;
  b : string;
end;

TBar = record
  a : double;
  b : longint;
  c : string;
end;

TFooBar = record
  foo : array of TFoo;
  bar : array of TBar;
end;

procedure Process(const inFooBar : TFooBar);

To make this Process method externally available via a FreePascal .SO how do I need to modify these declarations? I'm thinking something along the lines of

TFoo = record
  x, y : double;
  a : smallint;
  b : PChar;
end;

TBar = record
  a : double;
  b : longint;
  c : PChar;
end;

TFooBar = record
  foo : ^TFoo;
  foo_count : longint;
  bar : ^TBar;
  bar_count : longint;
end;

procedure Process(const inFooBar : TFooBar);

Am I on the right track? I don't have to get this exactly right, the other company's programmers will very likely fix up my mistakes. I just don't want them to laugh too hard when they see what I've sent them.


Use PAnsiChar instead of PChar, and integer instead of longint/smallint (since you use aligned records, there is no benefit of using a smallint field).

Define some pointer types like PFoo and PBar, which will be better written than ^TFoo e.g.

If you need to access some arrays, you could define TFooArray and TBarArray, as in the code below.

Don't forget the cdecl or stdcall calling convention for any function/procedure.

type
TFoo = record
  x, y : double;
  a : integer;
  b : PAnsiChar;
end;

TBar = record
  a : double;
  b : integer;
  c : PAnsiChar;
end;

TFooArray = array[0..maxInt div sizeof(TFoo)-1] of TFoo;
TBarArray = array[0..maxInt div sizeof(TBar)-1] of TBar;

PBar = ^TBar;
PFoo = ^TFoo;
PFooArray = ^TFooArray;
PBarArray = ^TBarArray;

TFooBar = record
  foo : PFooArray;
  foo_count : integer;
  bar : PBarArray;
  bar_count : integer;
end;

procedure Process(const inFooBar : TFooBar); cdecl; external 'ProcessGCCLibrary.so';

Will be your process read-only?

If the C part need to add some items, you'll have to provide some memory reallocation methods to the external library, mapping at least reallocmem().

Note that Delphi dynamic arrays can be mapped easily into C compatible structure, as such:

type
  TFooDynArray: array of TFoo;
  TBarDynArray: array of TBar;

procedure CallProcess(const aFoo: TFooDynArray; const aBar: TBarDynArray);
var tmp: TFooBar;
begin
  tmp.foo := pointer(aFoo);
  tmp.foo_count := length(aFoo);
  tmp.bar := pointer(aBar);
  tmp.bar_count := length(aBar);
  Process(tmp);
end;

This could make your Delphi code much more readable. Take a look at our TDynArray wrapper if you want high-level access to such a dynamic array of records, with TList-like methods.

Since the AnsiString type maps a PAnsiChar, from the binary point of view, you can even define your records as such:

type
TFoo = record
  x, y : double;
  a : integer;
  b : AnsiString;
end;

TBar = record
  a : double;
  b : integer;
  c : AnsiString;
end;

When mapped to the gcc application, it will be read as a regular *char.

Using AnsiString here you won't need to handle memory allocation from your Delphi code. With an associated dynamic array, it could make your Delphi code much easier to maintain. Note that our TDynArray wrapper will handle nested AnsiString in records as expected, even for the highest level methods (e.g. binary serialization or hashing).


To ensure that the record packing/alignment/padding is as GCC expects, add {$packrecords c} to your Pascal source. Note that this directive is specific to the Free Pascal Compiler, Delphi does not support it.


Looks pretty good. Thoughts:

  • Your arrays (declared as a pointer and count) are going to be fully manually managed - that's fine for you?

  • You should change:

    procedure Process(const inFooBar : TFooBar);
    

    to include a C-compatible calling convention. I'm not sure what FreePascal and GCC both support, but something like cdecl should work fine.

  • You should also (for safety) specify the structure alignment / packing.

  • PChar should be explicitly narrow or wide (wide is normal these days.)

0

精彩评论

暂无评论...
验证码 换一张
取 消