You can not safely pass complex objects like ref-counted strings into language-agnostic no-type-safety DLLs without special measures taken in advance (providing you know good enough low-level Delphi compiler implementation to design such scaffolding)
You also can not pass language-specific objects like open arrays
into DLL for the same reason.
So what you have to do? You have to do like Windows does: pass a single buffer with all the strings and parse it in DLL. Details depend upon what kinds of strings you are to send.
- Method: Multiple ASCIIZ strings.
- Restriction: no empty strings allowed, strings can not contain #0 char
- Parameter type:
PAnsiChar
or PWideChar
- Data sample: 'abcde'#0'fgh'#0'ijklmn'#0#0 - strings are terminated by #0; empty string (two consequtive #0) means end of data packet
draft DLL impl:
{$T+} {$POINTERMATH ON}
type PMyStrings = PAnsiChar;
procedure GenKey( Details: PMyStrings );
var a: AnsiString;
w: string; // would be UTF-16 String in Delphi 2009 and above
begin
repeat
a := Details; // conversion next ASCIIZ string to Delphi string
if a = '' then break; // empty string - end of data
Inc( Details, Length(a) + 1 ); // shifting pointer to next string in buffer
w := a; // conversion fixed 8-bits string to Delphi-default string;
{ process each string `w` }
until false;
end;
draft EXE impl:
type PMyStrings = PAnsiChar;
var isl: iJclStringList; a: AnsiString;
isl := JclStringList();
isl.Add( 'abcde' );
isl.Add( 'fgh' );
isl.Add( 'ijklmn' );
isl.Add( '' ); // empty string - end of data marker
a := AnsiString ( isl.Join( #0 ) );
GenKey( PMyStrings( a ) );
draft EXE impl 2:
type PMyStrings = PAnsiChar;
var a: AnsiString; s: string;
s := '';
s := s + 'abcde' + #0;
s := s + 'fgh' + #0;
s := s + 'ijklmn' + #0;
a := AnsiString ( s );
GenKey( PMyStrings( a ) );
or
- Method: counter-prefixed buffer
- Restriction: almost none; chosen size of prefixes (byte, smallint, longint) might limit total number of strings and max length of each one. You also would have to decide if you would use AnsiChar or WideChar to store data - type safety would be solely your responsibility.
- Parameter type: Pointer - you would type-cast it to different types while parsing the buffer
- Data sample: {32-bits integer, total number of strings} 3, {32-bits integer, 1st string length} 5, 'a', 'b', 'c', 'd', 'e', {32-bits integer, 2nd string length} 3, 'f','g','h', {32-bits integer, 3rd string length} 6, 'i','j','k','l','m','n'
draft DLL impl:
{$T+} {$POINTERMATH ON}
type PrefCount = UInt32; PPrefCount = ^PrefCount;
PrefStrLen = UInt32; PPrefStrLen = ^PrefStrLen;
PStrData = PAnsiChar;
procedure GenKey( Details: Pointer );
var pc: PPrefCount; pl: PPrefStrLen; pd: PStrData;
Count: PrefCount; Len: PrefStrLen;
a: AnsiString;
w: string; // would be UTF-16 String in Delphi 2009 and above
begin
pc := Details;
Count := pc^;
Inc(pc); Details := pc; // shifting the pointer past the counter cell
while Count > 0 do begin
pl := Details;
Len := pl^;
Inc(pl); Details := pl; // shifting the pointer past the n-th string length cell
SetLength(a, Len);
if Len > 0 then begin
pd := Details;
Assert( sizeof( a[1] ) = sizeof( pd^ ) );
Move( pd^, a[1], Len * sizeof( a[1] ) );
Inc( pd, Len );
Details := pd;
end;
w := a; // conversion fixed 8-bits string to Delphi-default string;
{ process each string `w` }
Dec( Count );
end;
end;
draft EXE impl:
{$T+} {$POINTERMATH ON}
type PrefCount = UInt32; PPrefCount = ^PrefCount;
PrefStrLen = UInt32; PPrefStrLen = ^PrefStrLen;
PStrData = PAnsiChar;
var isl: iJclStringList;
buffer: TBytes;
s: string; a: AnsiString;
L: integer;
pc: PPrefCount; pl: PPrefStrLen; pd: PStrData;
data: Pointer;
isl := JclStringList();
isl.Add( 'abcde' );
isl.Add( 'fgh' );
isl.Add( 'ijklmn' );
Assert( sizeof( a[1] ) = sizeof( pd^ ) );
L := 0;
for s in isl do
Inc( L, Length(s) );
L := L * sizeof( pd^ );
Inc( L, isl.Count * sizeof( PrefStrLen ) );
Inc( L, sizeof( PrefCount ) );
SetLength( buffer, L );
data := @buff[ Low(buff) ];
pc := data;
pc^ := isl.Count;
Inc(pc);
data := pc;
for s in isl do begin
pl := data;
pl^ := Length(s);
Inc(pl);
data := pl;
if s > '' then begin
a := AnsiString(s);
pd := data;
Move( a[1], pd^, pl^ * sizeof( pd^ ) );
Inc( pd, pl^ );
data := pd;
end;
end;
Dec( PByte( data ) ); // should point to next byte past the buffer
Assert( data = @buff[ High(buff) ] ); // if Length was correctly calculated and adjusted
data := @buff[ Low(buff) ];
GenKey( data );
or
- Method: ASCIIZ string with secondary separator.
- Restriction: strings can contain neither #0 char nor some another pre-defined char used as internal separator
- Parameter type:
PAnsiChar
or PWideChar
- Data sample: 'abcde'#1'fgh'#1'ijklmn'#0 - strings are separated by #1 here (and this character is banned from being part of legitimate data). #0 marks the end of the mega-string (thus of all strings).
draft DLL impl:
type PMyStrings = PAnsiChar;
const AuxSeparator = #1;
procedure GenKey( Details: PMyStrings );
var isl: iJclStringList;
a: AnsiString;
w: string; // would be UTF-16 String in Delphi 2009 and above
begin
a := Details; // conversion ASCIIZ string to Delphi string
w := a; // conversion fixed 8-bits string to Delphi-default string;
isl := JclStringList();
isl.Split( w, AuxSeparator ); // parsing mega-string into separate strings
for w in isl do begin
{ process each string `w` }
end;
end;
draft EXE impl:
type PMyStrings = PAnsiChar;
const AuxSeparator = #1;
var isl: iJclStringList; a: AnsiString;
isl := JclStringList();
isl.Add( 'abcde' );
isl.Add( 'fgh' );
isl.Add( 'ijklmn' );
a := AnsiString ( isl.Join( AuxSeparator ) );
GenKey( PMyStrings( a ) );
draft EXE impl 2:
type PMyStrings = PAnsiChar;
const AuxSeparator = #1;
var a: AnsiString; s: string;
s := '';
s := s + 'abcde' + AuxSeparator;
s := s + 'fgh' + AuxSeparator;
s := s + 'ijklmn'; // no internal separator for last string
a := AnsiString ( s );
GenKey( PMyStrings( a ) );
You can also combine those methods or use several parameters in DLL, for example you can send array of pointers to ASCIIZ strings
{$T+} {$POINTERMATH ON}
type PMyStrings = ^PAnsiChar;
procedure GenKey(Count: integer; Details: PMyStrings);
var a: AnsiString; p: PAnsiChar;
w: string;
begin
while Count > 0 do begin
p := Details^;
a := p;
Inc( Details );
Dec( Count );
w := a;
{ process string `w` }
end;
end;
and then EXE
type PMy1String = PAnsiChar;
var data: packed array of PMy1String;
SetLength( data, 3 );
data[0] := PMy1String( 'abcde' );
data[1] := PMy1String( 'fgh' );
data[2] := PMy1String( 'ijklmn' );
GenKey( Length(data), @data[0] );
Or anything that would suit you best. Just be very cautious about low-level data representation. For example difference between AnsiChar and WideChar in different languages - now it would be your responsibility to track it, not the compiler's one.