понедельник, 19 января 2009 г.

Почему возвращаемые значения-дескрипторы так непоследовательны?

Это перевод Why are HANDLE return values so inconsistent? Автор: Реймонд Чен.
Если вы внимательно посмотрите на различные функции, которые возвращают дескрипторы объектов ядра (THandle), то вы увидите, что какие-то их них возвращают 0 (например, CreateThread), а какие-то -1 - INVALID_HANDLE_VALUE (например, CreateFile). И вам каждый раз приходится заглядывать в документацию, чтобы проверить, что же возвращает вот эта конкретная функция при неудаче.

Почему же возвращаемые значения так отличаются?

Причины, как вы уже подозреваете, - исторические.

Эти значения выбирались, исходя из соображений совместимости с 16-ти битной Windows. 16-ти битные функции OpenFile, _lopen и _lcreat возвращали -1 при ошибке, поэтому 32-х разрядная функция CreateFile также возвращает -1 (INVALID_HANDLE_VALUE), чтобы упростить перенос приложений с Win16.

(Вооружённые этим знанием, вы уже можете ответить на следующий простой вопрос: почему, когда мне нужно вызывать CreateFile, хотя в действительности я не открываю файл? Разве не должна функция называться OpenFile? Ответ: да, OpenFile было бы более подходящим именем, но это имя уже занято.)

С другой стороны, в Win16 не было эквивалентов для CreateThread или CreateMutex, поэтому они возвращают 0.

Поскольку прецедент уже установил непоследовательность возвращаемых значений, то при добавлении новой функции всегда получается выбор между возвратом 0 или INVALID_HANDLE_VALUE.

Эта непоследовательность имеет несколько последствий.

По-первых, конечно же, вам нужно аккуратно проверять возвращаемые значения.

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

В-третьих, если вы хотите инициализировать значение дескриптора, то вам придётся делать это по-разному, в зависимости от функции, с которой вы собрались его использовать. Например, следующий код неверен:
var
H: THandle;
begin
H := 0;
if UseLogFile then
H := CreateFile(...);
DoOtherStuff;
if H <> 0 then
Log(H);
DoOtherStuff;
if H <> 0 then
CloseHandle(H);
end;
В этом коде два бага. Во-первых, возвращаемое из CreateFile значение проверяется неверно. Код выше проверяет на 0 вместо INVALID_HANDLE_VALUE. Во-вторых, код неверно инициализирует переменную H. Вот исправленный вариант:
var
H: THandle;
begin
H := INVALID_HANDLE_VALUE;
if UseLogFile then
H := CreateFile(...);
DoOtherStuff;
if H <> INVALID_HANDLE_VALUE then
Log(H);
DoOtherStuff;
if H <> INVALID_HANDLE_VALUE then
CloseHandle(H);
end;
В-четвёртых, вам нужно быть особенно внимательными со значением INVALID_HANDLE_VALUE: чисто случайно оно совпадает со значением псевдо-дескриптора, возвращаемым GetCurrentProcess. Многие функции ядра принимают такие псевдо-дескрипторы, поэтому, если вы, например, облажаетесь и случайно вызовите, скажем, WaitForSingleObject со значением INVALID_HANDLE_VALUE, в итоге вы обнаружите, что вы ждёте текущий процесс. Такое ожидание, конечно же, никогда не завершится, потому что процесс переходит в сигнальное состояние только при выходе, поэтому вы в итоге ждёте самого себя.

2 комментария:

  1. >>С другой стороны, в Win16 не было эквивалентов для CreateThread или CreateMutex, поэтому они возвращают 0.

    Странное утверждение.
    Почему бы в новых функциях не возвращать старое значение ошибки?

    ОтветитьУдалить
  2. Я не уверен и могу страшно наврать (сильно не бейте). Но могу попробовать угадать: думаю, потому что функции, возвращающие 0, пришли из WinNT. WinNT изначально была отдельной веткой (NT OS/2), не связанной с Win3.0/Win9x. Только после выхода Win3.0 MS сделала обе ветки совместимыми (добавила в NT поддержку WinAPI) - появился Win NT 3.1 и путаница с 0/INVALID_HANDLE_VALUE. 0 - это от Native API, а INVALID_HANDLE_VALUE - от WinAPI.

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.