Я пытаюсь использовать функцию GetPropertyValue в dll Advanced Installer.
Синтаксис функции следующий:
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
);
Параметры:
propertyName — входной параметр свойства для чтения (я использую «trialname» в качестве теста)
result - выходной параметр с начальным адресом буфера, куда записывается значение
resultLen - если функция выполнена успешно, содержит размер данных, скопированных в результат, иначе возвращает 234
Возвращаемые значения:
0 (ERROR_SUCCESS) — функция выполнена успешно.
234 (ERROR_MORE_DATA) — предоставленный буфер слишком мал для хранения всего значения и завершающего нулевого символа.
Я настроил функцию в 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;
При первом вызове функции я получаю возвращенный код ошибки 234, что, согласно документации, означает выделение буфера, а затем повторный вызов функции. Но при втором вызове функции я получаю нарушение доступа. Я нашел этот пост:
Предоставление буфера для получения значения Out LPWSTR
это помогло мне добраться до того места, где сейчас находится мой код, но у меня возникли проблемы с дальнейшим продвижением.
Вы совершаете несколько ошибок, которые приводят к неопределенному поведению:
__out LPWSTR
НЕ переводится в PLPWSTR
(указатель на указатель на WideChar), как у вас есть. На самом деле вместо этого он переводится как LPWSTR
(указатель на WideChar). __out
в C/C++ — это НЕ то же самое, что out
в Delphi.
Функция ожидает, что вы передадите указатель на предварительно выделенный буфер, который затем заполнится. Но вы ожидаете, что функция выделит для вас память, а этого не происходит. Вы вообще не выделяете буфер, хотя знаете, что должны это делать (вы сказали это в своем вопросе), поэтому второй вызов дает сбой, когда он пытается записать в недействительную память.
PWideString
— это указатель на WideString
, который несовместим ни с PLPWSTR
, ни с LPWSTR
в том виде, в каком вы его используете.
функция ожидает указатель на DWORD
, но вместо этого вы передаете ей указатель на DWORD64
. DWORD
— 4 байта, тогда как DWORD64
— 8 байт.
вы не инициализируете свою intsize
переменную с фактической емкостью, которую asReturn
может содержать. Вам повезло, что intSize
получил случайное значение, меньшее, чем ожидает функция, поэтому она возвращает 234 при первом вызове. Первый вызов вполне мог бы потерпеть неудачу, если бы intSize
вместо этого удерживал большее значение.
С учетом вышесказанного попробуйте что-нибудь еще вроде этого:
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;
Если когда-либо возможно, что значение свойства может измениться между двумя вызовами функции, вместо этого используйте цикл:
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;
Спасибо @RemyLebau
Я считаю, что ожидается, что вы предварительно выделите указатель
asReturn
на определенный размер и укажете этот размер в аргументеintSize
. Если это правда, то вам не следует использовать @ передasReturn
.