我在Delphi 10.1中编写了一个DLL,它使用Devart IBDac组件来允许在安装中备份和恢复火鸟数据库(从火鸟1.5升级到3的应用程序)。使用Ansi Inno Setup 5.5.9一切都很好。由于我想避免AnsiString
和PAnsiChar
作为参数(为了更方便地从Delphi使用),我安装了Inno Setup Unicode版本5.5.9(u),并将参数从AnsiString
/PAnsiChar
更改为String
在DLL中,也在Inno Setup Script中。我也尝试了WideString
在Inno设置脚本,但这根本不起作用。PChar
无法被Inno Setup识别。
奇怪的是,脚本部分工作,参数被正确地传输到DLL,但我在与参数无关的情况下经历了GPFs:
- 在DLL中的字符串上使用trim(没有参数!)创建GPF。
- 创建DBConnection-Object也创建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 Setup (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 Setup中声明如下:
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';
你不能在DLL API中使用string
类型,至少有两个原因:
-
Inno Setup将始终假定实际参数类型为
PChar
,当您将函数参数声明为string
时。 -
string
是"smart"进行内部分配和引用计数的类型。这样的类不能通过DLL边界,因为DLL不能释放应用程序分配的内存,反之亦然,因为每个类都有自己的内存管理器。此外,类的内存布局和内部逻辑在不同版本的Delphi(用于构建Inno Setup和DLL的版本)中可能有所不同。
在Delphi中实现实参为PWideChar
(= PChar
),在Unicode Inno Setup中声明形参为(wide) string
。我不认为使用PWideChar
表示"in"有任何不便。德尔菲论证。你可以在任何可以使用string
的地方使用它。
在freeppascal编译的DLL和Delphi编译的EXE之间交换字符串(PChar)
这个答案提供了如何从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函数的参数如下:
-
NumChars
:缓冲区所需的字符数,不包括终止null字符
Buffer
:指向字符串缓冲区的指针与Windows api一样,调用该函数两次。第一次调用获取所需的字符数,第二次调用将字符串复制到缓冲区中。该函数返回缓冲区所需的字符数,不包括结束的null字符。
Inno Setup code:
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;