【问题标题】:How can I dereference a pointer in Inno Setup Pascal Script?如何取消引用 Inno Setup Pascal 脚本中的指针?
【发布时间】:2020-03-05 13:13:06
【问题描述】:

我从 Inno Setup Script 中的 DLL 文件调用一个函数,它的返回类型是 PAnsiChar。 为了获得整个字符串,我需要取消引用指针,但标准的帕斯卡语法在这里不起作用。 甚至有可能做到这一点吗?

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; 

现在我遇到了这种错误:

我不明白为什么它必须查看我的“临时”目录?我还听说这个问题可能与 Windows 10 UAC 中的组策略有关,但我不确定我应该在这里做什么来消除这个错误。

【问题讨论】:

  • 我试图将我的代码作为文本发布,但字符太多,所以我无法这样做。
  • 但是你需要有函数声明(= API 规范)。这是什么?
  • 函数 GetSQLServerInstances: PAnsiChar;
  • 它是否声明为stdcall(不确定它是否对无参数函数有影响)。 – 除此之外,我相信我的回答显示了正确的解决方案。您可以使用任何其他语言的 DLL 检索完整的字符串吗?
  • 顺便说一句,您是否在最新版本的 Inno Setup 中对此进行了测试? (因为您使用的是一些过时的)

标签: pointers inno-setup dereference pascalscript


【解决方案1】:

如果我理解正确,您的 SQLDLL 自己管理一些内存缓冲区并返回一个指向 Unicode 字符串的指针(不是 ANSI,这就是为什么您在尝试 PAnsiChar 时只得到一个字符的原因,根据your comment)。

Inno Setup 不直接支持这一点,甚至没有 PWideChar 类型。但是,我们可以自己处理。我们只需要分配一个大小合适的 Inno 字符串并手动复制数据。

这是一个如何做到这一点的工作示例。它使用GetCommandLineW 作为返回PWideChar 的示例函数,但您可以对SQLDLL 函数执行相同的操作。

  • 从外部函数获取指针并将其存储在变量中(Cardinal - 在我的示例中,我为它创建了 typedef PWideChar)。
  • 使用lstrlenW获取字符串长度。
  • 创建一个空的常规String,但使用SetLength 将其设置为正确的长度。这将保留足够的容量,以便我们可以在下一步中将实际内容写入其中。
  • 使用lstrcpyW 将指针引用的字符串复制到您的常规String 变量。
    • (如果您使用 ANSI 版本的 Inno Setup:请改用 WideCharToMultiByte,请参阅本文末尾的更新。)

诀窍是导入lstrcpyW,将目标指针声明为String,但将源指针声明为Cardinal(或我的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="$212AC6,121344,121344,Z:\Temp\testsetup.exe" /DEBUGWND=$222722 

如您所见,命令行字符串(我们得到PWideChar)被正确复制到常规字符串变量中,最后可以正常访问。


更新:如果您使用的是 ANSI 版本的 Inno Setup 而不是 Unicode,则仅此代码将不起作用。所需的更改是:您将使用WideCharToMultiByte,而不是使用lstrcpyW

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

【讨论】:

  • 好答案。假设 OP 关于 DLL 的函数声明或实现确实是错误的(我实际上也这么认为)。并且还假设 OP 使用 Inno Setup 的 Unicode 版本(我不确定,因为 OP 使用的是旧版本的 Inno Setup)。 – 处理PWideChar 也包含在我对Upgrading from Ansi to Unicode version of Inno Setup 的回答中。
  • 感谢您的提示,我在 ANSI 版本中添加了如何操作的信息。
【解决方案2】:

您不能在 Inno Setup Pascal 脚本中取消引用指针。

但是有许多黑客可以做到这一点。这些技巧非常具体,因此取决于特定的用例。


尽管在您的特定情况下,由于指向字符数组的指针在 API 中很普遍,因此 Inno Setup Pascal 脚本(类似于 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?

【讨论】:

  • 我已经做到了,但它仍然只给了我字符串的第一个字符。 imgur.com/FaeqNG3
  • 在我看来,这听起来像是您实际上获得的是指向 unicode 字符串而不是 ANSI 字符串的指针。如果你用返回类型String 声明函数会发生什么?据我所知,在外部函数的上下文中,String 被编组为PWideChar(否则在 Inno 中无法访问)。另见stackoverflow.com/a/45349898/1871033
  • @CherryDT 如何将string 编组为PWideChar?指针指向的缓冲区将存储到哪里?
  • 实际上反过来,因为它是一个返回值。如果这个函数返回一个字符串指针,据我所知,它是已经分配了一个缓冲区的库(并且希望有一些逻辑来稍后释放它......)并且 Inno 只会将它 wstrcpy 到一个相同大小的字符串。所以我想。
  • @CherryDT 就是这样。 Delphi imo 无法自动将String 编组为PWideChar,因为它必须为每次调用后台函数自动分配一个缓冲区。什么会导致内存泄漏。这些事情必须由函数实现显式完成,而不是通过编组隐式完成。
猜你喜欢
  • 1970-01-01
  • 2017-02-12
  • 1970-01-01
  • 2013-01-22
  • 2021-01-21
  • 1970-01-01
  • 1970-01-01
  • 2016-06-23
  • 1970-01-01
相关资源
最近更新 更多