JConstantine
JConstantine

Reputation: 1070

How can I dereference a pointer in Inno Setup Pascal Script?

I call a function from DLL-file in Inno Setup Script and its return type is PAnsiChar. In order to get the whole string I need to dereference the pointer but the standard pascal syntax doesn't work here. Is it even possible to do that?

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;

I have only the DLL-file. The original language is Delphi.

I updated to the latest version of Inno Setup 6.0.3 and tested this code on my home Windows 10 Pro machine:

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

and now I'm having this kind of error: enter image description here

I don't understand why does it have to look into my 'temp' directory? I've also heard that this problem may somehow be connected with the group policies in Windows 10 UAC, but I'm not really sure what should I do here to get rid of this error.

Upvotes: 0

Views: 1137

Answers (2)

Martin Prikryl
Martin Prikryl

Reputation: 202118

You cannot dereference a pointer in Inno Setup Pascal Script.

But there are numerous hacks that allow that. Those hacks are very specific, so it depends on particular use case.


Though in your specific case, as pointers to character arrays are widespread in APIs, Inno Setup Pascal Script (similarly to Delphi) can assign a pointer to a character array to a string.

So, you should be able to simply assign the PChar to AnsiString:

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

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

See How to return a string from a DLL to Inno Setup?

Upvotes: 0

CherryDT
CherryDT

Reputation: 29011

If I understand correctly, your SQLDLL manages some memory buffer itself and returns a pointer to a Unicode string (not ANSI, that's why you got only one character when you tried PAnsiChar, according to your comment).

Inno Setup doesn't support this directly and doesn't even have a PWideChar type. However, we can handle it ourselves. We just have to allocate a Inno string with the right size and copy the data manually.

Here is a working example how to do that. It uses GetCommandLineW as an example function that returns a PWideChar, but you can do the same with your SQLDLL function.

  • Get the pointer from the external function and store it in a variable (a Cardinal - in my example I created a typedef PWideChar for it).
  • Get the string length using lstrlenW.
  • Create an empty regular String, but set it to the right length using SetLength. This will reserve enough capacity that we can write the actual contents into it in the next step.
  • Use lstrcpyW to copy the string that's referenced by the pointer to your regular String variable.
    • (In case you use the ANSI version of Inno Setup: Use WideCharToMultiByte instead, see my update at the end of this post.)

The trick is to import lstrcpyW in such a way that the destination pointer is declared as String but the source pointer is declared as Cardinal (or my typedef PWideChar here).

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 '[email protected] stdcall';

{ This function allows us to get us the length of a string from a PWideChar }
function lstrlenW(lpString: PWideChar): Cardinal;
external '[email protected] 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 '[email protected] 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;

If you put this into an installer and run it, you see output like this:

[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 

As you can see, the command line string (which we get as a PWideChar) is copied to a regular string variable correctly and can be accessed normally at the end.


Update: In case you are using the ANSI version of Inno Setup and not Unicode, this code alone won't work. The change needed is this: Instead of using lstrcpyW, you'd use WideCharToMultiByte:

function WideCharToMultiByte_ToInnoString(CodePage: Cardinal; dwFlags: Cardinal; lpWideCharStr: PWideChar; cchWideChar: Cardinal; lpMultiByteStr: String; cbMultiByte: Cardinal; lpDefaultChar: Cardinal; lpUsedDefaultChar: Cardinal): Integer;
external '[email protected] 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);

Upvotes: 3

Related Questions