Как разыменовать указатель в Inno Setup Pascal Script? - PullRequest
0 голосов
/ 05 марта 2020

Я вызываю функцию из DLL-файла в Inno Setup Script, и ее тип возврата - 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; 

и теперь у меня такая ошибка: enter image description here

Я не понимаю, почему она должна искать в моем каталоге 'temp'? Я также слышал, что эта проблема может быть как-то связана с групповыми политиками в Windows 10 UA C, но я не совсем уверен, что мне следует делать здесь, чтобы избавиться от этой ошибки.

Ответы [ 2 ]

2 голосов
/ 09 марта 2020

Если я правильно понимаю, ваш SQLDLL сам управляет некоторым буфером памяти и возвращает указатель на строку Unicode (не ANSI, поэтому вы получили только один символ при попытке PAnsiChar, согласно на ваш комментарий ).

Inno Setup не поддерживает это напрямую и даже не имеет типа PWideChar. Тем не менее, мы можем справиться с этим сами. Нам просто нужно выделить строку Inno правильного размера и скопировать данные вручную.

Вот рабочий пример, как это сделать. Он использует GetCommandLineW в качестве примера функции, которая возвращает PWideChar, но вы можете сделать то же самое с вашей функцией SQLDLL.

  • Получить указатель из внешней функции и сохранить его в переменная (a Cardinal - в моем примере я создал для нее typedef PWideChar).
  • Получите длину строки, используя lstrlenW.
  • Создайте пустую регулярную String, но установите правильную длину, используя SetLength. Это зарезервирует достаточную емкость, чтобы мы могли записать в нее фактическое содержимое на следующем шаге.
  • Используйте lstrcpyW, чтобы скопировать строку, на которую ссылается указатель, на вашу обычную переменную String.
    • (Если вы используете версию Inno Setup для ANSI: используйте 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, только этот код выиграет т работа. Требуется следующее изменение: вместо 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);
0 голосов
/ 05 марта 2020

Вы не можете разыменовать указатель в Inno Setup Pascal Сценарий.

Но есть множество хаков, которые позволяют это. Эти хаки очень специфичны c, поэтому это зависит от конкретного варианта использования.


Хотя в вашем конкретном случае c, поскольку указатели на массивы символов широко распространены в API, Inno Setup Pascal Скрипт (с улыбкой до Delphi) может назначать указатель на массив символов на строку.

Таким образом, вы можете просто назначить PChar на AnsiString:

function ReturnsPAnsiChar: PAnsiChar; extern '...';

var
  Str: AnsiString;
begin
  Str := ReturnsPAnsiChar;
end;

См. Как вернуть строку из DLL в Inno Setup?

...