4
votes

I wrote a DLL in Delphi 10.1 that uses Devart IBDac components to allow backup and restore of a Firebird DB within the installation (upgrade of an application from Firebird 1.5 to 3). With Ansi Inno Setup 5.5.9 everything worked fine. As I wanted to avoid AnsiString and PAnsiChar as parameters (for more convenient use from Delphi) I installed Inno Setup Unicode version 5.5.9(u) and changed the parameters from AnsiString/PAnsiChar to String in the DLL and also in the Inno Setup Script. I also tried WideString in the Inno Setup script but this didn't work at all. PChar is not recognized by Inno Setup.

The strange thing is that the script partly works, the parameters are correctly transferred to the DLL but I experience GPFs in situations that have nothing to do with the parameters:

  • Using trim on a string within the DLL (no parameter!) creates a GPF.
  • Creating a DBConnection-Object creates a GPF too.

I think the parameter-types are root of this evil. That is the only thing I changed in DLL and script. The versions with AnsiString still work well.

Thanks for sharing any ideas about, what I am doing wrong here.

Update: Example Code as requested:

Delphi procedure that runs a script from a resource, fails in the very first statement. With MessageBox in the first line I verified that all parameters arrive correctly.

procedure runScript(aServer, aDatabase, aUser, aPW, aPort, DLL, script: String); stdcall;
var
  ResStream: TResourceStream;
  DB: TIBCConnection;
  SC: TIBCScript;
  { Muss Ansistring sein wegen Resourcestream }
  s: ansistring;
begin
  try
    DB := TIBCConnection.create(nil);
    SC := TIBCScript.create(nil);
    SC.Connection := DB;
    try
      DB.clientlibrary := DLL;
      DB.Database := aDatabase;
      DB.Server := aServer;
      DB.Username := aUser;
      DB.Password := aPW;
      DB.Port := aPort;
      DB.connected := true;
      ResStream := TResourceStream.create(hInstance, script, RT_RCDATA);
      setlength(s, ResStream.size);
      ResStream.ReadBuffer(s[1], ResStream.size);
      SC.SQL.Text := String(s);
      SC.Execute;
      DB.close;
    finally
      SC.free;
      DB.free;
    end;
  except
    on e: Exception do
    begin
      MessageBox(0, PChar(e.message), PChar('Setup - Fehler in "' + script + '"'), mb_ok);
      raise;
    end;
  end;
end;

Definition of the procedure in Inno Setup (Unicode). Other required DLLs are declared in the first function definition so they are all present. It works with AnsiString/PAnsiChar!

procedure runScript(aServer, aDatabase, aUser, aPW, aPort, DLL, script: String); 
    external 'runScript@files:itcsetupfb.dll stdcall setuponly';

This restore function works but only if I do not use TRIM() within the function:

procedure restoreDB(destServer, destDatabase, destSysdbaPW, aBackupfile, aPort, DLL: String; PBRange: integer;
  PbHandle, LblHandle, WizHandle: THandle); stdcall;
var
  IBCRestoreService1: TIBCRestoreService;
  cnt: integer;
  s: String;
  Msg: TMsg;
