Search code examples
pointersinno-setupdereferencepascalscript

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.


Solution

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