RtlInitUnicodeString、IoCreateDevice、IoCreateSymbolicLink、IoDeleteDevice 四個 API 驅動函數的使用


      要解釋“驅動對象”,就得先從 DriverEntry() 說起:

       做過C語言開發的都知道程序是從 main() 函數開始執行。在進行 Windows 驅動程序開發的時候沒有 main() 函數作為函數入口,取而代之的是 DriverEntry().

       DriverEntry() 的原型如下:

extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath)
{    
}

       前面的 extern “C”大概的意思就是調用C編譯器對函數進行編譯,實現C++和C的混合編程。

       DriverEntry

       描述:初始化驅動程序,定位和申請硬件資源,創建內核對象

       參數:

       pDriverObject

       從I/O管理器中傳來的驅動對象

       pRegistryPath

       驅動程序在注冊表中的路徑

       返回值: 返回初始化驅動狀態

 

什么是 DRIVER_OBJECT 驅動對象,他的作用是什么?

       每一個 驅動對象 代表着一個已經裝載到內核模式下的驅動,以下例程的輸入參數之一就都包含驅動對象:

            DriverEntry, AddDevice, Reinitialize(可選例程),Unload(可選例程)

       它們都包含指向驅動對象的指針。

      驅動對象是一個半透明對象,驅動編寫者必須熟悉它的某些成員對象,以實現驅動的初始化功能和卸載功能(如果該驅動能夠卸載)。以下列出的是驅動對象中能被驅動訪問的成員:

      可訪問成員

      PDEVICE_OBJECT DeviceObject

      指向一個由驅動創建的設備對象,當驅動程序調用IoCreateDevice成功時,該成員會自動更新。驅動程序可以利用該成員以及DEVICE_OBJECT對象中的NextDevice成員來實現對由該驅動創建的所有設備列表中設備的遍歷。

      PDRIVER_EXTENSION DriverExtension

      驅動擴展對象指針,該對象唯一能訪問的成員是DriverExtension-> AddDevice,對應的是驅動DriverEntry例程中的AddDevice例程。

      PUNICODE_STRING HardwareDatabase

      指向\Registry\Machine\Hardware,該路徑指向的是注冊表中包含該硬件的配置信息。

      PFAST_IO_DISPATCH FastIoDispatch

      指向快速I/O入口地址,該成員之用於FSD(文件系統驅動)已經網絡傳輸驅動。

      PDRIVER_INITIALIZE DriverInit

      DriverEntry例程的入口點,由I\O管理器設置。

     PDRIVER_STARTIO DriverStarIo

      驅動程序中StartIo例程的入口地址(如果有的話),當驅動初始化時,DriverEntry例程負責設置它,如果驅動程序沒有StartIo,該成員為NULL。

      PDRIVER_UNLOAD DriverUnload

      驅動程序中Unload例程的入口地址(如果有的話),當驅動初始化時,DriverEntry例程負責設置它,如果驅動程序沒有StartIo,該成員為NULL。

      PDRIVER_DISPATCHMajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]

      派遣例程表,該表包含了驅動中DispatchXxx routines等所有派遣例程的入口地址。該數組的索引值為IRP_MJ_Xxx,該值代表每一個IRP的主功能函數代碼(IRP major function code), 任何驅動都必須為IRP_MJ_Xxx請求設置入口地址。每一個DispatchXxx例程的定義如下所示:

NTSTATUS(*PDRIVER_DISPATCH) (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp); 

     備注:

      每一個內核模式的驅動的初始化例程(驅動入口)都必須命名為 DriverEntry,這樣系統才可以自動將驅動加載進來。

      如果例程的名字為其他,則驅動編寫者必須為鏈接器指定對應的初始化例程的名字,否則,系統的加載器或者I/O管理器將無法找到驅動的傳遞地址。其他標准驅動的名字可以由驅動編寫者自行決定。

      一個驅動必須在驅動對象中設置 DispatchXxx 的入口地址,在驅動加載之后,該驅動對象將傳遞給 DriverEntry。一個設備的驅動必須為每一個 IRP_MJ_XXX 設置一個或者多個DispatchXxx 的入口地址,對同一類型的設備來說,該 IRP_MJ_XXX 都將被處理。一個上層的驅動必須為所有的 IRP_MJ_XXX 設置一個或者多個 DispatchXxx 入口點,這些IRP_MJ_XXX 將被發送到下一級的設備驅動,否則,驅動無法給沒有設置 DispatchXxx 例程發送任何 IRP_MJ_XXX。

      DriverEntry 例程設置了的驅動的 AddDevice, StartIO 以及 Unload 等函數的入口地址。

      當驅動加載時,設備驅動可以利用 HardWareDatabase 字符串來從注冊表中獲取硬件的配置信息。該字符串對驅動來說只讀。驅動對象中沒有列出的成員是無法訪問的。

 

