如何取消引用 Inno Setup Pascal Script 中的指针?
How can I dereference a pointer in Inno Setup Pascal Script?
我在 Inno Setup 脚本中从 DLL 文件调用一个函数,它的 return 类型是 PAnsiChar
。
为了获得整个字符串,我需要取消引用指针,但标准的 Pascal 语法在这里不起作用。
甚至可以这样做吗?
function SQLDLL : PAnsiChar;
external 'GetSQLServerInstances@files:IsStartServer.dll stdcall setuponly';
function NextButtonClick(CurPage: Integer): Boolean;
var
hWnd: Integer;
Str : AnsiString;
begin
if CurPage = wpWelcome then begin
hWnd := StrToInt(ExpandConstant('{wizardhwnd}'));
MessageBox(hWnd, 'Hello from Windows API function', 'MessageBoxA', MB_OK or MB_ICONINFORMATION);
MyDllFuncSetup(hWnd, 'Hello from custom DLL function', 'MyDllFunc', MB_OK or MB_ICONINFORMATION);
Str := SQLDLL;
try
{ if this DLL does not exist (it shouldn't), an exception will be raised }
DelayLoadedFunc(hWnd, 'Hello from delay loaded function', 'DllFunc', MB_OK or MB_ICONINFORMATION);
except
{ handle missing dll here }
end;
end;
Result := True;
end;
我只有 DLL 文件。原文为Delphi.
我更新到最新版本的 Inno Setup 6.0.3 并在我家 Windows 10 Pro 机器上测试了这段代码:
[Setup]
AppName=My Program
AppVersion=1.5
WizardStyle=modern
DefaultDirName={autopf}\My Program
DisableProgramGroupPage=yes
DisableWelcomePage=no
UninstallDisplayIcon={app}\MyProg.exe
OutputDir=userdocs:Inno Setup Examples Output
[Files]
Source: "MyProg.exe"; DestDir: "{app}"
Source: "MyProg.chm"; DestDir: "{app}"
Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme
Source: "IsStartServer.dll"; Flags: dontcopy
[Code]
function SQLDLL : PAnsiChar;
external 'GetSQLServerInstances@files:IsStartServer.dll stdcall';
function NextButtonClick(CurPage: Integer): Boolean;
var
Str : PAnsiChar;
begin
Str := SQLDLL;
Result := True;
end;
现在我遇到了这种错误:
我不明白为什么它必须查看我的 'temp' 目录?我还听说这个问题可能以某种方式与 Windows 10 UAC 中的组策略有关,但我不确定我应该怎么做才能摆脱这个错误。
您不能在 Inno Setup Pascal 脚本中取消引用指针。
但是有许多技巧可以做到这一点。这些技巧非常具体,因此取决于特定的用例。
虽然在您的特定情况下,由于指向字符数组的指针在 API 中很普遍,Inno Setup Pascal Script(类似于 Delphi)可以将指向字符数组的指针分配给字符串。
因此,您应该能够简单地将 PChar
分配给 AnsiString
:
function ReturnsPAnsiChar: PAnsiChar; extern '...';
var
Str: AnsiString;
begin
Str := ReturnsPAnsiChar;
end;
见How to return a string from a DLL to Inno Setup?
如果我理解正确的话,你的 SQLDLL
自己管理一些内存缓冲区和 returns 一个指向 Unicode 字符串的指针(不是 ANSI,这就是为什么你根据 ).
,当您尝试 PAnsiChar
时只得到一个字符
Inno Setup 不直接支持它,甚至没有 PWideChar
类型。但是,我们可以自己处理。我们只需要分配一个大小合适的 Inno 字符串并手动复制数据。
这是一个如何执行此操作的工作示例。它使用 GetCommandLineW
作为 returns 和 PWideChar
的示例函数,但您可以对 SQLDLL
函数执行相同的操作。
- 从外部函数获取指针并将其存储在变量中(
Cardinal
- 在我的示例中,我为其创建了一个 typedef PWideChar
)。
- 使用
lstrlenW
获取字符串长度。
- 创建一个空的正则
String
,但使用 SetLength
将其设置为正确的长度。这将预留足够的容量,以便我们在下一步写入实际内容。
- 使用
lstrcpyW
将指针引用的字符串复制到您的常规 String
变量。
- (如果您使用 ANSI 版本的 Inno Setup:请改用
WideCharToMultiByte
,请参阅本文末尾的更新 post。)
诀窍是以目标指针声明为 String
但源指针声明为 Cardinal
的方式导入 lstrcpyW
(或我的 typedef PWideChar
此处)。
type
PWideChar = Cardinal; { Inno doesn't have a pointer type, so we use a Cardinal instead }
{ Example of a function that returns a PWideChar }
function GetCommandLineW(): PWideChar;
external 'GetCommandLineW@kernel32.dll stdcall';
{ This function allows us to get us the length of a string from a PWideChar }
function lstrlenW(lpString: PWideChar): Cardinal;
external 'lstrlenW@kernel32.dll stdcall';
{ This function copies a string - we declare it in such a way that we can pass a pointer
to an Inno string as destination
This works because Inno will actually pass a PWideChar that points to the start of the
string contents in memory, and internally the string is still null-terminated
We just have to make sure that the string already has the right size beforehand! }
function lstrcpyW_ToInnoString(lpStringDest: String; lpStringSrc: PWideChar): Integer;
external 'lstrcpyW@kernel32.dll stdcall';
function InitializeSetup(): Boolean;
var
returnedPointer: PWideChar; { This is what we get from the external function }
stringLength: Cardinal; { Length of the string we got }
innoString: String; { This is where we'll copy the string into }
begin
{ Let's get the PWideChar from the external function }
returnedPointer := GetCommandLineW();
{ The pointer is actually just a renamed Cardinal at this point: }
Log('String pointer = ' + IntToStr(returnedPointer));
{ Now we have to manually allocate a new Inno string with the right length and
copy the data into it }
{ Start by getting the string length }
stringLength := lstrlenW(returnedPointer);
Log('String length = ' + IntToStr(stringLength));
{ Create a string with the right size }
innoString := '';
SetLength(innoString, stringLength);
{ This check is necessary because an empty Inno string would translate to a NULL pointer
and not a pointer to an empty string, and lstrcpyW cannot handle that. }
if StringLength > 0 then begin
{ Copy string contents from the external buffer to the Inno string }
lstrcpyW_ToInnoString(innoString, returnedPointer);
end;
{ Now we have the value stored in a proper string variable! }
Log('String value = ' + innoString);
Result := False;
end;
如果将它放入安装程序并运行它,您会看到如下输出:
[15:10:55,551] String pointer = 9057226
[15:10:55,560] String length = 106
[15:10:55,574] String value = "R:\Temp\is-9EJQ6.tmp\testsetup.tmp" /SL5="2AC6,121344,121344,Z:\Temp\testsetup.exe" /DEBUGWND=2722
可以看到,命令行字符串(我们得到的是一个PWideChar
)被正确的复制到了一个普通的字符串变量中,最后可以正常访问了。
更新: 如果您使用的是 ANSI 版本的 Inno Setup 而不是 Unicode,则单独使用此代码是行不通的。需要做的改变是:不要使用 lstrcpyW
,而是使用 WideCharToMultiByte
:
function WideCharToMultiByte_ToInnoString(CodePage: Cardinal; dwFlags: Cardinal; lpWideCharStr: PWideChar; cchWideChar: Cardinal; lpMultiByteStr: String; cbMultiByte: Cardinal; lpDefaultChar: Cardinal; lpUsedDefaultChar: Cardinal): Integer;
external 'WideCharToMultiByte@kernel32.dll stdcall';
{ Later on: Instead of calling lstrcpyW_ToInnoString, use this:
Note: The first parameter 0 stands for CP_ACP (current ANSI code page), and the
string lengths are increased by 1 to include the null terminator }
WideCharToMultiByte_ToInnoString(0, 0, returnedPointer, stringLength + 1, innoString, stringLength + 1, 0, 0);
我在 Inno Setup 脚本中从 DLL 文件调用一个函数,它的 return 类型是 PAnsiChar
。
为了获得整个字符串,我需要取消引用指针,但标准的 Pascal 语法在这里不起作用。
甚至可以这样做吗?
function SQLDLL : PAnsiChar;
external 'GetSQLServerInstances@files:IsStartServer.dll stdcall setuponly';
function NextButtonClick(CurPage: Integer): Boolean;
var
hWnd: Integer;
Str : AnsiString;
begin
if CurPage = wpWelcome then begin
hWnd := StrToInt(ExpandConstant('{wizardhwnd}'));
MessageBox(hWnd, 'Hello from Windows API function', 'MessageBoxA', MB_OK or MB_ICONINFORMATION);
MyDllFuncSetup(hWnd, 'Hello from custom DLL function', 'MyDllFunc', MB_OK or MB_ICONINFORMATION);
Str := SQLDLL;
try
{ if this DLL does not exist (it shouldn't), an exception will be raised }
DelayLoadedFunc(hWnd, 'Hello from delay loaded function', 'DllFunc', MB_OK or MB_ICONINFORMATION);
except
{ handle missing dll here }
end;
end;
Result := True;
end;
我只有 DLL 文件。原文为Delphi.
我更新到最新版本的 Inno Setup 6.0.3 并在我家 Windows 10 Pro 机器上测试了这段代码:
[Setup]
AppName=My Program
AppVersion=1.5
WizardStyle=modern
DefaultDirName={autopf}\My Program
DisableProgramGroupPage=yes
DisableWelcomePage=no
UninstallDisplayIcon={app}\MyProg.exe
OutputDir=userdocs:Inno Setup Examples Output
[Files]
Source: "MyProg.exe"; DestDir: "{app}"
Source: "MyProg.chm"; DestDir: "{app}"
Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme
Source: "IsStartServer.dll"; Flags: dontcopy
[Code]
function SQLDLL : PAnsiChar;
external 'GetSQLServerInstances@files:IsStartServer.dll stdcall';
function NextButtonClick(CurPage: Integer): Boolean;
var
Str : PAnsiChar;
begin
Str := SQLDLL;
Result := True;
end;
现在我遇到了这种错误:
我不明白为什么它必须查看我的 'temp' 目录?我还听说这个问题可能以某种方式与 Windows 10 UAC 中的组策略有关,但我不确定我应该怎么做才能摆脱这个错误。
您不能在 Inno Setup Pascal 脚本中取消引用指针。
但是有许多技巧可以做到这一点。这些技巧非常具体,因此取决于特定的用例。
虽然在您的特定情况下,由于指向字符数组的指针在 API 中很普遍,Inno Setup Pascal Script(类似于 Delphi)可以将指向字符数组的指针分配给字符串。
因此,您应该能够简单地将 PChar
分配给 AnsiString
:
function ReturnsPAnsiChar: PAnsiChar; extern '...';
var
Str: AnsiString;
begin
Str := ReturnsPAnsiChar;
end;
见How to return a string from a DLL to Inno Setup?
如果我理解正确的话,你的 SQLDLL
自己管理一些内存缓冲区和 returns 一个指向 Unicode 字符串的指针(不是 ANSI,这就是为什么你根据
PAnsiChar
时只得到一个字符
Inno Setup 不直接支持它,甚至没有 PWideChar
类型。但是,我们可以自己处理。我们只需要分配一个大小合适的 Inno 字符串并手动复制数据。
这是一个如何执行此操作的工作示例。它使用 GetCommandLineW
作为 returns 和 PWideChar
的示例函数,但您可以对 SQLDLL
函数执行相同的操作。
- 从外部函数获取指针并将其存储在变量中(
Cardinal
- 在我的示例中,我为其创建了一个 typedefPWideChar
)。 - 使用
lstrlenW
获取字符串长度。 - 创建一个空的正则
String
,但使用SetLength
将其设置为正确的长度。这将预留足够的容量,以便我们在下一步写入实际内容。 - 使用
lstrcpyW
将指针引用的字符串复制到您的常规String
变量。- (如果您使用 ANSI 版本的 Inno Setup:请改用
WideCharToMultiByte
,请参阅本文末尾的更新 post。)
- (如果您使用 ANSI 版本的 Inno Setup:请改用
诀窍是以目标指针声明为 String
但源指针声明为 Cardinal
的方式导入 lstrcpyW
(或我的 typedef PWideChar
此处)。
type
PWideChar = Cardinal; { Inno doesn't have a pointer type, so we use a Cardinal instead }
{ Example of a function that returns a PWideChar }
function GetCommandLineW(): PWideChar;
external 'GetCommandLineW@kernel32.dll stdcall';
{ This function allows us to get us the length of a string from a PWideChar }
function lstrlenW(lpString: PWideChar): Cardinal;
external 'lstrlenW@kernel32.dll stdcall';
{ This function copies a string - we declare it in such a way that we can pass a pointer
to an Inno string as destination
This works because Inno will actually pass a PWideChar that points to the start of the
string contents in memory, and internally the string is still null-terminated
We just have to make sure that the string already has the right size beforehand! }
function lstrcpyW_ToInnoString(lpStringDest: String; lpStringSrc: PWideChar): Integer;
external 'lstrcpyW@kernel32.dll stdcall';
function InitializeSetup(): Boolean;
var
returnedPointer: PWideChar; { This is what we get from the external function }
stringLength: Cardinal; { Length of the string we got }
innoString: String; { This is where we'll copy the string into }
begin
{ Let's get the PWideChar from the external function }
returnedPointer := GetCommandLineW();
{ The pointer is actually just a renamed Cardinal at this point: }
Log('String pointer = ' + IntToStr(returnedPointer));
{ Now we have to manually allocate a new Inno string with the right length and
copy the data into it }
{ Start by getting the string length }
stringLength := lstrlenW(returnedPointer);
Log('String length = ' + IntToStr(stringLength));
{ Create a string with the right size }
innoString := '';
SetLength(innoString, stringLength);
{ This check is necessary because an empty Inno string would translate to a NULL pointer
and not a pointer to an empty string, and lstrcpyW cannot handle that. }
if StringLength > 0 then begin
{ Copy string contents from the external buffer to the Inno string }
lstrcpyW_ToInnoString(innoString, returnedPointer);
end;
{ Now we have the value stored in a proper string variable! }
Log('String value = ' + innoString);
Result := False;
end;
如果将它放入安装程序并运行它,您会看到如下输出:
[15:10:55,551] String pointer = 9057226
[15:10:55,560] String length = 106
[15:10:55,574] String value = "R:\Temp\is-9EJQ6.tmp\testsetup.tmp" /SL5="2AC6,121344,121344,Z:\Temp\testsetup.exe" /DEBUGWND=2722
可以看到,命令行字符串(我们得到的是一个PWideChar
)被正确的复制到了一个普通的字符串变量中,最后可以正常访问了。
更新: 如果您使用的是 ANSI 版本的 Inno Setup 而不是 Unicode,则单独使用此代码是行不通的。需要做的改变是:不要使用 lstrcpyW
,而是使用 WideCharToMultiByte
:
function WideCharToMultiByte_ToInnoString(CodePage: Cardinal; dwFlags: Cardinal; lpWideCharStr: PWideChar; cchWideChar: Cardinal; lpMultiByteStr: String; cbMultiByte: Cardinal; lpDefaultChar: Cardinal; lpUsedDefaultChar: Cardinal): Integer;
external 'WideCharToMultiByte@kernel32.dll stdcall';
{ Later on: Instead of calling lstrcpyW_ToInnoString, use this:
Note: The first parameter 0 stands for CP_ACP (current ANSI code page), and the
string lengths are increased by 1 to include the null terminator }
WideCharToMultiByte_ToInnoString(0, 0, returnedPointer, stringLength + 1, innoString, stringLength + 1, 0, 0);