25 февраля 2010 г.

Ответ на задачку №4

Ответ на задачку №4.

Проблема в приведённом коде кроется в управлении кодом ошибки. На первый взгляд - проблем нет.

Однако: какое у нас правило для вызова 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;

(*) Окей - не каждый такой вызов может приводить к смене кода ошибки. Например, освобождение памяти (строка или массив) могут привести только к записям в память, когда менеджер памяти делает правки в своих внутренних структурах учёта - случай, когда он решает придержать память. Но гарантий нет - это и называется "исключительно благодаря случайности".

24 февраля 2010 г.

Обмен материалами: HTML и кодировки

Прочитал Предложение для блогов Delphi. Не то, чтобы я загорелся этой идеей, да и постов для обмена у меня нет. Однако....

У меня есть материал, вернее код, который отлично подходит под формат блога "Delphi в Интернет". Это - обёртка над MLang. Я взял код из рабочего проекта (это часть EurekaLog 7, кстати; я писал это, когда работал над правильной поддержкой unicode) и отправил его Владиславу. А он по нему написал статью: MLang в Delphi. Работа с кодировками Web-страниц. Сам я никакой статьи из этого делать не собирался - не формат моего блога, да и в web я ничего не понимаю. А вот для блога "Delphi в Интернет" материал в самый раз.

Краткое описание:

В общем, очень часто бывают задачи получения web-страничек. В результате обычно получают Ansi-строку, содержащую контент страницы. Однако, страничка эта может быть в куче разных кодировок. Win1251, UTF-8, KOI-8r и т.п. Как получить из этого читабельный текст? Ведь нельзя просто взять и использовать фиксированную кодовую страницу: а вдруг она потом изменится? Кроме того, она же зависит и от страницы: у каждого сайта своя кодировка.

В своё время, когда мне надо было решить эту проблему, я приемлемого решения не нашёл и пришлось писать своё (везде все используют фиксированный набор кодировок, что меня не устраивало совершенно). Поэтому я написал своё решение, использующее MLang - я так понимаю это то, чем пользуется IE. MLang.pas - заголовочник для работы с MLang. Готового я не нашёл. У джедаев нет. Нашёл какие-то поделки на RSDN, которые пришлось допилить напильником.

В общем, не буду дальше распинаться, читаем статью. Возможно, в дальнейшем в блоге Владислава вас ожидает ещё материал на связанную тему или практический пример использования.

22 февраля 2010 г.

Замечания по переводам

Решил немного сменить подход к переводам. Теперь я не буду переводить материал, требующий проработки кода.

Причина - ужасно медленная скорость работы. Проблема в том, что на проработку кода требуется большой кусок (и, что самое главное) непрерывного времени. Если перевод я вполне могу делать во время перерывов кодинга (по абзацу другому за раз), то проработку кода нельзя делать в перерывах от кодинга (иначе какой же это перерыв?). В итоге получается, что пост с кодом у меня всё время откладывается и откладывается. Вот сейчас идёт серия про энумераторы - так всего готово два поста, а задержка уже огромная.

Поэтому я решил не заканчивать эту серию (по возможности я закончу её в фоновом режиме) и больше не браться за перевод постов с кодом. Это позволит мне выпускать переводы порегулярнее.

Да, и ещё у нас сегодня фигня творится с delphifeeds.ru. Почему-то один мой пост расклонировался на кучу записей. Весьма странно, особенно если учитывать, что я его написал за один проход, опубликовал и больше не редактировал. Как-то плохо delphifeeds.ru воспринял возвращение моих переводов. Такое ощущение, что скрипт сборки подбирает этот пост каждый раз во время проверки, почему-то считая его новым. На это указывают примерно равные промежутки между записями - около получаса.