Кто-то может подумать, что типы BOOL и Boolean являются одним и тем же типом в Delphi, т.к. они могут содержать только true и false. Верно ведь? И да и нет. Они кажутся одинаковыми, но есть небольшая разница в трактовке этих типов при присваивании и сравнении. Чтобы увидеть разницу, нам нужно открыть окно CPU-отладчика и копнуть ассемблерный код. Не закрывайте страничку - это действительно не так сложно понять.
Вы также можете упаковать boolean-значение в integer. Фактически, заголовочники Windows определяют булевский тип именно так - так что мы посмотрим и на этот способ тоже.
Следующий ассемблерный код был снят с окна CPU в Delphi 7:
Integer
value := Integer(True); mov [value],$00000001BOOL/LongBool
value := true; mov [value],$ffffffffBoolean
value := true; mov byte ptr [value],$01Я не хочу философствовать о плохих или хороших сторонах этих строк. Главное тут в результате, который мы видим. Присваивание Integer и Boolean просто копирует единичку в переменную value. С другой стороны, присвоение для BOOL/LongBool выглядит иначе - и, фактически, с точностью до наоборот. Оно копирует значение $FFFFFFFF - что, очевидно, есть отрицательное значение для единицы. Так что результатом в переменной value будет -1. Вы можете проверить это с помощью следующего кода. Переменная i будет содержать отрицательное число.
var B: BOOL; I: Integer; begin B := true; i := Integer(B); end;Почему важно понимать эту разницу? Ну, некоторые функции Win32 API проверяют свои аргументы и могут возвращать ошибку 87 (неверный параметр) или (в худшем случае) весьма странно себя вести. Как вы увидели выше, каждый раз, когда значению типа BOOL присваивают true, Delphi устанавливает его в -1. Если функция Win32 API приводит тип аргумента к обычному integer, то она получит -1 ($ffffffff) и может провалиться, если она ожидает только 0 или 1.
Здесь история только начинается... Все заголовочники JEDI API (и стандартные заголовочники в Delphi - прим. пер.) используют тип BOOL, приводя его к Delphi-скому LongBool, который - как мы уже видели ранее - определяет значение “-1″ как true. В большинстве случаев функции Win32 API проверяют на равенство нулю - и тогда мы в порядке. Они сравнивают аргумент с нулём (false) - а в противном случае считают его true. Поскольку я не хочу брать на себя ответственность за столь кардинальные изменения - я решил оставить везде тип BOOL. Кроме того, изменение BOOL на Integer означало бы, что вы не сможете больше присваивать true и false, без необходимости приведения типа к BOOL (или Integer).
В итоге всё упирается в совместимость и удобство. Однако, с другой стороны вы должны помнить, что тип BOOL в Delphi может быть источником ошибки №87 (неверный аргумент).
Вывод:
X | BOOL в Delphi | BOOL в C | Boolean в Delphi |
обычный тип | LongBool | int | магия компилятора |
размер в байтах | 4 | 4 | 1 |
false = ? | 0 | 0 | 0 |
true = ? | -1 ($FFFFFFFF) | 1 | 1 |
Следующий список содержит функции, которые могут конфликтовать с различными объявлениями, потому что они проверяют значения аргумента. Если вы найдёте функцию, которая ведёт себя странно с аргументом типа BOOL - отправляйте её мне.
# | Имя | Заголовочный файл | Примечание |
1. | QueryServiceConfig2(W/A) | JwaWinSVC.pas | Проблема с TServiceDelayedAutoStartInfo (VISTA) |
Прим. пер.: разумеется, этот список больше, но в оригинальном посте в списке была всего одна функция. Проблемы, например, имеются у такой известной функции как CreateMutex. Посмотрите, как она реализована в Windows.pas. А может тут речь идёт только о полях BOOL в записях.
Решения, которые вы найдёте в JEDI API.
Вы можете найти различные решения этой проблемы в исходниках JEDI. Почему решений несколько? Потому что код был написан многими людьми, с разным уровнем знаний, и в различное время. Так что если вы увидите объявление записи (struct), которая использует объявление BOOL как в SERVICE_DELAYED_AUTO_START_INFO - вам нужно убедиться, что функция, которая использует аргументы такого типа, ведёт себя корректно.
typedef struct _SERVICE_DELAYED_AUTO_START_INFO { BOOL fDelayedAutostart; } SERVICE_DELAYED_AUTO_START_INFO;И здесь может быть два возможных решения... ну или вы так думаете.
type _SERVICE_DELAYED_AUTO_START_INFO = record fDelayedAutostart : BOOL; end; // или _SERVICE_DELAYED_AUTO_START_INFO = record fDelayedAutostart : Boolean; end;Первое объявление кажется корректным, но, как вы уже поняли, оно не всегда будет таким (например, оно не является верным для _SERVICE_DELAYED_AUTO_START_INFO в Vista). Второе может работать, но только благодаря случаю. Учтите, что тип BOOL в C имеет размер 4 байта (= sizeof(int)), а тип Boolean состоит только из одного. Получается, что мы обрезали у записи три байта. Поэтому целевая функция (здесь: ChangeServiceConfig2) “думает”, что поле записи fDelayedAutoStart состоит из 4-х байт (sizeof(int)) и читает три байта за структурой. Поскольку мы не знаем их содержания - они могут быть заполненными случайными данными. В результате получим число, которое не является ни нулём, ни +/-1. Т.к. ChangeServiceConfig2 может быть успешно выполнена только для 1 или 0, то мы получаем ошибку 87 (неверный аргумент).
Поэтому, на самом деле, здесь могут быть два таких решения:
type _SERVICE_DELAYED_AUTO_START_INFO = record fDelayedAutostart : Boolean; Pad : Array[0..2] of Byte; end; _SERVICE_DELAYED_AUTO_START_INFO = record fDelayedAutostart : Integer; end;Я предпочитаю второе решение и вам того же советую. Почему? Сначала вы можете подумать, что первый вариант - это идеальное решение, потому что вы можете просто присваивать true или false - прямо как в C. Однако, как я только что сказал, три байта в поле Pad тоже должны быть инициализированны. Поэтому правильный способ использования был бы таким:
ZeroMemory(@myRecord, SizeOf(myRecord));Проблема в том, что это не будет работать. Почему? Многие Delphi-программисты, работающие напрямую с API, находят решения своих проблем в исходниках на C. Они просто переводят исходники слово-в-слово с C на Delphi, не разбираясь в деталях. А дело в том, что вы не найдёте вызова ZeroMemory в таком C коде! Программист C может просто присвоить полю fDelayedAutoStart TRUE и всё: проблемные три байта инициализируются в момент присваивания. Ну, не кажется ли вам теперь второе решение лучшим?
Именно по этой причине вы увидите в последних версиях JEDI API только такую запись:
type _SERVICE_DELAYED_AUTO_START_INFO = record fDelayedAutostart : Integer; end;Да, компилятор не даст присвоить значение true напрямую, но это лучшее решение, что пришло мне в голову.
Нет ничего лучше, чем просто работающий код...
info.fDelayedAutostart := Integer(true);
Читать далее: BOOL vs. VARIANT_BOOL vs. BOOLEAN vs. bool.
Отправил на QC предложение добавить новый Boolean тип: http://qc.embarcadero.com/wc/qcmain.aspx?d=72852
ОтветитьУдалитьПо идее, они сами должны быть в курсе, но вдруг прокатит :D
замудрил
ОтветитьУдалитьПредполагаю, что проверка на равенство 0 или 1 выполняется, т.к. в будущем вероятно изменение типа параметра на Enum или Set с дополнительными значениями.
ОтветитьУдалитьТакое, например, наблюдается с твиками в реестре: 0 - выключено, 1 - включено, 2 - какой-то особый режим.