虛擬裝置驅動程式 (VxD) 是 Windows 9x 核心架構的關鍵,運行於 Ring 0,負責硬體虛擬化、系統服務與多任務仲裁。使用 Vireo VToolsD 輔助 C++ 開發 VxD 時,理解其與外界 (應用程式、其他 VxD、DOS/Win16/Win32 程式) 通訊的五種標準介面至關重要。本文系統整理這五大介面的建立方式、呼叫慣例與程式碼範例。
| 介面類型 | 呼叫端 | VToolsD 精靈頁籤 | 核心機制 | 適用場景 |
|---|---|---|---|---|
| 1. Win32 應用程式介面 (W32_DeviceIoControl) | 32-bit 應用程式 (VC/BCB/Delphi) | API 頁 → Protected Mode (DPMI) | CreateFile + DeviceIoControl → VMM 發送 W32_DEVICEIOCONTROL 訊息 | 現代 Win32 應用與 VxD 雙向資料交換、控制 (首選) |
| 2. Real/V86 模式介面 (標準應用程式入口點) | 16-bit DOS/Win16/Win3.1 應用 (組合語言) | API 頁 → Standard Application Entry Points → Real/V86 Mode | INT 2Fh, AX=1684h 取得入口 → CALL FAR 進入 V86_API_Entry | 相容舊版 DOS/Win16 程式呼叫 VxD |
| 3. 16-bit 保護模式介面 (標準應用程式入口點) | 16-bit Windows 應用 (BC 3.x/Win16) | API 頁 → Standard Application Entry Points → Protected Mode | INT 2Fh, AX=1684h 取得選擇子:偏移 → CALL FAR 進入 PM_API_Entry | 16-bit Windows 應用呼叫 VxD |
| 4. DPMI Real/V86 廠商專用介面 | DPMI 客戶端 (DOS 延伸器/Win32s) | API 頁 → Vendor Specific Application Entry Points → Real/V86 Mode | INT 2Fh, AX=168Ah + 廠商 ID 字串 → 進入 My_V86VendorEntry::handler | 不想佔用標準 ID、需唯一字串識別的 VxD |
| 5. DPMI 保護模式 廠商專用介面 | DPMI 保護模式客戶端 | API 頁 → Vendor Specific Application Entry Points → Protected Mode | INT 2Fh, AX=168Ah + 廠商 ID 字串 → 進入 My_ProtVendorEntry::handler | 32-bit DPMI 客戶端呼叫 VxD |
| 6. VxD 服務 (VxD-to-VxD) | 其他 VxD | VxD Services 頁 | 宣告服務表 + VxDCall/VxDJmp 巨集 → 透過 INT 20h 動態連結 | VxD 間共享核心功能、資料結構、硬體存取 |
HANDLE h = CreateFile("\\\\.\\MYVXD.VXD", 0, 0, NULL, 0, FILE_FLAG_DELETE_ON_CLOSE, NULL);
// 同步呼叫
char inBuf[64], outBuf[64]; DWORD bytesRet;
BOOL ok = DeviceIoControl(h, MY_IOCTL_CODE, inBuf, sizeof(inBuf), outBuf, sizeof(outBuf), &bytesRet, NULL);
// 非同步呼叫 (Overlapped)
OVERLAPPED ov = {0}; ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
DeviceIoControl(h, MY_IOCTL_CODE, inBuf, sizeof(inBuf), outBuf, sizeof(outBuf), &bytesRet, &ov);
GetOverlappedResult(h, &ov, &bytesRet, TRUE); // 等待完成
CloseHandle(h);
// 1. QuickVxD 勾選 Windows 95 Control Messages → W32_DEVICEIOCONTROL
// 2. 實作 OnW32DeviceIoControl
DWORD MyDevice::OnW32DeviceIoControl(PIOCTLPARAMS p) {
switch (p->dioc_IOCtlCode) {
case MY_IOCTL_CODE:
// p->dioc_InBuf : 輸入緩衝區 (使用者模式位址)
// p->dioc_OutBuf : 輸出緩衝區
// p->dioc_CBInBuf / CBOutBuf : 緩衝區大小
// 同步模式:直接複製資料
memcpy(p->dioc_OutBuf, "Response from VxD", 18);
p->dioc_Olp->O_InternalHigh = 18; // 回傳位元組數
return 0; // 成功 -> DeviceIoControl 回傳 TRUE
case 0: case -1: // VMM 動態載入/卸載通知
return 0;
default:
return 0; // 未知代碼也回傳 0 避免應用程式掛住
}
}
// 非同步模式處理 (p->lpoOverlapped != 0):
// 1. _LinPageLock 鎖定使用者緩衝區頁面
// 2. 處理完成後,填入 p->dioc_OutBuf 與 p->lpoOverlapped->O_InternalHigh
// 3. VWIN32_DIOCCompletionRoutine(p->lpoOverlapped->O_Internal) 喚醒應用程式
需在 Device Parameters 指定 Device ID (如 3180h,可向 Microsoft 申請或用 Vireo 測試 ID)。
EntryPoint dd ?
mov ax, 1684h
mov bx, 3180h ; Device ID
int 2Fh ; ES:DI 返回入口點
mov ax, es
or ax, di
jz Error
mov word ptr [EntryPoint], di
mov word ptr [EntryPoint+2], es
mov ah, MY_FUNC_CODE
call [EntryPoint] ; 進入 VxD V86_API_Entry
Error: ...
// V86_API_Entry / PM_API_Entry 簽名相同
void MyDevice::V86_API_Entry(VMHANDLE hVM, CLIENT_STRUCT *p) {
if (p->CBRS.Client_AH == MY_FUNC_CODE) {
// 處理邏輯,結果回傳於 Client_AL/Client_AX/Client_EAX
p->CBRS.Client_AL = 0; // 成功
}
}
不需 Device ID,改用唯一廠商 ID 字串 (如 "MYVXD") 識別。
VendorID db 'MYVXD',0
EntryPoint dd ?
mov ax, 168Ah
lea si, VendorID ; DS:SI 指向 ID 字串
int 2Fh ; AL=0 成功,ES:DI 入口點
cmp al, 0
jne Error
mov word ptr [EntryPoint], di
mov word ptr [EntryPoint+2], es
call [EntryPoint] ; 進入 My_V86VendorEntry::handler
提供最高效、無模式切換開銷的核心層 API 共享。
// MYVXD.H (QuickVxD VxD Services 頁產生)
#define MYVXD_DeviceID 0x3180
#define MYVXD_Minus_Service 1
_declspec(naked) DWORD _cdecl MYVXD_Minus(DWORD a, DWORD b); // 宣告
// MYVXD.CPP 實作
DWORD _cdecl MyDevice::MYVXD_Minus(DWORD a, DWORD b) {
_asm { mov eax, a; sub eax, b; ret } // 直接回傳 a-b
}
// 服務表宣告 (自動產生於 .h)
Begin_Service_Table(MYVXD)
MYVXD_Service(MYVXD_Get_Version, Local)
MYVXD_Service(MYVXD_Minus, Local)
End_Service_Table(MYVXD)
// YOURVXD.MAK 需包含 wrap.cpp
// wrap.cpp (QuickVxD 產生的中介層)
#include "myvxd.h"
DWORD _cdecl MyDevice::MYVXD_Minus(DWORD a, DWORD b) {
VxDJmp(MYVXD_Minus); // 透過 VMM 服務表跳轉
}
// YOURVXD.CPP 使用
#define DEVICE_MAIN
#include "yourvxd.h"
Declare_Virtual_Device(YOUR)
#undef DEVICE_MAIN
#include "myvxd.h" // 必須在 DEVICE_MAIN 區塊外
void SomeFunction() {
PDDB pddb = Get_DDB(0, "MYVXD "); // 名稱補空格至 8 字元
if (!pddb) { // 未載入,嘗試動態載入
PDEVICEINFO pi; PDDB pd;
DWORD r = VxDLDR_LoadDevice("MYVXD.VXD", VxDLDR_INIT_DEVICE, &pi, &pd);
if (r) return; // 載入失敗
}
DWORD result = MyDevice::MYVXD_Minus(100, 28); // 呼叫服務 -> 72
}
W32_DEVICEIOCONTROL 的自訂命令碼使用 2048~4095,避免與系統保留值 (0, -1) 衝突。_LinPageLock 鎖定實體頁面,防止分頁錯誤。VIREO_TEST_ID (3180h)。掌握這六種介面機制,即可根據呼叫端環境 (Win32/Win16/DOS/DPMI/VxD) 選擇最適合的通訊方式,構建強健的 Windows 9x 系統級解決方案。