Проблема в приведённом коде кроется в управлении кодом ошибки. На первый взгляд - проблем нет.
Однако: какое у нас правило для вызова SetLastError в подпрограмме? А такое, что вызов этой функции должен выполняться последним действием, дабы последующие вызовы подпрограмм не испортили сохранённый для вызывающего код ошибки.
Но, вроде бы, вызов SetLastError и есть последнее действие в программе?
Но тут на сцену выходит магия компилятора Delphi. В нашей процедуре мы используем переменную WideString, которая, как известно, относится к т.н. авто-финализируемым типам (их ещё называют managed-типами) - т.е. типам, временем жизни которых управляет компилятор.
Окей, тогда наводящий вопрос: а где в Delphi освобождаются авто-финализируемые типы данных? Очевидно, в нашем случае это произойдёт в строке с "end;" - т.е. после вызова SetLastError.
Вот вам и место проблемы. Финализация WideString означает безусловный вызов SysFreeString (упражнение: объяснить почему; подсказка: как связаны WideString и BSTR?). Вызов же SysFreeString сбрасывает код ошибки. Что означает, что ваша подпрограмма возвращает мусор, а не ERROR_INVALID_PARAMETER, как вы планировали. Ооопс.
Фактически, это означает, что вы не можете вернуть код ошибки из функции, где вы используете WideString.
Но это ещё не всё! Ведь помимо WideString у нас полно других (и даже более родных) управляемых типов данных. Что с ними? А с ними всё аналогично. Строки (String), дин. массивы, интерфейсы и т.п. - все они будут удалены в "end;". Что, фактически, означает как минимум вызов функций менеджера памяти, а как максимум - вызов деструктора объекта произвольной сложности (для интерфейсов). И плакали ваши коды ошибок.
Отсюда можно сделать вывод, что вы вообще не можете вернуть код ошибки из подпрограммы, где вы использовали хоть один (любой) управляемый тип данных. А любой такой работающий сегодня код работает исключительно благодаря случайности (*)!
Вообще-то это баг. И это баг Delphi. Кой следует сообщить на QC, что я, пожалуй и сделаю в ближайшее время. Кстати, это не единственный баг Delphi с GetLastError/SetLastError. Например, давно уже я сообщил о таком баге. Как видим, похоже, что Delphi вообще очень небрежно относится к кодам ошибок, что делает программирование на них исключительно сложным делом.
Как это можно исправить? Исключительно использованием wrapper-ов, например так:
function DoSomethingW(const AStr: PWideChar): Integer; stdcall;
function RealDoSomething(const AStr: PWideChar; out Rslt: Integer): Integer;
var
S: WideString;
begin
Result := ERROR_SUCCESS;
S := AStr;
if (S <> '') and SomeCondition then
Rslt := Length(S) // положим, это и есть реальная работа
else
begin
Result := ERROR_INVALID_PARAMETER;
Rslt := 0;
end;
end;
begin
SetLastError(RealDoSomething(AStr, Result));
end;
(*) Окей - не каждый такой вызов может приводить к смене кода ошибки. Например, освобождение памяти (строка или массив) могут привести только к записям в память, когда менеджер памяти делает правки в своих внутренних структурах учёта - случай, когда он решает придержать память. Но гарантий нет - это и называется "исключительно благодаря случайности".