什么是 DEVICE_OBJECT 驅動對象,它的作用是什么?

      DEVICE_OBJECT 結構被操作系統用來表示為一個”設備對象 ”,這個設備對象可以代表邏輯的、虛擬的或者是物理設備。每一個設備對象都會有一個指針來指向下一個設備對象(如果有的話),形成一個設備鏈。設備鏈的第一個設備是在 DRIVER_OBJECT 驅動對象結構體中指明的。

 

驅動的 API 函數:

     1. RtlInitUnicodeString

      作用: 初始化設備名稱指針。

VOID  RtlInitUnicodeString
(
IN OUT UNICODE_STRING DestinationString,
IN PCWSTR SourceString
);

       參數:

       DestinationString

       需要初始化的指針 UNICODE_STRING

       SourceString

       指向一個以空結尾的 Unicode 字符串常量,用這個字符串來初始化 DestinationString     

       如例:

       代碼1:

UNICODE_STRING US1; 

RtlInitUnicodeString(&US1,L"DDDD");

         會動態分配一塊指向“DDDD”的內存指針,賦值給US1.Buffer;

       代碼2:

wchar_t tmpstr[260]={0}; 

UNICODE_STRING US1; 

RtlInitUnicodeString(&US1,tmpstr);

          這時 US1.Buffer 直接指向 tmpstr, 如果修改了 US1,也會同時修改 tmpstr。

        另外此時 US1.MaximumLength=2;

        要重新設定 MaximumLength=260*2,才能正常使用。

        以前總是對符號鏈接不太明白,今天看到了一篇文章,講的很好,記錄一下。

        Windows下的設備是以"\Device\[設備名]”形式命名的。例如磁盤分區的c盤,d盤的設備名就是 "\Device\HarddiskVolume1”,"\Device\HarddiskVolume2”, 當然也可以不指定設備名稱。

        如果 IoCreateDevice 中沒有指定設備名稱,那么I/O管理器會自動分配一個數字作為設備的名稱。

              例如"\Device\00000001"。

       \Device\[設備名],不容易記憶,通常符號鏈接可以理解為設備的別名,更重要的是設備名,只能被內核模式下的其他驅動所識別,而別名可以被用戶模式下的應用程序識別。

        例如c盤,就是名為"c:"的符號鏈接,其真正設備對象是"\Device\HarddiskVolume1”,所以在寫驅動時候,一般我們創建符號鏈接,即使驅動中沒有用到,這也算是一個好的習慣吧。

        驅動中符號鏈接名是這樣寫的 :

        L"\\??\\HelloDDK"  --->\??\HelloDDK

        L"\\DosDevices\\HelloDDK"  --->\DosDevices\HelloDDK

        在應用程序中,符號鏈接名:

        L"\\\\.\\HelloDDK"  -->\\.\HelloDDK

        winobj 和 DeviceTree 可以用來查看這些信息。

 

      2. IoCreateDevice

       作用: 此函數用於創建常規的設備對象.

         NTSTATUS IoCreateDevice(

              IN PDRIVER_OBJECT DriverObject,

              IN ULONG DeviceExtensionSize,

              IN PUNICODE_STRING DeviceNameOPTIONAL,

              IN DEVICE_TYPE DeviceType,

              IN ULONG DeviceCharacteristics,

              IN BOOLEAN Exclusive,

              OUT PDEVICE_OBJECT *DeviceObject  );

          參數:

          DriverObject

        一個指向調用該函數的驅動程序對象.每一個驅動程序在它的DriverEntry過程里接收一個指向它的驅動程序對象. WDM功能和過濾驅動程序也在他們的AddDevice過程接受一個驅動程序對象的指針.

          DeviceExtensionSize

        指定驅動程序為設備擴展對象而定義的結構體的大小.

          DeviceName

        (可選的參數)指向一個以零結尾的包含 Unicode 字符串的緩沖區,那是這個設備的名稱,該字符串必須是一個完整的設備路徑名. WDM功能驅動程序和過濾驅動程序它們設備對象沒有名字.

          注意:如果設備名未提供(即這個參數是NULL),IoCreateDevice創建的設備對象將不會有一個DACL與之相關聯.

          DeviceType

        指定一個由一個系統定義的FILE_DEVICE_XXX常量,表明了這個設備的類型 (如FILE_DEVICE_DISK,FILE_DEVICE_KEYBOARD等),或供應商定義的一種新型設備的類型.

          DeviceCharacteristics

        指定一個或多個系統定義的常量,連接在一起,提供有關驅動程序的設備其他信息.對於可能的設備特征信息, 見DEVICE_OBJECT結構體.

          Exclusive

        如果指定設備是獨占的,大部分驅動程序設置這個值為FALSE,如果是獨占的話設置為TRUE,非獨占設置為FALSE.

          DeviceObject

        一個指向 DEVICE_OBJECT 結構體指針的指針,這是一個指針的指針,指向的指針用來接收DEVICE_OBJECT結構體的指針.

         返回值:

        IoCreateDevice 函數成功時返回 STATUS_SUCCESS,失敗時返回適當的 NTSTATUS 錯誤代碼.這些錯誤代碼是:

                STATUS_INSUFFICIENT_RESOURCES  //資源不足

                STATUS_OBJECT_NAME_EXISTS   //指定對象名存在

                STATUS_OBJECT_NAME_COLLISION   //對象名有沖突


         調用要求: 包含文件( wdm.h,ntddk.h)

      3. IoCreateSymbolicLink 

         作用:  此函數用於將設備與符號連接進行綁定。

         NTSTATUS   IoCreateSymbolicLink( 

             IN PUNICODE_STRING  SymbolicLinkName,

             IN PUNICODE_STRING  DeviceName  );

          參數:

          SymbolicLinkName

          Unicode 字符串指針,是一個用戶態可見的名稱。

          DeviceName

          Unicode 字符串指針,是驅動程序創建的設備對象名稱。

          返回值:

          如果符號鏈接創建成功 返回 STATUS_SUCCESS 

 

        4. IoDeleteDevice 

         作用:  此函數用於刪除已建立的設備

         VOID   IoDeleteDevice(  

             IN PDEVICE_OBJECT  DeviceObject );

         參數:

         DeviceObject

         PDEVICE_OBJECT類型的指針,指向需要刪除的設備對象

         無返回值

 

        5. IoDeleteSymbolicLink 

         作用:  此函數用於從系統中刪除一個符號鏈接。

         NTSTATUS IoDeleteSymbolicLink(

              IN PUNICODE_STRING SymbolicLinkName );

         參數:

           SymbolicLinkName

         PUNICODE_STRING類型的指針,指向一個緩沖 Unicode 字符串的用戶可見鏈接符號名稱

           返回值:

         如果符號鏈接刪除成功 返回 STATUS_SUCCESS 

 

