Проблема в приведённом коде кроется в управлении кодом ошибки. На первый взгляд - проблем нет.
Однако: какое у нас правило для вызова 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;
(*) Окей - не каждый такой вызов может приводить к смене кода ошибки. Например, освобождение памяти (строка или массив) могут привести только к записям в память, когда менеджер памяти делает правки в своих внутренних структурах учёта - случай, когда он решает придержать память. Но гарантий нет - это и называется "исключительно благодаря случайности".
Супер. Когда смотрел задачку, никакого косяка сразу не увидел, а искать косяк, не зная, как он проявляется, трудно. Очень интересно, хоть и бесполезно: ни разу ещё не доводилось работать через коды ошибок.
ОтветитьУдалитьСправедливо. Задачка сложная, особенно, если учесть, что там участвует баг Delphi.
ОтветитьУдалитьВозможно, мне бы и стоило дать подсказку, но я не знал как: с одной стороны - см. выше, с другой - в первых же ответах дали решение (причём, кажется, слизанное). Так что я был "в непонятках": не то задача сложна, не то проста.
В любом случае, кода там не много, поэтому можно было устроить штурм. Кроме того, я стараюсь писать ответы не сразу "вот проблема", а подводя к ней постепенно. Так что у вас есть возможность, прочитав два абзаца, вернуться к задачке и попробовать добить.
P.S. В чём был тайный смысл постить чужое готовое решение - я так и не понял. Мои задачки - это аналог квинтаны. Читеря, вы не обманываете никого, кроме самого себя.
>>> Очень интересно, хоть и бесполезно: ни разу ещё не доводилось работать через коды ошибок.
ОтветитьУдалитьНу это вообще кому как. Не всем нужно писать DLL и общаться с другими языками (в качестве вызываемой стороны), но уж кому надо - у тех это типичная задача.
Отправил на QC.
ОтветитьУдалитьА какова верояность что в SysFreeString будет вызов SetLastError, учитывая, что компилятор сам управляет выделением памяти? И есть ли он там?
ОтветитьУдалитьА почему бы просто не возвращать коды ошибок из функций?
ОтветитьУдалить>>> А какова верояность что в SysFreeString будет вызов SetLastError
ОтветитьУдалитьНу вы же не станете писать программы, опираясь на вероятность? Нужен чётко детерминированный способ.
>>> А почему бы просто не возвращать коды ошибок из функций?
Вы не сможете сделать это, если протокол не находится под вашим контролем (например - вы пишите плагин к готовой программе).
Бесполезная статья. У меня работает всё корректно
ОтветитьУдалитькод
DoSomethingW('');
ShowMessageFmt('%d', [GetLastError]);
Показывает 87
Это проверено на моих Delphi7, D2005, D2007, D2010, DXE, DXE3. У Вас от кривых компонентов был кривой менеджер памяти который можно подменять начиная с Delphi2 - читайте книгу Секреты Delphi2 Рея Лишнера.
Вдумчиво читать не пробовали?
ОтветитьУдалитьХотя проверил строка '' равна nil по этому у меня работало корректно если поменять условие S='' и передавать DoSomethingW('test') то возвращается действительно не то что надо.
ОтветитьУдалить