Delphi DLL中的Inno设置Unicode版本和字符串参数

Delphi DLL中的Inno设置Unicode版本和字符串参数,delphi,dll,unicode,parameters,inno-setup,Delphi,Dll,Unicode,Parameters,Inno Setup,我在Delphi 10.1中编写了一个DLL,它使用Devart IBDac组件允许在安装过程中备份和恢复Firebird DB(将应用程序从Firebird 1.5升级到3)。有了Ansi Inno Setup 5.5.9,一切正常。由于我想避免将AnsiString和PAnsiChar作为参数(为了从Delphi更方便地使用),我安装了Inno-Setup Unicode版本5.5.9(u),并在DLL和Inno-Setup脚本中将参数从AnsiString/PAnsiChar更改为Stri

我在Delphi 10.1中编写了一个DLL,它使用Devart IBDac组件允许在安装过程中备份和恢复Firebird DB(将应用程序从Firebird 1.5升级到3)。有了Ansi Inno Setup 5.5.9,一切正常。由于我想避免将
AnsiString
PAnsiChar
作为参数(为了从Delphi更方便地使用),我安装了Inno-Setup Unicode版本5.5.9(u),并在DLL和Inno-Setup脚本中将参数从
AnsiString
/
PAnsiChar
更改为
String
。我还在Inno设置脚本中尝试了
WideString
,但根本不起作用PChar

奇怪的是,脚本部分工作,参数正确地传输到DLL,但我在与参数无关的情况下遇到GPFs:

  • 在DLL内的字符串上使用trim(无参数!)会创建GPF
  • 创建DBConnection对象也会创建GPF
我认为参数类型是这种罪恶的根源。这是我在DLL和脚本中更改的唯一内容。带有
AnsiString
的版本仍然运行良好

谢谢分享我的想法,我在这里做错了什么

更新:请求的示例代码:

从资源运行脚本的Delphi过程在第一条语句中失败。通过第一行的
MessageBox
,我验证了所有参数都正确到达

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;
Inno设置(Unicode)中过程的定义。其他必需的DLL在第一个函数定义中声明,因此它们都存在。它与
AnsiString
/
PAnsiChar
一起工作

procedure runScript(aServer, aDatabase, aUser, aPW, aPort, DLL, script: String); 
    external 'runScript@files:itcsetupfb.dll stdcall setuponly';
此还原函数可以工作,但仅当我在函数中不使用TRIM()时:

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;
此函数在Inno设置中声明如下:

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';

您不能从Inno安装程序中使用DLL API中的
字符串
类型,至少有两个原因:

  • 当您将函数参数声明为
    string
    时,Inno Setup将始终假定实际参数类型为
    PChar

  • 字符串
    是“智能”类型,它进行内部分配和引用计数。这样的类不能通过DLL边界,因为DLL不能释放应用程序分配的内存,反之亦然,因为每个类都有自己的内存管理器。此外,该类的内存布局和内部逻辑在不同版本的Delphi(用于构建Inno设置和DLL的版本)之间可能会有所不同

在Delphi中实现参数为
PWideChar
(=
PChar
),并在Unicode Inno设置中将参数声明为(宽)
string
。我认为在Delphi中为“in”参数使用
PWideChar
不会带来任何不便。您可以在任何可以使用
字符串的地方使用它

有关类似的讨论,请参见

此答案提供了如何从DLL获取字符串的完整示例。我使用的是FPC而不是Delphi,但应该非常接近

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.
DLL函数的参数如下所示:

  • Buffer
    :指向字符串缓冲区的指针
  • NumChars
    :缓冲区所需的字符数,不包括终止的空字符
与Windows API一样,调用该函数两次。第一个调用获取所需的字符数,第二个调用将字符串复制到缓冲区中。函数返回缓冲区所需的字符数,不包括终止的空字符

Inno设置代码:

function GetSampleString(Buffer: string; NumChars: DWORD): DWORD;
  external 'GetSampleString@Sample.dll 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;