添加驅動設備例程

     用工具查看驅動及驅動設備

     請看下面的實例:

     首先創建一個頭文件()

#pragma once
#ifdef _cplusplus
extern "C"
{
#endif
#include <ntddk.h>  //引用內庫的 ntddk.h 頭文件,它提供函數、類、結構和各種接口
#ifdef _cplusplus
}
#endif

static PDEVICE_OBJECT MyDevice;  //定義一個全局變量來存儲設備
static UNICODE_STRING SymbolicLinkName;  //定義一個全局變量來存鏈接符

#define INITCODE cod_seg("INIT")  //注意,是INITCODE cod_seg ,名稱寫錯了會藍屏
#pragma INITCODE
NTSTATUS CreateMyDevice(PDRIVER_OBJECT pDriverObject)
{
    NTSTATUS Status; //定義一個局部變量來存儲返回值    
    UNICODE_STRING DeviceName; // 定義一個局部變量來存儲設備名稱

    RtlInitUnicodeString(&DeviceName, L"\\Device\\Myddk.Device"); //實例化設備名稱變量,並給它賦值
    RtlInitUnicodeString(&SymbolicLinkName, L\\Device\\DeviceSymbolLink); //實例化設備鏈接符名稱變量,並給它賦值

    //用 IoCreateDevice API 函數創建設備
    Status = IoCreateDevice(pDriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, &MyDevice);
    if (!NT_SUCCESS(Status)) 
    {
        if (Status==STATUS_INSUFFICIENT_RESOURCES)
        {
            KdPrint(("資源不足"));
        }
        if (Status == STATUS_OBJECT_NAME_EXISTS)
        {
            KdPrint(("指定對象名稱存在"));
        }
        if (Status == STATUS_OBJECT_NAME_COLLISION)
        {
            KdPrint(("對象名沖突"));
        }
        KdPrint(("創建設備失敗!!"));
        return Status;
    }

    MyDevice->Flags |= DO_BUFFERED_IO;  //設置讀寫方式,這里為緩沖區讀寫方式

    //給創建的設備名稱綁定鏈接符
    Status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);
    if (!NT_SUCCESS(Status))
    {
        IoDeleteDevice(MyDevice);
        KdPrint(("設備鏈接符綁定失敗,設備已刪除!!"));
        return Status;
    }
    KdPrint(("設備創建完成,設備已成功加載"));
    return Status;
}
#ifdef _cplusplus
extern "C"
{
#endif
#include "myddk.h"
#ifdef _cplusplus
}
#endif

