1
votes

I have a dll I have written in delphi. It requires a array of strings being passed to it. If I call the dll from another delphi app for testing it works.

I thought I had it working in inno but have changed it round so as it is called from a wizard page after instillation of files.

I get no errors but the dll is not entered as I have a message box within the dll to say it is being run.

below is the code can someone please enlighten me on why my dll does not run.

[files]
......
Source: "mydll.dll"; Flags: dontcopy; [code]
procedure GenKey(Details: array of string);
external 'GenKey@files:mydll.dll stdcall setuponly';
.....

function RegistrationComplete(Page: TWizardPage): boolean;
var
  Details: array[0..15] of String;
begin
....Load the details array of strings.....

  GenKey(Details);
  Result := True;
end;


procedure InitializeWizard();
begin
  Page := CreateCustomPage( wpInstalling, ExpandConstant('{cm:Registration_Caption}'), ExpandConstant('{cm:Registration_Description}') );
   with Page do
  begin
    OnNextButtonClick := @RegistrationComplete;
  end;

end;

Ok so from some research and searching I have come up with this so far. But I still need clarification. To get it working. I understand I need to use a PChar as a pointer to a buffer which I are declaring as a string. Using delphi as a test platform this all works but it appears that the string is getting lost when the pointer value is passed to the dll.

So when I call the dll from inno I get this error message "Internal exception EEDFADE"

Inno code as follows

var
  Buff: string;

procedure GenKey(Buff: PChar);
external 'GenKey@files:mydll.dll stdcall';

function PadZero(src: string; Width: Integer): String;
var
  I : Integer;
  Temp: string;
begin
  Temp := '';
  for i := (Length(Src)+1) to width do
    Temp := Temp + '0';
  if Length(Src) > width then Src := Copy( Src, 1, width);
  Result := Temp + Src;
end;


{ CustomForm_NextkButtonClick }

function RegistrationComplete(Page: TWizardPage): boolean;
var 
  Details: TStringList;
  BuffLen: integer;
begin
  Details := TStringList.Create;
  ....
  Load string list
  ....

  Buff := Details.Text;

  BuffLen := Length(Buff) + 1;
  Buff := PadZero(InttoStr(BuffLen),4) + Buff;

  GenKey(PChar(Buff));
  Result := True;
end;

DLL routine developed in delphi XE6

procedure GenKey (Buff: PChar);
var
  I: Integer;
  Details : TStringList;
  S: string;
  BuffLen: integer;
begin
  S := '';
  for I := 0 to 3 do
  begin
    S := S + Buff^;
    inc(Buff)
  end;

  BuffLen := strtoint(S);

  Details := TStringList.Create;
  S := '';
  for I := 1 to BuffLen do
  begin
    S := S + (Buff^);
    inc(Buff);
  end;
  Details.Text := S;
  showmessage(S);

  Details.Free;
end;
3

3 Answers

2
votes

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.

1
votes

In this page of InnoSetup Help (item Support Classes Reference) you will see that PascalScript (the scripting language InnoSetup uses) supports TStrings class. You may try it as a replacement to array of string, that will certainly not work (not a type supported by DLLs).

The fact the class in PascalScript has the same name of another Delphi class does not mean it will work properly, it will depend on how TStrings is implemented in InnoSetup, but I think it is worth a try.

If it didn't work directly, then you can do as The Arioch suggested, but using this classes as helpers:

  1. Change the parameter type in the DLL function to receive a String
  2. Create a TStringList instance in PascalScript and load it with all the strings you need
  3. Pass the value of its Text property to the DLL
  4. Inside your function in the DLL, assign the parameter to the Text property of a TStringList instance

I guess this may do the trick!

0
votes

Ok so I have solved it.

Note this works with dll developed in Delphi XE6 RAD studio and inno setup v 5.5.6(u).

SO I changed to the unicode version.

Inno setup was changed to

[Files]
Source: "MyDll.dll"; DestDir: "{tmp}"; Flags: dontcopy  

[Code]
procedure MyProc(Buff: PAnsiChar);
external 'MyProc@files:MyDll.dll stdcall delayload';

function WizardPage(Page: TWizardPage): boolean;
var 
  BuffLen: integer;
  Details: TStringList;
  Buff: AnsiString;
begin
  Details := TStringList.Create;
  ...
  Load Details
  ...

  Buff := Details.Text;

  BuffLen := Length(Buff) + 1;
  Buff := PadZero(InttoStr(BuffLen),4) + Buff;

  MyProc(PAnsiChar(Buff));
  Result := False;
end;

Dll

changed to have code in pass file rather than dll.

interface
  procedure GenKey (Buff: PAnsiChar) stdcall;

implimentation

procedure GenKey (Buff: PAnsiChar);
var
  Bios, HD, Code, CodeDisp : string;
  I: Integer;
  S: AnsiString;
  Details : TStringList;
  BuffLen: integer;
begin
  S := '';

  for I := 0 to 3 do
  begin
    S := S + Buff^;
    inc(Buff)
  end;


  BuffLen := strtoint(S);

  Details := TStringList.Create;
  S := '';
  for I := 1 to BuffLen do
  begin
    S := S + (Buff^);
    inc(Buff);
  end;
  Details.Text := S;
  showmessage(S);

  ...
  Deal with details
  ...
  Details.Free;
end;

For referance to where I got the answers