begin
  IBCRestoreService1 := TIBCRestoreService.create(nil);
  SendMessage(PbHandle, PBM_SETRANGE, 0, PBRange shl 16);
  cnt := 0;
  try
    try
      with IBCRestoreService1 do
      begin
        clientlibrary := DLL;
        Database.Text := destDatabase;
        Server := destServer;
        Username := 'sysdba';
        Password := destSysdbaPW;
        Port := aPort;
        Options := [roFixFssMetadata, roNoValidityCheck, roFixFssData, roOneRelationAtATime, roDeactivateIndexes];
        FixFssCharset := 'ISO8859_1';
        BackupFile.Text := aBackupfile;
        PageSize := 16384;
        verbose := true;
        Attach;
        try
          ServiceStart;
          while IsServiceRunning do
          begin
            inc(cnt);
            if cnt > PBRange then
              cnt := 0;
            s := GetNextLine + ' ';
            { this was formerly s:=trim(strinGreplace(s,'gbak:','', }
            { [rfReplaceall,rfIgnoreCase])). I Identified the trim to be the  }
            { cause of the GPF. Very strange. }
            delete(s, 1, 5);
            while (length(s) > 0) and (s[1] = #32) do
              delete(s, 1, 1);
            { if we have no message file (local fb15) }
            if pos('format message', s) > 0 then
              s := 'Arbeite ...';
            SendMessage(LblHandle, $0C, 0, longint(s));
            SendMessage(PbHandle, PBM_SETPOS, cnt, 0);
            while PeekMessage(Msg, WizHandle, 0, 0, PM_REMOVE) do
            begin
              TranslateMessage(Msg);
              DispatchMessage(Msg);
            end;
            UpdateWindow(PbHandle);
          end;
        finally
          detach;
        end;
      end;
    finally
      IBCRestoreService1.free;
    end;
  except
    on e: Exception do
    begin
      MessageBox(0,
        PChar('Die Wiederherstellung der EMILpro Datenbank ist leider fehlgeschlagen. Folgender Fehler trat auf: ' +
        #13#10#10 + e.message), 'Setup - Fehler bei Wiederherstellung', mb_ok);
      raise;
    end;
  end;
end;

This function is declared in Inno Setup like this:

procedure restoreDB(destServer, destDatabase, destSysdbaPW, aBackupfile, aPort, DLL: String; 
         PBRange: integer; PbHandle,LblHandle,WizHandle: THandle); 
         external 'restoreDB@files:itcsetupfb.dll,fbclient.dll,gds32.dll,icudt52.dll,icudt52l.dat,icuin52.dll,icuuc52.dll,fbintl.dll,firebird.conf,firebird.msg,ib_util.dll,engine12.dll,msvcp100.dll,msvcr100.dll,fbintl.conf stdcall setuponly';
2

2 Answers

3
votes

You cannot use the string type in DLL API from Inno Setup for at least two reasons:

  • Inno Setup will always assume that the actual parameter type is PChar, when you declare the function parameter as string.

  • The string is "smart" type that does its internal allocation and reference counting. Such class cannot pass a DLL boundary, as the DLL cannot deallocate memory allocated by the application and vice versa, as each has its own memory manager. Moreover memory layout and internal logic of the class can vary among versions of Delphi (the version used to build Inno Setup and the DLL).

Implement the argument as PWideChar (= PChar) in Delphi and declare the parameter as (wide) string in Unicode Inno Setup. I do not see any inconvenience of using PWideChar for "in" argument in Delphi. You can use it wherever you can use the string.

For similar discussion, see
Exchanging strings (PChar) between a Freepascal compiled DLL and a Delphi compiled EXE

1
votes

This answer provides a full example of how to get a string from a DLL. I used FPC rather than Delphi, but it should be pretty close to the same.

Source code for DLL:

{$MODE OBJFPC}
{$H+}

library Sample;

uses
  SysUtils;

function GetSampleString(Buffer: PWideChar; NumChars: DWORD): DWORD; stdcall;
var
  OutStr: UnicodeString;
begin
  OutStr := 'sample output string';
  if Assigned(Buffer) and (NumChars >= Length(OutStr)) then
    StrPCopy(Buffer, OutStr);
  result := Length(OutStr);
end;

exports
  GetSampleString;

end.

The DLL function's parameters are as follows:

  • Buffer: Pointer to the string buffer
  • NumChars: Number of characters required for the buffer, not including the terminating null character

As with Windows APIs, call the function twice. The first call gets the number of characters required, and the second call copies the string into the buffer. The function returns the number of characters required for the buffer, not including the terminating null character.

Inno Setup code:

function GetSampleString(Buffer: string; NumChars: DWORD): DWORD;
  external '[email protected] stdcall';

function SampleString(): string;
var
  NumChars: DWORD;
  OutStr: string;
begin
  result := '';
  NumChars := GetSampleString('', 0);  // First call: Get # chars needed
  SetLength(OutStr, NumChars);         // Allocate string
  if GetSampleString(OutStr, NumChars) > 0 then
    result := OutStr;
end;