第四章 移植RIOMIP
4.3 替代方案的設計與實作
4.3.3 串流介面驅動程式實作
所謂串流界面驅動程式指的是驅動程式實作了串流界面。而透過實作這個介面可以 讓應用程式可以利用檔案輸入輸出的方式來存取驅動程式。一般我們會用表 4-x 中的五 個系統函式來存取驅動程式。而要實作串流介面程式,我們必須實作表 4-5 中的 10 個 函數。這幾個函式當中,有些是跟表 4-4 有對應關係的,我將再下面一一說明。
表 4-4FILE I/O 函式
54
XXX_Init XXX_Deinit XXX_Open XXX_Close XXX_Read XXX_Write XXX_IOControl XXX_PowerUp XXX_PowerDown XXX_Seek
表 4-5STREAM INTERFACE FUNCTIONS
下面我將講解在我的驅動程式中,所實作到的串流界面函式:
XXX_Init
DWORD XXX_Init(DWORD dwContext);
DWORD XXX_Init(LPCTSTR pContext, LPCVOID lpvBusContext);
當裝置管理員去載入驅動程式的實體時,裝置管理員會去呼叫驅動程式的 Init 函 式。然而 Init 函式的宣告型態有上述兩種,前者為舊的型態,但在新版中仍然支援;
後者為先的型態。兩種型態的第一個參數,通常傳入的是一個字串指標,指向裝置管理 員在 Registry 裡為驅動程式所建立的主動機碼(active key),所有被裝置管理員所載 入的驅動程式都會在 Registry 有此機碼。但是如果驅動程式是被應用程式手動載入,
這個參數就不一定是主動機字串的指標。
在 Registry 裡面,存在 HKEY_LOCAL_MACHINE\Drivers\BuiltIn
底下的驅動程式,在系統啟動時都會被裝置管理員自動載入;此外應用程式可以呼 叫下面兩個系統函式,來手動載入驅動程式。
55
HANDLE RegisterDevice(
HANDLE ActivateDeviceEx(
LPCWSTR lpszDevKey, LPCVOID lpRegEnts, DWORD cRegEnts, LPVOID lpvParam );
如果我們利用 RegisterDevice 來載入我們的驅動程式,Init 的第一個參數就是 RegisterDevice 的第四個參數,所以他可以是任意的數值,端看程式設計師是否需要用 這個資料來傳遞特定的資訊。而如果我們呼叫的是 ActivateDeviceEx,Init 的第一個 參數跟之前所講的一樣,是驅動程式在 Registry 主動碼字串的指標。而如果 Init 函式 所用的是新型態的宣告,則他的第二個參數,就是 ActivateDeviceEx 的第四個參數。
通常我們在這個函式裡面,會去做一些初始化的動作。但由於我的驅動程式並非用 來控制實際的硬體裝置。所以在此函式中,我僅去做記憶體的配置和初始化變數和資料 結構。
如果載入成功,驅動程式必須回傳 0 以外的值給裝置管理員。這個值通常被稱作裝 置本體操作(device context handle),為一指標用來指向包含驅動程式狀態的資料結 構。如果該驅動程式是可多實體(可以被載入超過一次,支援多個硬體裝置實體),驅動 程式本身必須能獨立的維護每一個驅動程式實體本身的狀態,而每次被載入時回傳不一 樣的裝置本體操作。在我的實作當中,由於驅動程式只會被我自己手動載入一次,所以 回傳的值為一固定常數,並無實質作用。
XXX_Open
56
DWORD XXX_Open(
DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode );
當應用程式呼叫 CreateFile 來開啟驅動程式時 ,這個函式就會被呼叫。他的第一 個參數為 Init 函式的回傳值 ,而第二和第三個參數則是對應到 CreateFile 的第二和 第三個參數,分別表示存取的形式 (read/write 或 read-only) 和共享的模式 (FILE_SHARE_READ 或 FILE_SHARE_READ)。
當驅動程式接受了應用程式的開啟後,會回傳一個非 0 的數值,這個數值通常用來 表示開啟本體(open context)。通常為一個指標,指向一資料結構用來存放開啟的狀態 變數。如果驅動程式允許多重存取, 能讓應用程式能同時重複開啟,驅動程式本身就 必須能獨立的維護每一個應用程式開啟本體的狀態,而每次被開啟時回傳不一樣的開啟 本體。而如果驅動程式本身只被允許開啟一次,則通常我們會直接回傳函式接收到的第 一個參數,就是 Init 回傳的裝置本體。在我實作的驅動程式裡,就是這樣的作法。
XXX_DeInit
BOOL XXX_Deinit( DWORD hDeviceContext );
當驅動程式被卸載 (Unload) 時,Deinit 函式就會被呼叫。函式的唯一傳入參數為 裝置本體,讓 Deinit 函式用來辨別是哪一個驅動程式的實體將被卸載。
在這個函式中,通常是做一些資源的釋放和關閉相對應的硬體。因為我的驅動程式 必沒有實質的硬體裝置,所以慬做記憶體的釋放動作。當卸載成功後,則回傳 TRUE 值;
如果失敗或有任何錯誤,則回傳 FLASE 值。
XXX_Close
BOOL XXX_Close( DWORD hOpenContext );
57
當應用程式呼叫 ClsoeHandle 來關閉它之前所開啟的驅動程式時,Close 函式就會 被呼叫。傳入的參數為之前驅動程式的 Open 函式回傳的開啟本體,用來識別關閉的是 哪一次的開啟動作。
在這個函式中,通常會釋放用來存放開啟狀態的開啟本體的記憶體空間,但由於我 的驅動曾是不支援多重開啟,所以只重設驅動程式狀態到未被開啟的狀態。
XXX_READ
DWORD XXX_Read(
DWORD hOpenContext, LPVOID pBuffer,
XXX_WRITE
DWORD XXX_Write(
DWORD hOpenContext, LPCVOID pBuffer,
XXX_IOControl
58
BOOL XXX_IOControl(
DWORD hOpenContext, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut );
當應用程式呼叫 DeviceIOControl 系統函式來存取驅動程式時,驅動程式的 IOControl 函式就會被呼叫。這是驅動程式和應用程式間一個非常實用而且常用的介 面,許多驅動程式常利用這個介面來和應用程式溝通,甚至利用此介面來作讀寫的動 作,而不用 Read 和 Write。我實作的非同步輸入輸出動作也是實作在介面上。IOControl 的第一個參數為 Open 函式所傳的開啟本體;而第二個參數為裝置定義的一個控指碼,
用來識別控制動作本身。這個控指碼是程式設計師可以自行定義的。我們可以針對不同 的控制動作,定義相對應的控制碼。
59
圖 4-21 定義I/O 控制碼
例如,圖 4-21 中為我驅動程式中定義的控制碼。而第三和第四個參數分別是輸入 緩衝區的指標和輸入緩衝區大小。輸入緩衝區通常是存放著應用程式要傳遞給驅動程式 的資料。第四和第五個參數分別是輸出緩衝區的指標和輸出緩衝區大小。輸出緩衝區通 常是永來讓驅動程式寫回資料給應用程式用的。最後一個參數則是最後驅動程式寫道輸 出緩衝區的資料位元個數。
60