WDM驱动程序可以使用下面4种格式的字符串:
- Unicode串,由UNICODE_STRING结构描述,包含16位字符。Unicode有足够的代码空间编码地球上所有语言的字型。
- ANSI串,由ANSI_STRING结构描述,包含8位字符。另一种OEM_STRING串与其相似,也是用8位字符描述串。两者的不同是,OEM串中的字符字型由当前代码页决定,而ANSI串的字符字型不依赖任何代码页。WDM驱动程序不必处理OEM串,因为它们只能来自用户模式,在驱动程序看到这些串之前,它们已经被某些内核模式部件转换成Unicode串。
- 空结尾的字符串。你可以用普通的C语法表达串常量,例如,"Hello, world!",该串使用了类型为CHAR的8位字符,这些字符被假定属于ANSI字符集。串常量中的字符来自你创建源程序所用的编辑器。如果你的编辑器需要依赖当前代码页才能显示编辑窗口中的字符字型,那么应该注意,这些字符在Windows ANSI字符集中可能会有不同的含义。
- 空结尾的宽字符(WCHAR类型)串,用C语法也可以表达宽串常量,例如,L"Goodbye, cruel world!",该串看起来象Unicode串常量,但是,最后从文本编辑器出来的串实际仅使用了Windows ANSI字符集中ASCII和Latin1区(0020-007F和00A0-00FF)中的字符。
UNICODE_STRING和ANSI_STRING有相同的数据结构。Buffer域指向包含串数据的缓冲区。MaximumLength给出了缓冲区的长度,Length提供串的当前长度,它不考虑任何可能存在的空结束符。length域的单位是字节,对于UNICODE_STRING结构也是这样。
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _STRING {
USHORT Length;
USHORT MaximumLength;
PCHAR Buffer;
} ANSI_STRING, *PANSI_STRING;
表3-7列出了用于处理Unicode和ANSI字符串的服务函数。我还对应列出了一些标准C运行时间库函数,这些函数在内核模式中也存在,它们可以处理常规的C样式串。虽然它们从来没有在DDK文档中公开过,但标准DDK头中有这些函数的声明,而且连接库中也包含了它们,所以没有理由不使用它们。
表3-7. 串处理函数
操作 | ANSI串函数 | Unicode串函数 |
---|---|---|
Length | strlen | wcslen |
Concatenate | strcat, strncat | wcscat, wcsncat, RtlAppendUnicodeStringToString, RtlAppendUnicodeToString |
Copy | strcpy, strncpy, RtlCopyString | wcscpy, wcsncpy, RtlCopyUnicodeString |
Reverse | _strrev | _wcsrev |
Compare | strcmp, strncmp, _stricmp, _strnicmp, RtlCompareString, RtlEqualString | wcscmp, wcsncmp, _wcsicmp, _wcsnicmp, RtlCompareUnicodeString, RtlEqualUnicodeString, RtlPrefixUnicodeString |
Initialize | _strset, _strnset, RtlInitAnsiString, RtlInitString | _wcsnset, RtlInitUnicodeString |
Search | strchr, strrchr, strspn, strstr | wcschr, wcsrchr, wcsspn, wcsstr |
Upper/lowercase | _strlwr, _strupr, RtlUpperString | _wcslwr, _wcsupr, RtlUpcaseUnicodeString |
Character | isdigit, islower, isprint, isspace, isupper, isxdigit, tolower, toupper, RtlUpperChar | towlower, towupper, RtlUpcaseUnicodeChar |
Format | sprintf, vsprintf, _snprintf, _vsnprintf | swprintf, _snwprintf |
String conversion | atoi, atol, _itoa | _itow, RtlIntegerToUnicodeString, RtlUnicodeStringToInteger |
Type conversion | RtlAnsiStringToUnicodeSize, RtlAnsiStringToUnicodeString | RtlUnicodeStringToAnsiString |
Memory release | RtlFreeAnsiString | RtlFreeUnicodeString |
系统DLL还输出了许多RtlXxx函数,但我仅列出了在DDK头文件中定义了原型的函数,在驱动程序中我们仅应使用这些函数。
分配和释放串缓冲区
我不将详细描述这些串处理函数,DDK中的描述已经十分详细。你可以根据你的串使用经验使用它们。但这里有一个问题我要提一下。
你可能经常把UNICODE_STRING或ANSI_STRING类型的串定义成自动变量,或者定义成设备扩展的一部分。而串的数据缓冲区通常是动态分配的内存。但有时候你希望使用串常量,记录谁拥有某个串缓冲区可能是一个问题。考虑下面函数片段:
UNICODE_STRING foo; if (bArriving) RtlInitUnicodeString(&foo, L"Hello, world!"); else { ANSI_STRING bar; RtlInitAnsiString(&bar, "Goodbye, cruel world!"); RtlAnsiStringToUnicodeString(&foo, &bar, TRUE); } ... RtlFreeUnicodeString(&foo); // <--don't do this! |
在第一种情况下,我们为驱动程序中的宽字符串常量初始化foo.Length、foo.MaximumLength,和foo.Buffer。在另一种情况下,我们要求系统(使用第三个参数为TRUE的RtlAnsiStringToUnicodeString调用)为转化ANSI串分配内存。在第一种情况下,调用RtlFreeUnicodeString就是一个错误,因为它将无条件地释放常量串“Hello, world!”占用的内存,由于foo串的Buffer成员指向一个常量串,不是动态分配的内存,所以根本不能释放。在另一种情况下,由于我们指定了TRUE参数,系统为表达转换结果动态地分配了Unicode串缓冲区,所以我们必须调用RtlFreeUnicodeString释放该缓冲区,以避免内存泄漏。
Blob数据(大块数据)
我从数据库技术中借来术语“blob”来描述一块无结构定义的字节组合。表3-8列出了处理这种数据的函数(包括一些来自标准运行时间库中的函数),你可以在内核模式中调用它们。我假设你能从它们的名字上看出它们的用法。然而,我需要指出一些不明显的事实:
- 内存的“copy”和“move”操作之间的区别在于可否容忍源和目的相重叠。move操作不管源和目的是否重叠。而copy操作在源和目的有任何重叠时不工作。
- “byte”操作和“memory”操作的区别是操作的间隔尺寸。byte操作保证按字节为单位执行。而memory操作可以在内部使用更大的块,所有这些块的和等于指定的字节数。这个区别会根据平台的不同而改变,在32位Intel计算机上,byte操作实际上是对应memory操作的宏。但在Alpha平台上,RtlCopyBytes与RtlCopyMemory是完全不同的函数。
表3-8. 处理blob数据的服务函数
服务函数或宏 | 描述 |
---|---|
memchr | 在blob中寻找一个字节 |
memcpy, RtlCopyBytes, RtlCopyMemory | 复制字节,不允许重叠 |
memmove, RtlMoveMemory | 复制字节,允许重叠 |
memset, RtlFillBytes, RtlFillMemory | 用给定的值填充blob |
memcmp, RtlCompareMemory, RtlEqualMemory | 比较两个blob |
memset, RtlZeroBytes, RtlZeroMemory | blob清零 |