#define PAGEDCODE cod_seg("PAGE")  //注意,是PAGEDCODE cod_seg ,名稱寫錯了會藍屏
#pragma PAGEDCODE
VOID Unload(PDRIVER_OBJECT pDriverObject);
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pUnicodeString)
{

    CreateMyDevice(pDriverObject);  //調用頭文件里的創建設備的函數,來進行創建設備

    //用 DriverUnload 宏來進行卸載,然后把卸載的過程賦給它,其實DriverUnload已經可以卸載驅動了,
    //這里賦值的作用主要是讓它能打印出消息
    pDriverObject->DriverUnload = Unload; 
    KdPrint(("我的第一個驅動"));
    return STATUS_SUCCESS;
}

//創建一個卸載驅動設備的過程
VOID Unload(PDRIVER_OBJECT pDriverObject)
{    
    //卸載驅動對象,什么對象呢,DeviceObject設備對象,設備對象叫什么呢?叫 MyDevice
    pDriverObject->DeviceObject = MyDevice;

    //用 IoDeleteDevice API函數卸載設備,參數為指定要卸載的設備對象,
    //此參數是已在 myddk.h頭文件中實例化的設備對象
    IoDeleteDevice(MyDevice); 

    //用 IoDeleteSymbolicLink API函數卸載鏈接符,參數為指定要卸載的鏈接符對象,
    //此參數為已在 myddk.h頭文件中實例化的 SymbolicLinkName鏈接符對象
    IoDeleteSymbolicLink(&SymbolicLinkName); 
    KdPrint(("驅動和設備移除成功!!"));
}

 

       然后,在源文件中,來加載與卸載這個設備!

#ifdef _cplusplus
extern "C"
{
#endif
#include "myddk.h"
#ifdef _cplusplus
}
#endif

#define PAGEDCODE cod_seg("PAGE")  //注意,是PAGEDCODE cod_seg ,名稱寫錯了會藍屏
#pragma PAGEDCODE
VOID Unload(PDRIVER_OBJECT pDriverObject);
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pUnicodeString)
{

    CreateMyDevice(pDriverObject);  //調用頭文件里的創建設備的函數,來進行創建設備

    //用 DriverUnload 宏來進行卸載,然后把卸載的過程賦給它,其實DriverUnload已經可以卸載驅動了,
    //這里賦值的作用主要是讓它能打印出消息
    pDriverObject->DriverUnload = Unload; 
    KdPrint(("我的第一個驅動"));
    return STATUS_SUCCESS;
}

//創建一個卸載驅動設備的過程
VOID Unload(PDRIVER_OBJECT pDriverObject)
{      
    KdPrint(("驅動和設備移除成功!!"));
}

然后,我們用 DriverMonitor 工具來把生成的驅動加載到系統中,再用 Winobj 工具來查看設備,看是否創建了我們定義的名稱為“Myddk.Device”的設備。

如圖:

新建位圖圖像

驅動加載成功!

新建位圖圖像

在 Winobj 工具中的 Device 路徑項中可以查看到,我們的設備 Myddk.Device 以創建成功。設備名稱為 Myddk.Device;設備類型為 Device;可是設備的鏈接符沒有??這是為什么,鏈接符也定義初始化也綁定了,如果綁定不成功,會刪除設備啊!!

呵呵呵,我也不明白…這問題暫擱着,我問繼續往下看。

現在我們用 DriverMonitor 工具卸載驅動看看!!

新建位圖圖像

卸載成功!

新建位圖圖像 - 副本

什么情況??怎么設備還在呢??

新建位圖圖像 - 副本 - 副本

但是驅動卻不在了!!

哦,想起來了,我剛剛確實卸載的只是驅動,並沒卸載什么設備…,暈,咋這么古板呢!!

好吧!看來我們還得用代碼向系統內存發送卸載設備的指令!!

見下面一小段代碼:

//創建一個卸載驅動設備的過程
VOID Unload(PDRIVER_OBJECT pDriverObject)
{    
    //卸載驅動對象,什么對象呢,DeviceObject設備對象,設備對象叫什么呢?叫 MyDevice
    pDriverObject->DeviceObject = MyDevice;

    //用 IoDeleteDevice API函數卸載設備,參數為指定要卸載的設備對象,
    //此參數是已在 myddk.h頭文件中實例化的設備對象
    IoDeleteDevice(MyDevice); 

    //用 IoDeleteSymbolicLink API函數卸載鏈接符,參數為指定要卸載的鏈接符對象,
    //此參數為已在 myddk.h頭文件中實例化的 SymbolicLinkName鏈接符對象
    IoDeleteSymbolicLink(&SymbolicLinkName); 
    KdPrint(("驅動和設備移除成功!!"));
}

再次測試…

果不其然,添加卸載設備的API后順利卸載。

新建位圖圖像


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM