程式扎記: [ Windows DDP ] Windows 內核函式 : 字串操作

標籤

2011年1月4日 星期二

[ Windows DDP ] Windows 內核函式 : 字串操作

前言 : 
和應用程式一樣, 驅動程式需要經常和字串打交道. 其中包含 ASCII 字串, 寬字串還有 DDK 定義的 ANSI_STRING 資料結構和 UNICODE_STRING 資料結構. 

ASCII 字串和寬字串 : 
應用程式中, 往往使用兩種字元 : 一種是 char 型的字串, 負責記錄 ANSI 字元集, 它是指向一個 char 陣列的指標, 每個 char 型變數的大小為一個位元組, 字串是以 0 旗標字串結束. 還有一種是 wchar_t 型的寬字串, 負責描述 unicode 字元集的字串, 它是只像一個 wchar_t 陣列的指標, wchar_t 字元大小為兩個位元組, 字串以 0 旗標字串結束. ANSI 字元的使用如下 : 

char* str1 = "abc";

str1 指標指向內容是 61 626 300. UNICODE 字元的構造如下 : 
wchar_t *str2 = L"abc";

str2 指標指向的內容是 6100 620 063 000 000. 在建構字串的時候使用一個關鍵字 "L" 編譯器會自動生成所需要的寬字元. 
在驅動程式開發中, DDK 將 char 和 wchar_t 類別替換成 CHAR 和 WCHAR 類別. 對於這兩類的字串, DDK 提供對應的字串操作函式, 例如 strcpy, sprintf, strcat, strlen 等. 但 DDK 的幫助文檔中, 不會查到這些函式的用法. 微軟公司不鼓勵直接使用這些函式, 取而代之的是使用同樣功能的巨集. 驅動程式可以用 KdPrint 列印 ASCII 字串與寬字串. KdPrint 類似於 C 語言的 printf 函式. 例如列印一個 ASCII 字串/寬字元 : 
  1. CHAR *string="Hello";  
  2. KdPrint(("%s\n", string)); // 列印ASCII, 注意是小寫 %s  
  3. WCHAR *strings=L"Hello";  
  4. KdPrint(("%S\n",string2)); // 列印寬字元, 注意是大寫 %S  
ANSI_STRING 字串與 UNICODE_STRING 字串 : 
DDK 不鼓勵程式設計師使用 C 語言的字串, 主要是因為 : 標準 C 的字串處理函式容易導致緩衝區溢位等錯誤. 如果程式設計師不對字串長度進行檢驗, 很容易導致這個錯誤, 從而導致整個系統的崩潰. DDK 鼓勵程式設計師使用 DDK 自訂的字串, 這種資料格式定義如下 : 
- ANSI_STRING Structure :
  1. typedef struct _STRING {  
  2.   USHORT Length;  
  3.   USHORT MaximumLength;  
  4.   PCHAR  Buffer;  
  5. } ANSI_STRING, *PANSI_STRING;  

這個資料結構對 ANSII 字串進行了封裝. 
參數說明 : 
* Length : 字元長度.
* MaximumLength : 整個字串緩衝區的最大長度
* Buffer : 緩衝區指標.

和 ANSI_STRING 相對應, DDK 將寬字串封裝成 UNICODE_STRING 資料結構 : 
- UNICODE_STRING Structure :
  1. typedef struct _UNICODE_STRING {  
  2.   USHORT Length;  
  3.   USHORT MaximumLength;  
  4.   PWSTR  Buffer;  
  5. } UNICODE_STRING, *PUNICODE_STRING;  

參數說明 : 
* Length : 字元長度. 單位是位元組. 如果是 N 個字元, 那麼 Length 等於 N 的 2 倍.
* MaximumLength : 整個字串緩衝區的最大長度
* Buffer : 緩衝區指標.

