[Previous] [Next]

Соглашения об описании данных и функций

Дополнительные описатели типов

В документации и примерах DDK можно видеть относительно новые способы задания типов данных, которые внедряются там примерно с тем же энтузиазмом и размахом, как ранее это происходило с так называемой венгерской нотацией.

Помимо типов данных, представляющих сложные данные (структуры и объединения, типа DRIVER_OBJECT), в драйверных исходных текстах широко применяются новые обозначения известных стандартных типов данных. Заголовочный файл ntdef.h содержит множество определений, которые придают новый вид старым и хорошо известным описателям типов языка С. Например, описатели беззнаковых целых типов и указателей на переменные такого типа принимают вид:

typedef unsigned char UCHAR;
typedef unsigned short USHORT;
typedef unsigned long ULONG;
.......
typedef UCHAR *PUCHAR;
typedef USHORT *PUSHORT;
typedef ULONG *PULONG;
.......
typedef unsigned __int64 ULONGLONG;
.......
typedef ULONGLONG *PULONGLONG; 

Для чего это затеяна эта игра? В основном — для унификации стиля, когда в обращение вводятся совершенно новые типы данных, которые не являются базисными для языка С, скажем типа WCHAR (двухбайтный Unicode символ), или типа данных (а на самом деле — объединения) LARGE_INTEGER, наиболее часто используемого при программировании таймерных объектов, например:

typedef union _LARGE_INTEGER {
	struct {
		ULONG LowPart;
		LONG HighPart;
	};
	struct {
		ULONG LowPart;
		LONG HighPart;
	} u;
	LONGLONG QuadPart;
} LARGE_INTEGER; 

Другая цель этих дополнений — достичь универсальности исходных текстов при переходе с 32-разрядных платформ на 64-разрядные.

Квалификаторы IN, OUT, OPTIONAL

Еще одним небесполезным элементом украшения программного кода, активно используемого в DDK, являются макроопределения IN, OUT и другие. Как можно увидеть в файле ntdef.h, они не обозначают ровным счетом ничего, но зато повышают наглядность программного кода при его чтении, поскольку сообщают о назначении переменных, рядом с которыми используются. (Поскольку они ничего не значат, что является нетипичным для программирования, то позволим себе применить к ним нетипичный термин "квалификатор", который тоже ничего особенного не обозначает.)

#define IN
..........
#define OUT
..........
#define OPTIONAL
..........
#define CRITICAL 

Если обратиться к использованию этих элементов, то можно рассмотреть определение функции RtlZeroMemory, которая может быть использована для обнуления некоторой области виртуальной памяти (обнуляет страничную память на уровнях IRQL только ниже DISPATCH_LEVEL, нестраничную — на любых), а именно:

VOID
RtlZeroMemory(IN VOID UNALIGNED *Destination, IN ULONG Length); 

В этом описании квалификатор IN сообщает, что указатель Destination и длина буфера Length являются параметрами, передаваемыми внутрь вызова RtlZeroMemory.

В иных случаях бывают даже сочетания IN OUT, что сигнализирует: через этот параметр вызова информация передается внутрь данного вызова и возвращается обратно. Квалификатор OPTIONAL обозначает необязательные параметры.

Типы возвращаемых значений функций

Все функции, описываемые в языке С, либо возвращают значение определенного типа, либо не возвращают ничего (в последнем случае они описываются типом void). В переводе на новые термины, предлагаемые DKK, функции возвращают значения типа CHAR, UCHAR, SHORT, USHORT и т.п., либо описываются как VOID функции.

В дополнение к этим, знакомым и понятным типам функций в DDK добавлен тип NTSTATUS. Что означает этот новый тип?

Тип NTSTATUS несет код завершения функции и, на самом деле, является простым переопределением типа integer long, что можно увидеть, например, в файле ntdef.h, а именно:

typedef LONG NTSTATUS;
typedef NTSTATUS *PNTSTATUS;

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

Когда Диспетчер ввода/вывода вызывает одну из процедур драйвера, а она возвращает ему управление, передавая при этом код NTSTATUS, то сигнализирует ему об условиях окончания работы. Файл NTSTATUS.h содержит символьные имена для всех возможных значений кодов NTSTATUS.

Код, сообщающий об удачном завершении, носит имя STATUS_SUCCESS (кстати, равный 0). В случае, если работа завершена с ошибкой, код неудачного завершения, например, STATUS_UNEXPECTED_IO_ERROR (равный 0xC00000E9), транслируется системой в системный код ошибки и передается пользовательскому приложению, для которого работала данная процедура драйвера.

Для работы с кодами завершения NTSTATUS существуют несколько полезных макроопределений (описанных в файле ntdef.h), среди которых самым употребительным является макроопределение NT_SUCCESS(). При употреблении в логических выражениях, оно позволяет проверять, означает ли анализируемый код удачное завершение работы, например:

NTSTATUS Status = IoConnectInterrupt();
if( !NT_SUCCESS(Status) ) { <обработка ошибки> } 

Соглашения об именах функций драйвера и системных вызовов

Не последнюю роль в разработке программного продукта играют правила составления идентификаторов и размещения исходного текста в файлах. Если хорошие правила вырабатываются годами участия в крупных проектах, то плохие каждый может сформулировать без труда: называйте функции и переменные случайным образом, а еще лучше — x1, x2, function133 и т.п.

Поскольку Microsoft следует определенным правилам составления имен своих вызовов, то системные функции отличить в тексте драйвера несложно: они имеют префикс из числа представленных в таблице 4.1, например, HalGetInterruptVector, вызов, относящийся к множеству аппаратных абстракций, что обозначено префиксом Hal. Правда, Microsoft все-таки не до конца последовательно проводит эту линию в жизнь. Например, в именах функций, обслуживающих объект адаптера (структура DMA_OPERATIONS), нет уже никаких префиксов такого типа, как, скажем, в именах FreeAdapterChannel, CalculateScatterGatherList и AllocateCommonBuffer.

В пакете DDK все имена типов данных и все макроопределения вводятся прописными (большими) буквами, например, PVOID, PHYSICAL_ADDRESS, READ_PORT_UCHAR. Упоминание о так называемой венгерской нотации в DDK практически не встречается, однако, Microsoft рекомендует разработчикам драйверов использовать собственные короткие префиксы для обозначения собственных функций и, возможно, идентификаторов переменных.