Windows驱动—Windows应用程序和Windows驱动通信编程
文章目錄
- 介紹
- 知識前奏
- 內(nèi)核方面編程
- 設備對象和符號鏈接
- 分發(fā)函數(shù)
- 應用方面編程
- 打開設備
- 設備控制請求
- 代碼
- 應用層代碼
- 內(nèi)核層代碼
- 完整工程代碼
- 測試效果
介紹
Windows應用程序(Ring3層)和內(nèi)核驅(qū)動(Ring0層)是運行在Windows權(quán)限的不同級別,簡單來說各有優(yōu)勢。內(nèi)核層權(quán)限較大 能做很多 應用程序辦不到的事情 不直接面向程序使用的用戶,Windows應用程序在Ring3層 直接面向用戶,界面友好。當應用層辦不到的時候就需要借助內(nèi)核層了,所以 win32應用程序和Windows內(nèi)核驅(qū)動通信是有必要的。Windows應用程序和Windows內(nèi)核驅(qū)動程序直接是可以進行雙向通信的,相互都是可以發(fā)送信息的。
本篇博客,將使用一個最簡單的例子來講解 Win32程序和內(nèi)核驅(qū)動程序通信編程。這里把內(nèi)核驅(qū)動當做一個Server,應用程序當做一個客戶端。客戶端向內(nèi)核驅(qū)動發(fā)生一個 小寫字符串,內(nèi)核驅(qū)動將小寫字符串轉(zhuǎn)成大寫 然后回射回來。
知識前奏
內(nèi)核方面編程
設備對象和符號鏈接
如果驅(qū)動需要和應用程序通信,首先必須要生成一個設備對象(Device Object)。設備對象和分發(fā)函數(shù)構(gòu)成了整個內(nèi)核體系的基本框架。設備對象用來暴露給應用層,應用層可以像操作文件一樣操作它。用于和應用程序通信的設備往往用來"控制"這個內(nèi)核驅(qū)動,所以往往稱之為**“控制設備對象”**(Control Device Object,CDO)。生成設備對象使用IoCreateDevice函數(shù),原型如下:
/*return STATUS_SUCCESS成功 */ NTSTATUS IoCreateDevice(// 可直接從DriverEntry參數(shù)中獲得IN PDRIVER_OBJECT DriverObject,// 表示設備擴展大小(應用設備擴展)會專門講述IN ULONG DeviceExtensionSize,// 設備名IN PUNICODE_STRING DeviceName OPTIONAL,// 設備類型,Windows已經(jīng)規(guī)定了一系列設備類型IN DEVICE_TYPE DeviceType,// 表示一組設備屬性IN ULONG DeviceCharacteristics,// 表示是否一個獨占設備,設置獨占,這個設備將在同一個時刻只能被打開一個句柄。一般都不會設置為獨占設備IN BOOLEAN Exclusive,// 返回結(jié)果OUT PDEVICE_OBJECT *DeviceObject);控制設備需要有一個名字,這樣才會被暴露出來,供其他程序打開與之通信。設備的名字可以在IoCreateDevice或IoCreateDeviceSecure時指定。但是,應用層是無法直接通過設備的名字來打開對象的,必須建立一個暴露給應用層的符號鏈接。符號鏈接是記錄一個字符串對應到另一個字符串的簡單結(jié)構(gòu)。函數(shù)原型IoCreateSymbolicLink如下:
NTSTATUS IoCreateSymbolicLink(// 符號鏈接名,如果該符號鏈接名存在 則創(chuàng)建不成功IN PUNICODE_STRING SymbolicLinkName,// 設備名IN PUNICODE_STRING DeviceName);控制設備和符號鏈接的刪除很簡單,一一對應,IoDeleteDevice、IoDeleteSymbolicLink
分發(fā)函數(shù)
分發(fā)函數(shù)是一組用來處理發(fā)送給設備對象(當然包括控制設備)的請求的函數(shù)。這些函數(shù)當然由內(nèi)核驅(qū)動的開發(fā)者編寫,以便處理這些請求并返回給Windows。分發(fā)函數(shù)是設置在驅(qū)動對象上的。Windows的IO管理器在收到請求時,會根據(jù)請求發(fā)送的目標,也就是一個設備對象,來調(diào)用這個設備對象所從屬的驅(qū)動對象上的分發(fā)函數(shù)。最簡單的3種請求:
- 打開(Create):在試圖訪問一個設備對象之前,必須先用打開請求打開它。只有得到成功的返回,才可以發(fā)送其他的請求。
- 關(guān)閉(Close):在結(jié)束訪問一個設備對象之后,發(fā)送關(guān)閉請求將它關(guān)閉。關(guān)閉之后,就必須再次打開才能訪問。
- 設備控制(Device Control):設備控制請求是一種即可以用來輸入(應用到內(nèi)核),又可以用來輸出(從內(nèi)核到應用)的請求。
分發(fā)函數(shù)原型:
NTSTATUS MyDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp) {// 在分發(fā)函數(shù)內(nèi)部進行 請求的處理。一般有如下幾個步驟// 第1步,判斷請求是否發(fā)送給該驅(qū)動的設備對象// 第2步,獲取請求的當前棧空間(空間中含有請求相關(guān)的信息)// 第3步,獲取請求的功能號,不同的功能號做不同的處理(這里就可以對輸入輸出做操作了)。// 第4步,結(jié)束請求 }應用方面編程
打開設備
在應用程序中 打開驅(qū)動中創(chuàng)建的設備。和打開文件沒什么區(qū)別,使用 CreateFile即可,要注意文件的路徑。
/* 文件的路徑就是符號鏈接的路徑,但是符號鏈接的路徑在應用看來,是以"\\.\"開頭的。注意,這些"\"在C語言中要使用"\\"來轉(zhuǎn)義 */ #define MY_DEVOBJ_SYB_NAME (L"\\\\.\\lcx10000")HANDLE deviceHandle = CreateFile(MY_DEVOBJ_SYB_NAME,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM,0);設備控制請求
設備控制請求即可以進行輸入也可進行輸出。每個設備控制請求都會有一個功能號,用來區(qū)分不同的設備控制請求。這個功能號體現(xiàn)在CTL_CODE中。
CTL_CODE是一個宏,是SDK里頭文件提供的。我們要做的是直接利用這個宏來生成一個自己的設備控制請求功能號。CTL_CODE有4個參數(shù)。
- 參數(shù)1,設備類型,這里的控制設備與任何硬件都沒有關(guān)系,所以直接定義為未知類型FILE_DEVICE_UNKNOWN。
- 參數(shù)2,生成這個功能號的核心數(shù)字,這個數(shù)字直接用來和其他參數(shù)“合成”功能號?0x0~0x7ff被微軟預留了,同時也不能超過0xfff。如果要定義超過一個的功能號,那么不同的功能號就靠這個數(shù)字進行區(qū)分。
- 參數(shù)3,METHOD_BUFFERED是說用緩存方式。用緩存方式,輸入輸出緩存會在用戶和內(nèi)核之間拷貝。這是比較簡單和安全的一種方式。
- 參數(shù)4,是這個操作需要的權(quán)限。當需要將數(shù)據(jù)發(fā)送到設備時,相當于往設備寫入數(shù)據(jù),所以標志為擁有寫數(shù)據(jù)權(quán)限(FILE_WRITE_DATA)。
設備控制請求函數(shù)原型:
BOOL DeviceIoControl(// 設備句柄HANDLE hDevice,// Control codeDWORD dwIoControlCode,// 輸入緩沖區(qū)LPVOID lpInBuffer,// 輸入緩沖區(qū)長度DWORD nInBufferSize,// 輸出緩沖區(qū)LPVOID lpOutBuffer,// 輸出緩沖區(qū)長度DWORD nOutBufferSize,// 接受到的有效數(shù)據(jù)長度LPDWORD lpBytesReturned,// 指向OVERLAPPED結(jié)構(gòu)體的指針LPOVERLAPPED lpOverlapped );#define CTL_CODE( DeviceType, Function, Method, Access )代碼
代碼中有很詳細的注釋。
應用層代碼
這里使用簡單MFC界面操作代碼
// 設備名對應的符號鏈接名,用于暴露給應用層。符號鏈接在應用看來是在\\.\ 的 #define MY_DEVOBJ_SYB_NAME (L"\\\\.\\lcx10000")//CTL_CODE創(chuàng)建控制碼 #define IOCTL_SEND_AND_REC_STR\CTL_CODE(FILE_DEVICE_UNKNOWN\, 0x801, METHOD_BUFFERED,\FILE_READ_DATA | FILE_WRITE_DATA)void CMy02_Win32ToDriverDlg::OnBnClickedSendtodriver() {UpdateData(TRUE);int len = m_uiSendToDriverString.GetLength();char* pInStr = new char[len+1];char *pOutStr = new char[len+1];memset(pInStr,0,len+1);memset(pOutStr,0,len+1);for(int i = 0; i < len ; i++){pInStr[i] = m_uiSendToDriverString.GetAt(i);}//char* pStr = (char*)m_uiSendToDriverString.GetBuffer(0);int ret_len = 0;// 1.打開驅(qū)動設備HANDLE deviceHandle = CreateFile(MY_DEVOBJ_SYB_NAME,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM,0);if(deviceHandle == INVALID_HANDLE_VALUE){DWORD errCode = ::GetLastError();m_uiDriverResponse = _T("CreateFile 失敗!驅(qū)動未加載");m_uiDriverResponse.AppendFormat(_T(" errCode=%d"),errCode);}else{// 2.向驅(qū)動設備發(fā)送設備控制請求if( DeviceIoControl(deviceHandle,IOCTL_SEND_AND_REC_STR,pInStr,len,pOutStr,len+1,(LPDWORD)&ret_len,NULL)){m_uiDriverResponse = _T("suc.");m_uiDriverResponse.AppendFormat(_T("retLen=%d,response=%s"),ret_len,CString(pOutStr));}}UpdateData(FALSE); }內(nèi)核層代碼
#include <Wdm.h> #include <wdmsec.h>// 全局設備對象 PDEVICE_OBJECT g_devObj = NULL;// 設備名對應的符號鏈接名,用于暴露給應用層。符號鏈接一般都是在\??\路徑下 #define MY_DEVOBJ_SYB_NAME (L"\\??\\lcx10000")// 設備一般都是位于 \Device\這個路徑下的 #define MY_DEVOBJ_NAME (L"\\Device\\lcx10000")// 可用VS自帶的GUID生成器生成 {D1AC1F58-AAC4-45DD-AEC4-A9670DC47B29} static const GUID g_devGUID = { 0xd1ac1f58, 0xaac4, 0x45dd, { 0xae, 0xc4, 0xa9, 0x67, 0xd, 0xc4, 0x7b, 0x29 } };//CTL_CODE創(chuàng)建控制碼 #define IOCTL_SEND_AND_REC_STR\CTL_CODE(FILE_DEVICE_UNKNOWN\, 0x801, METHOD_BUFFERED,\FILE_READ_DATA | FILE_WRITE_DATA)NTSTATUS MyDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp) {NTSTATUS status = STATUS_SUCCESS;ULONG ret_len = 0,i = 0,temp = 0;// 第1步,判斷請求是否發(fā)送給該驅(qū)動的設備對象,如果不是 簡單返回成功if(DeviceObject == g_devObj){// 第2步,獲取請求的當前棧空間(空間中含有請求相關(guān)的信息)PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);// 第3步,獲取請求的功能號,不同的功能號做不同的處理// 打開請求的主功能號是 IRP_MJ_CREATE// 關(guān)閉請求的主功能號是 IRP_MJ_CLOSE// 設備控制請求的主功能號是IRP_MJ_DEVICE_CONTROL// 處理打開和關(guān)閉IRP,可以簡單返回成功即可if( irpStack->MajorFunction == IRP_MJ_CREATE ||irpStack->MajorFunction == IRP_MJ_CLOSE ){status = STATUS_SUCCESS;}// 處理設備控制請求DeviceIoControlelse if( irpStack->MajorFunction == IRP_MJ_DEVICE_CONTROL){// 當前是一個緩存方式的設備控制請求,直接從IRP請求參數(shù)中獲取緩沖區(qū)buffer// 同時 這里輸入緩沖區(qū)、輸出緩沖區(qū)是共享的PVOID buffer = Irp->AssociatedIrp.SystemBuffer;ULONG inLen = irpStack->Parameters.DeviceIoControl.InputBufferLength;ULONG outLen = irpStack->Parameters.DeviceIoControl.OutputBufferLength;//KdBreakPoint();// 斷點設置,使用windbg調(diào)試時可以打開//控制碼由這個宏函數(shù)CTL_CODE創(chuàng)建if(irpStack->Parameters.DeviceIoControl.IoControlCode== IOCTL_SEND_AND_REC_STR){if(inLen > 0){// 做一個簡單打印DbgPrint("str=%s",(char*)buffer);// 要求輸出緩存要多于輸入緩存if(outLen >= inLen){// 這里轉(zhuǎn)大寫后返回//_strupr((char*)buffer);ret_len = inLen;for(; i <= inLen ; i++){temp = (ULONG)((char*)buffer)[i];if( temp >= 97 && temp <= 122){((char*)buffer)[i] -= 32;}}}else{status = STATUS_INVALID_PARAMETER;}}else{status = STATUS_INVALID_PARAMETER;}}else{// 其他控制碼請求,一律返回非法參數(shù)錯誤。status = STATUS_INVALID_PARAMETER;}}}// KdBreakPoint(); // 斷點設置,使用windbg調(diào)試時可以打開// 第4步,結(jié)束請求// 這個Informatica用來記錄這次返回到底使用了多少輸出空間Irp->IoStatus.Information = ret_len;// 用于記錄這個請求的完成狀態(tài)Irp->IoStatus.Status = status;// 用于結(jié)束這個請求IoCompleteRequest(Irp,IO_NO_INCREMENT);return status; }VOID DriverUnload(__in struct _DRIVER_OBJECT *DriverObject) { UNICODE_STRING DeviceLinkName = RTL_CONSTANT_STRING(MY_DEVOBJ_SYB_NAME);DbgPrint("DriverUnload enter \n");// 刪除符號鏈接IoDeleteSymbolicLink(&DeviceLinkName);// 刪除設備對象IoDeleteDevice(g_devObj); }NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path) {int i;NTSTATUS status;// 設備名UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(MY_DEVOBJ_NAME);UNICODE_STRING SDDLString = RTL_CONSTANT_STRING(L"D:P(A;;GA;;;WD)");UNICODE_STRING DeviceLinkName = RTL_CONSTANT_STRING(MY_DEVOBJ_SYB_NAME);DbgPrint("DriverEntry enter \n");// 如果驅(qū)動需要和應用程序通信,首先必須要生成一個設備對象status = IoCreateDevice(driver,0,&DeviceName,FILE_DEVICE_UNKNOWN,FILE_DEVICE_SECURE_OPEN,FALSE,&g_devObj);// 由于IoCreateDevice函數(shù)生成的設備具有默認的安全,那么必須具有管理員權(quán)限的進程才能打開它// 可用如下函數(shù)替換,不過下面的函數(shù)在 WinXP中無法使用//status = IoCreateDeviceSecure(driver,0,// &DeviceName,FILE_DEVICE_UNKNOWN,// FILE_DEVICE_SECURE_OPEN,FALSE,// &SDDLString,// 設備對象安全設置// &g_devGUID, // 設備guid// &g_devObj);if(!NT_SUCCESS(status)){return status;}// 創(chuàng)建符號鏈接status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceName);if(!NT_SUCCESS(status)){// 一旦失敗,之前生成的設備對象也要刪掉,防止內(nèi)存泄漏IoDeleteDevice(g_devObj);return status;}// 設置驅(qū)動對象的分發(fā)函數(shù),分發(fā)函數(shù)是一組用來處理發(fā)送給設備對象的請求的函數(shù)// 這里driver->MajorFunction是一個數(shù)組,不同的請求可以設置不同的處理函數(shù)// 這里為了方便所有的請求都用一個處理函數(shù),在函數(shù)內(nèi)部去區(qū)分請求,再做不同的邏輯處理//int i; // 變量定義要放在最前面,放這里不行for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++){//driver->MajorFunction[i] = NULL;driver->MajorFunction[i] = MyDispatch;}// 支持動態(tài)卸載。driver->DriverUnload = DriverUnload;return STATUS_SUCCESS; }完整工程代碼
筆者用vs2010進行的編譯 win32應用程序和驅(qū)動程序,在Win XP下測試正常。完整項目工程可以在這里下載。
測試效果
總結(jié)
以上是生活随笔為你收集整理的Windows驱动—Windows应用程序和Windows驱动通信编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 8. OD-输入错误的信息注册未注册的软
- 下一篇: C/C++:Windows编程—Inli