和 ANSI_STRING 不同的是 UNICODE_STRING 的緩衝區是記錄寬字元的緩衝區. 和 ANSI_STRING 一樣, 字串的結束不是以 NULL 為旗標, 而是依靠欄位 Length. 關於 ANSI_STRING 字串和 UNICODE_STRING 字串, KdPrint 同樣提供了列印 log 的方法 : 
  1. ANSI_STRING ansiString;  
  2. // 省去對 ansiString 初始化  
  3. KdPrint("%Z\n", &ansiString);  // 注意是 %Z  
  4. UNICODE_STRING uniString;  
  5. // 省去對 uniString 初始化  
  6. KdPrint("%wZ\n", &uniString); // 注意是 %wZ  
字元初始化與銷毀 : 
ANSI_STRING 字串和 UNICODE_STRING 字串使用前需要進行初始化. 有兩種辦法建構這個資料結構. 
(1) 方法一是使用 DDK 提供了對應函式. 
初始化 ANSI_STRING 字串函式 RtlInitAnsiString : 
- Syntax :
  1. VOID RtlInitAnsiString(  
  2.   __out     PANSI_STRING DestinationString,  
  3.   __in_opt  PCSZ SourceString  
  4. );  

參數說明 : 
* DestinationString : 要初始化的 ANSI_STRING 字串.
* SourceString : 字串的內容.

初始化 UNICODE_STRING 字串函式 RtlInitUnicodeString : 
- Syntax :
  1. VOID RtlInitUnicodeString(  
  2.   __out     PUNICODE_STRING DestinationString,  
  3.   __in_opt  PCWSTR SourceString  
  4. );  

參數說明 : 
* DestinationString : 要初始化的 UNICODE_STRING字串.
* SourceString : 字串的內容.

以使用 RtlInitAnsiString 為例, 其使用方法是 : 
  1. ANSI_STRING AnsiString1;  
  2. CHAR* string1="hello";  
  3. RtlInitAnsiString(&AnsiString1, string1);  
這種辦法是將 AnsString1 中的 Buffer 指標指向 string1 指標指向的地方. 這種初始化的優點是操作簡單, 用完後不用清理記憶體. 但帶來另外一個問題是, 如果修改 string1, 同時會導致 AnsiString1 字元發生變化!! 看下面程式碼 : 
- 測試 ANSI_STRING 範例代碼 : 修改 string1 會影響 AnsiString1
  1. //(1)用RtlInitAnsiString初始化字串  
  2. ANSI_STRING  AnsiString1;  
  3. CHAR * string1= "hello";  
  4. //初始化ANSI_STRING字串  
  5. RtlInitAnsiString(&AnsiString1,string1);  
  6. KdPrint(("AnsiString1:%Z\n",&AnsiString1));//列印hello  
  7.   
  8. string1[0]='H';  
  9. string1[1]='E';  
  10. string1[2]='L';  
  11. string1[3]='L';  
  12. string1[4]='O';  
  13. //改變string1,AnsiString1同樣會導致變化  
  14. KdPrint(("AnsiString1:%Z\n",&AnsiString1));//列印HELLO  

(2) 另一種方法是程式設計師自己申請記憶體, 並初始化記憶體, 當不用字串時, 需要回收字串佔用的記憶體 : 
- 範例代碼 : 程式設計師自己初始化 UNICODE_STRING
  1. #define BUFFER_SIZE 1024  
  2.     UNICODE_STRING UnicodeString1 = {0};  
  3.     //設置緩衝區大小  
  4.     UnicodeString1.MaximumLength = BUFFER_SIZE;  
  5.     //分配記憶體  
  6.     UnicodeString1.Buffer = (PWSTR)ExAllocatePool(PagedPool,BUFFER_SIZE);  
  7.     WCHAR* wideString = L"hello";  
  8.   
  9.     //設置字元長度,因為是寬字元,所以是字元長度的2倍  
  10.     UnicodeString1.Length = 2*wcslen(wideString);  
  11.   
  12.     //保證緩衝區足夠大,否則程式終止  
  13.     ASSERT(UnicodeString1.MaximumLength>=UnicodeString1.Length);  
  14.     //記憶體拷貝  
  15.     RtlCopyMemory(UnicodeString1.Buffer,wideString,UnicodeString1.Length);  
  16.     //設置字元長度  
  17.     UnicodeString1.Length = 2*wcslen(wideString);  
  18.   
  19.     KdPrint(("UnicodeString:%wZ\n",&UnicodeString1));  
  20.   
  21.     //清理記憶體  
  22.     ExFreePool(UnicodeString1.Buffer);  
  23.     UnicodeString1.Buffer = NULL;  
  24.     UnicodeString1.Length = UnicodeString1.MaximumLength = 0;  

對於最後一步清理記憶體, DDK 同樣給出了簡化函式, 分別是 RtlFreeAnsiString  RtlFreeUnicodeString, 這兩個函式內部呼叫了 ExFreePool 去回收記憶體. 

字串複製 : 
DDK 提供針對 ANSI_STRING 字串和 UNICODE_STRING 字串的複製字串命令, 分別是 RtlCopyString  RtlCopyUnicodeString. 下面程式演示了如何使用 RtlCopyUnicodeString 與 RtlCopyString 函式的使用方法 : 
- 範例代碼 : For RtlCopyUnicodeString 函式 & RtlCopyString 函式
  1. #pragma INITCODE  
  2. VOID StringCopyTest()   
  3. {  
  4.     //初始化UnicodeString1  
  5.     UNICODE_STRING UnicodeString1;  
  6.     RtlInitUnicodeString(&UnicodeString1,L"Hello World");  
  7.   
  8.     //初始化UnicodeString2  
  9.     UNICODE_STRING UnicodeString2={0};  
  10.     UnicodeString2.Buffer = (PWSTR)ExAllocatePool(PagedPool,BUFFER_SIZE);  
  11.     UnicodeString2.MaximumLength = BUFFER_SIZE;  
  12.   
  13.     //將初始化UnicodeString2拷貝到UnicodeString1  
  14.     RtlCopyUnicodeString(&UnicodeString2,&UnicodeString1);  
  15.   
  16.     //分別顯示UnicodeString1和UnicodeString2  
  17.     KdPrint(("UnicodeString1:%wZ\n",&UnicodeString1));  
  18.     KdPrint(("UnicodeString2:%wZ\n",&UnicodeString2));  
  19.   
  20.     //銷毀UnicodeString2  
  21.     //注意!!UnicodeString1不用銷毀  
  22.     RtlFreeUnicodeString(&UnicodeString2);        
  23. }  

字串比較 : 
DDK 提供了對 ANSI_STRING 字串和 UNICODE_STRING 字串的相關字串比較的命令, 分別是 RtlCompareString  RtlCompareUnicodeString. 這兩個函式的參數型式相同, 以 RtlCompareUnicodeString 為例, 前面兩個參數分別是需要比較的字串, 第三個參數指定是否對大小寫敏感. 如果函式返回值為 0, 表示兩個字串相等. 如果小於零, 則表示第一個字串小於第二個字串. 反之亦然. 
同時DDK 又有提供了 RtlEqualString  RtlEqualUnicodeString 函式, 其使用方法和上面兩個函式類似, 只是返回非零代表相等, 零代表不相等. 下面程式演示了如何使用 RtlCompareUnicodeString 函式 : 
- RtlCompareUnicodeString 使用範例代碼 :
  1. #pragma INITCODE  
  2. VOID StringCompareTest()   
  3. {  
  4.     //初始化UnicodeString1  
  5.     UNICODE_STRING UnicodeString1;  
  6.     RtlInitUnicodeString(&UnicodeString1,L"Hello World");  
  7.   
  8.     //初始化UnicodeString2  
  9.     UNICODE_STRING UnicodeString2;  
  10.     RtlInitUnicodeString(&UnicodeString1,L"Hello");  
  11.   
  12.     if (RtlEqualUnicodeString(&UnicodeString1,&UnicodeString2,TRUE))  
  13.     {  
  14.         KdPrint(("UnicodeString1 and UnicodeString2 are equal\n"));  
  15.     }else  
  16.     {  
  17.         KdPrint(("UnicodeString1 and UnicodeString2 are NOT equal\n"));  
  18.     }  
  19.   
  20. }  

字串轉化成大寫 : 
DDK 提供了對 ANSI_STRING 字串和 UNICODE_STRING 字串的大小寫轉換函式 RtlUpcaseUnicodeString  RtlUpperString. 下面代碼演示如何使用 RtlUpcaseUnicodeString 函式 : 
- RtlUpcaseUnicodeString函式範例代碼 :
  1. #pragma INITCODE  
  2. VOID StringToUpperTest()   
  3. {  
  4.     //初始化UnicodeString1  
  5.     UNICODE_STRING UnicodeString1;  
  6.     RtlInitUnicodeString(&UnicodeString1,L"Hello World");  
  7.   
  8.     //變化前  
  9.     KdPrint(("UnicodeString1:%wZ\n",&UnicodeString1));  
  10.   
  11.     //變大寫  
  12.     RtlUpcaseUnicodeString(&UnicodeString1,&UnicodeString1,FALSE);  
  13.   
  14.     //變化後  
  15.     KdPrint(("UnicodeString1:%wZ\n",&UnicodeString1));  
  16. }  

字串與整數型數字相互轉換 : 
DDK 提供了 UNICODE_STRING 字串與整數相互轉換的內核函式. 
(1) 將 UNICODE_STRING 字串轉換成整數. 
這個函式是 RtlUnicodeStringToInteger, 其宣告是 : 
- Syntax :
  1. NTSTATUS RtlUnicodeStringToInteger(  
  2.   __in      PCUNICODE_STRING String,  
  3.   __in_opt  ULONG Base,  
  4.   __out     PULONG Value  
  5. );  

(2) 將整數轉換成 UNICODE_STRING 字串. 
這個函式是 RtlIntegerToUnicodeString, 其宣告是 : 
- Syntax :
  1. NTSTATUS RtlIntegerToUnicodeString(  
  2.   __in      ULONG Value,  
  3.   __in_opt  ULONG Base,  
  4.   __inout   PUNICODE_STRING String  
  5. );  

以下是字串與整數互相轉換的範例 : 
- 字串與整數互相轉換範例代碼 :
  1. #pragma INITCODE  
  2. VOID StringToIntegerTest()   
  3. {  
  4.     //(1)字串轉換成數字  
  5.     //初始化UnicodeString1  
  6.     UNICODE_STRING UnicodeString1;  
  7.     RtlInitUnicodeString(&UnicodeString1,L"-100");  
  8.   
  9.     ULONG lNumber;  
  10.     NTSTATUS nStatus = RtlUnicodeStringToInteger(&UnicodeString1,10,&lNumber);  
  11.     if ( NT_SUCCESS(nStatus))  
  12.     {  
  13.         KdPrint(("Conver to integer succussfully!\n"));  
  14.         KdPrint(("Result:%d\n",lNumber));  
  15.     }else  
  16.     {  
  17.         KdPrint(("Conver to integer unsuccessfully!\n"));  
  18.     }  
  19.   
  20.     //(2)數字轉換成字串  
  21.     //初始化UnicodeString2  
  22.     UNICODE_STRING UnicodeString2={0};  
  23.     UnicodeString2.Buffer = (PWSTR)ExAllocatePool(PagedPool,BUFFER_SIZE);  
  24.     UnicodeString2.MaximumLength = BUFFER_SIZE;  
  25.     nStatus = RtlIntegerToUnicodeString(200,10,&UnicodeString2);  
  26.   
  27.     if ( NT_SUCCESS(nStatus))  
  28.     {  
  29.         KdPrint(("Conver to string succussfully!\n"));  
  30.         KdPrint(("Result:%wZ\n",&UnicodeString2));  
  31.     }else  
  32.     {  
  33.         KdPrint(("Conver to string unsuccessfully!\n"));  
  34.     }  
  35.   
  36.     //銷毀UnicodeString2  
  37.     //注意!!UnicodeString1不用銷毀  
  38.     RtlFreeUnicodeString(&UnicodeString2);  
  39.   
  40. }  

