Search code examples
delphidlladvanced-installerlpcstrlpwstr

Using Advanced Installer DLL with Delphi


I am trying to use the GetPropertyValue function in the Advanced Installer dll.

The syntax for the function is:

UINT GetPropertyValue(
  __in    LPCWSTR propertyName,      // name of the licensing property to be read
  __out   LPWSTR result,             // buffer where the property value will be written
  __inout DWORD * resultLen          // capacity
);

The parameters are:

propertyName - input parameter of property to read (Im using 'trialname' as test)

result - output parameter with starting address of buffer where value is written

resultLen - if function is successful contains size of data copied to result, else returns 234

The return values are:

0 (ERROR_SUCCESS) - The function succeeded

234 (ERROR_MORE_DATA) - The provided buffer was too small to hold the entire value and the terminating null character

I have set up the function as follows in Delphi:

function GetPropertyValue(propertyName : LPCWSTR; Result : PLPWSTR; ResultLen : PDWORD64):Int32; stdcall; external DLLDirectory;

procedure TForm3.btn2Click(Sender: TObject);
Var
  asReturn : PWideString;
  intSize : DWORD64;
  TestValue : LPCWSTR;
  theint : Integer;
begin
  TestValue := LPCWSTR('TrialName');
  theint := GetPropertyValue(TestValue, @asReturn, @intsize);
  ShowMessage(theint.ToString);
  ShowMessage(intsize.ToString);
  if theint = 234 then begin
    GetPropertyValue(TestValue,@asReturn,@intsize);
  end;
end;

The first time I call the function i am getting the error code 234 returned which according to documentation means to allocate the buffer then call the function again. But on second call to the function i am getting access violation. I have found this post:

Providing a buffer to receive an Out LPWSTR value

which has helped me to get to where my code is now, but I am having trouble going any further.


Solution

  • You are making several mistakes that lead to undefined behavior:

    • __out LPWSTR DOES NOT translate to PLPWSTR (pointer to pointer to WideChar) as you have it. It actually translates to LPWSTR (pointer to WideChar) instead. __out in C/C++ is NOT the same thing as out in Delphi.

      The function expects you to pass in a pointer to a pre-allocated buffer, which it then fills in. But you are expecting the function to allocate the memory for you, which it doesn't. You are not allocating the buffer at all, even though you know you are supposed to (you said as much in your question), which is why the 2nd call is crashing when it tries to write to invalid memory.

    • PWideString is a pointer to a WideString, which is not compatible with either PLPWSTR or LPWSTR, the way you are using it.

    • the function expects a pointer to a DWORD, but you are giving it a pointer to a DWORD64 instead. DWORD is 4 bytes, whereas DWORD64 is 8 bytes.

    • you are not initializing your intsize variable to the actual capacity that asReturn can hold. You are getting lucky that intSize happens to receive a random value that is smaller than the function is expecting, which it why it returns 234 on the 1st call. The 1st call could very well have crashed if intSize were holding a larger value instead.

    With that said, try something more like this:

    function GetPropertyValue(propertyName : LPCWSTR; Result : LPWSTR; ResultLen : PDWORD): Integer; stdcall; external DLLDirectory;
    
    procedure TForm3.btn2Click(Sender: TObject);
    var
      sPropName, sValue : UnicodeString;
      dwSize : DWORD;
      intResult : Integer;
    begin
      sPropName := 'TrialName';
      dwSize := 0;
      intResult := GetPropertyValue(PWideChar(sPropName), nil, @dwSize);
      if intResult = 234 then begin
        SetLength(sValue, dwSize-1); // -1 to ignore null terminator
        intResult := GetPropertyValue(PWideChar(sPropName), PWideChar(sValue), @dwSize);
      end;
      if intResult = 0 then
        ShowMessage('Value: ' + sValue)
      end
        ShowMessage('Error: ' + intResult.ToString);
    end;
    

    If it is ever possible that the property value could change between the 2 function calls, then use a loop instead:

    function GetPropertyValue(propertyName : LPCWSTR; Result : LPWSTR; ResultLen : PDWORD): Integer; stdcall; external DLLDirectory;
    
    procedure TForm3.btn2Click(Sender: TObject);
    var
      sPropName, sValue : UnicodeString;
      dwSize : DWORD;
      intResult : Integer;
    begin
      sPropName := 'TrialName';
      dwSize := 0;
      repeat
        intResult := GetPropertyValue(PWideChar(sPropName), PWideChar(sValue), @dwSize);
        if intResult <> 234 then Break;
        SetLength(sValue, dwSize-1); // -1 to ignore null terminator
      until False;
      if intResult = 0 then begin
        SetLength(sValue, dwSize); // does not include null terminator
        ShowMessage('Value: ' + sValue);
      end else
        ShowMessage('Error: ' + intResult.ToString);
    end;