ANSI_STRING 字串與 UNICODE_STRING 字串相互轉換 : 
DDK 提供了 ANSI_STRING 字串與 UNICODE_STRING 字串相互轉換的相關函式. 
(1) 將 UNICODE_STRING 字串轉成 ANSI_STRING 字串. 
對應轉換函式是 RtlUnicodeStringToAnsiString, 其宣告是 : 
- Syntax :
  1. NTSTATUS RtlUnicodeStringToAnsiString(  
  2.   __inout  PANSI_STRING DestinationString,  
  3.   __in     PCUNICODE_STRING SourceString,  
  4.   __in     BOOLEAN AllocateDestinationString  
  5. );  

(2) 將 ANSI_STRING 字串轉換成 UNICODE_STRING 字串 
對應轉換函式是 RtlAnsiStringToUnicodeString, 其宣告是 : 
- Syntax :
  1. NTSTATUS RtlAnsiStringToUnicodeString(  
  2.   __inout  PUNICODE_STRING DestinationString,  
  3.   __in     PCANSI_STRING SourceString,  
  4.   __in     BOOLEAN AllocateDestinationString  
  5. );  

底下程式碼演是兩種字串間的轉換 : 
- 範例代碼 :
  1. #pragma INITCODE  
  2. VOID StringConverTest()   
  3. {  
  4.     //(1)將UNICODE_STRING字串轉換成ANSI_STRING字串  
  5.     //初始化UnicodeString1  
  6.     UNICODE_STRING UnicodeString1;  
  7.     RtlInitUnicodeString(&UnicodeString1,L"Hello World");  
  8.   
  9.     ANSI_STRING AnsiString1;  
  10.     NTSTATUS nStatus = RtlUnicodeStringToAnsiString(&AnsiString1,&UnicodeString1,TRUE);  
  11.       
  12.     if ( NT_SUCCESS(nStatus))  
  13.     {  
  14.         KdPrint(("Conver succussfully!\n"));  
  15.         KdPrint(("Result:%Z\n",&AnsiString1));  
  16.     }else  
  17.     {  
  18.         KdPrint(("Conver unsuccessfully!\n"));  
  19.     }  
  20.   
  21.     //銷毀AnsiString1  
  22.     RtlFreeAnsiString(&AnsiString1);  
  23.   
  24.     //(2)將ANSI_STRING字串轉換成UNICODE_STRING字串  
  25.     //初始化AnsiString2  
  26.     ANSI_STRING AnsiString2;  
  27.     RtlInitString(&AnsiString2,"Hello World");  
  28.   
  29.     UNICODE_STRING UnicodeString2;  
  30.     nStatus = RtlAnsiStringToUnicodeString(&UnicodeString2,&AnsiString2,TRUE);  
  31.       
  32.     if ( NT_SUCCESS(nStatus))  
  33.     {  
  34.         KdPrint(("Conver succussfully!\n"));  
  35.         KdPrint(("Result:%wZ\n",&UnicodeString2));  
  36.     }else  
  37.     {  
  38.         KdPrint(("Conver unsuccessfully!\n"));  
  39.     }  
  40.   
  41.     //銷毀UnicodeString2  
  42.     RtlFreeUnicodeString(&UnicodeString2);  
  43. }  

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!