一、什么是VxD
从多任务操作系统Windows 3 ?1 起, 计算机中的任一物理设备x
可同时被基于Dos 或Windows 的多个进程使用,
这种一对多的关系称为“设备虚拟化”, 各进程通过运行在核心层的VxD(
虚拟x 设备驱动程序) 存取物理设备x。操作系统提供给用户的软件服务也可以用VxD
实现。计算机中的其他资源, 如CPU、内存等也可同时被多个进程使用,
各进程在系统提供的虚拟机(VM) 环境下存取这类资源。
VxD 可由虚拟机管理器(VMM) 在开机时装入核心层( 称静态装入,
即置VxD 于c:\windows\system 目录下, 在c:\windows\system.ini 文件中, 对节[386Enh]
加一行“device= 此VxD 文件名"), 或由应用程序实时装入(
称动态装入), 而后, 各进程便可存取锁定在内存中的VxD 数据区,
以实时控制VxD 的行为,VxD
的内部结构可防止两个进程同时存取其数据区。VxD 通过响应VMM
发给它的事件与外界交互。
Windows 95 中, 基于Dos 的每个进程在单独的VM 中运行( 称在V86
模式下运行), 既可按Dos 单进程方式, 在640k 低内存中运行(
称在实模式下运行), 又可利用多进程环境的优点, 在整个内存中运行(
称在保护模式下运行), 通过95 的DPMI 接口存取内存高端的Windows
图形环境。其他16 位或32 位应用程序均在同一系统VM 中运行。
下面只讨论95 环境下的VxD。
二、VxD 的创建
1. 由汇编语言创建VxD: 需安装微软公司的Win32 SDK 及DDK。
2. 由C 或C ++语言创建VxD: 需安装VC2.0 或BC4.0, 及Vireo Software
公司的VToolsD 软件包。
VToolsD 含3 个实用工具: 可创建VxD 框架的QuickVxD; 可动态装卸VxD 的VxD
Loader; 可显示内存VxD 特性的VxD Viewer。
QuickVxD 含7 个对话页:
(1) Device Parameters 页
包括最多8 个字符的VxD 名, 唯一标识号(ID), 相对其他VxD
的装入顺序(VxD Viewer 可显出某VxD 的装入顺序值Init Order, 若指定新VxD
的装入顺序小于此Init Order, 则新VxD 将在此VxD 前被装入), 实现语言(C
或C ++) 静、动态装入方式等。
(2) VxD Services 页
可被其他VxD 访问的接口( 称为VxD 服务), 要求本VxD 的ID>0,
且未与内存各VxD 的ID 值冲突。
此ID 可向微软公司申请, 也可使用Vireo 公司的VIREO_TEST_ID(3180h)。下称此类ID
为接口ID。
(3) API 页
可被应用程序在实模式/V86 模式下、保护模式下、DPMI 的实模式/V86
模式下、DPMI 的保护模式下访问的接口( 统称应用接口),
前两者要求本VxD 提供接口ID, 后两者只要求本VxD 提供以0
结尾的唯一标识串; 访问前, 先要静态或动态装入本VxD( 第4
者要求静态装入)。
第1、3 者可被普通汇编程序访问, 第2、4 者可被在BC 的Windows 3 ?x(16)
平台上生成的Windows 程序访问。
(4) Control Messages 页
对出现在Windows 3 ?1 及Windows 95 中各消息的响应,
如静态装入时的DTNAMIC_INIT 消息。
(5) Windows95 Control Messages 页
对只出现在Windows 95 中各消息的响应, 如动态装入时的SYS_DYNAMIC_INIT
消息。
(6) 用C ++实现VxD 时的Classes 页
从虚拟设备驱动程序类VDevice 派生的类名( 如MyDevice),
此类的成员函数将接收(4) 及(5) 页中出现的大多数消息。
从VM 实例类VVirtualMachine 派生的类名( 如MyVM),
此类的成员函数将接收贯穿在VM 生命期中的各消息, 如系统VM
初启消息Sys_VM_Init;
从线程实例类VThread 派生的类名( 如MyThread)。此类的成员函数将接收贯穿在线程生命期中的各消息,
如新线程初启消息THREAD_INIT。
(7) Output Files 页
体现以上内容的3 个VxD 文件(.h,.c 或.cpp,.mak)
将被存放的目录位置。
三、C ++语言的VxD
与外界通信的所有接口
我们将简要实现my ?VxD 的应用接口及服务,
它们均作为类的函数成员, 存于my ?h,my ?cpp 中。
1 ?被32 位C 应用程序访问的接口
应用程序先用CreateFile 打开VxD, 后用DeviceIoControl 使VMM 发送W32_DEVICEIOCONTROL
消息给VxD:
HANDLE h;char ibuf[2],obuf[2];
BOOL r;DWORD oc;OVERLAPPED o;
h=CreateFile("\\\\.\\my.vxd",0,0,0,0,
FILE_FLAG_DELETE_ON_CLOSE,0);
// 打开静态my ?VxD, 或动态装入my ?VxD
r=DeviceIoControl(h, 命令码C,ibuf,sizeof(ibuf),
obuf,sizeof(obuf), &oc,NULL 或&o);
/ *与my ?VxD 的事件过程OnW32DeviceIoControl
交换数据, 用ibuf 向VxD 传数据,
用obuf 从VxD 取数据,VxD 传回的数据
总量放在oc 中*/
CloseHandle(h);// 关闭或动态卸下VxD
my ?VxD 应在Windows 95 control messages 页上选
W32_DEVICEIOCONTROL 事件, 在DWORD
MyDevice::OnW32DeviceIoControl(PIOCTLPARAMS p)
事件过程中写:
switch(p ->dioc_IOCtlCode){
case 命令码C:
用p 指向的IOCTLPARAMS 结构,
与应用程序交换数据;
if ( 成功) return(0); / *使DeviceIoControl 的
返回值r 为TRUE */
else return(1);
default:
return(0);
}
以上做法要求VxD 立即交换数据( 同步通讯), 值FILE_FLAG_DELETE_ON_CLOSE
指明CloseHandle 将不在内存中保留引用记数为0 的VxD。
VxD 也可延迟交换数据, 此时, 应用程序先传值FILE_FLAG_DELETE_ON_CLOSE|FILE_FLAG_OVERLAPPED
到CreateFile, 用o ?hEvent=CreateEvent(0,TRUE,0,NULL) 创建事件, 再传o
的地址到DeviceIoControl, 然后用GetOverlappedResult(h, &o, &oc,TRUE) 在o
上睡眠。
此时,p ->lpoOverlapped 一定大于0,VxD 可用VMM 服务_LinPageLock,
按页上锁p ->dioc_InBuf 指向的应用程序ibuf 区,p ->dioc_OutBuf
指向的obuf 区,p ->lpoOverlapped 指向的o 结构。要交换数据时,
可置数据及数据总量到p ->dioc_OutBuf 及p ->lpoOverlapped ->O_InternalHigh,
然后调用VMM 服务VWIN32_DIOCCompletionRoutine(p ->lpoOverlapped ->O_Internal)
唤醒应用程序。
VMM 动态装卸VxD 时, 以命令码0 及-1 发送W32_DEVICEIOCONTROL 消息给VxD,
故Vireo 公司建议命令码C 取[2048,4095]。
2 . 被Real/V86 模式下16 位应用程序访问的接口
my-VxD 先要指定接口ID( 如3180h), 再在API 页上选Standard Application
Entry Points 框中的Real/V86 Mode 标签, 即可生成MyDevice::V86_API_Entry 入口,
访问它的汇编程序是:
entry dd ?
mov ax,1684h ; 功能号
mov bx,3180h ; 接口ID
int 2fh ;
取入口的段/ 偏移到es/di,
成功时,di 及es 返回非零值
mov ax,es
or ax,di
jz L0
mov word ptr [entry],di
mov word ptr [entry +2],es
mov ah, 码C
call [entry]
L0: 错误处理
MyDevice::V86_API_Entry
(VMHANDLE hVM,CLIENT_STRUCT *p) 入口可以是:
if (p ->CBRS.Client_AH== 码C) p ->CBRS.Client_AL=0;
3.?被保护模式下16 位应用程序访问的接口
与第2 条类似, 但选Protected Mode 标签, 即可生成MyDevice::PM_API_Entry
入口, 访问它的程序是:
int PASCAL WinMain(HANDLE h1,
HANDLE h0,LPSTR lpCmdLine,int nCmdShow){
FARPROC entry; //32 位
_asm{
mov ax,1684h
mov bx,3180h
int 2fh ; 取入口的选择符/ 偏移到es/di,
成功时,di 及es 返回非零值
mov ax,es
or ax,di
jz L0
mov word ptr [entry],di
mov word ptr [entry +2],es
mov ah, 码C
call [entry]
}
对PM_API_Entry 的处理如第2 条。
4 . 被DPMI 的实模式/V86 模式下16 位应用程序访问的接口
与第2 条类似, 但在API 页上选Vendor Specific Application Entry Points
中的Real/V86 Mode 标签, 然后在Vendor ID String 中输入唯一标识串my,
即可生成My_V86VendorEntry::handler 入口, 访问它的程序是:
str db 'my',0 ;VxD 的唯一标识串
entry dd ?
mov ax,168Ah ; 功能号
lea si,str ; 要求ds/si 值是str 的段值/ 偏移值
int 2Fh ; 取入口的段/ 偏移到es/di, 成功时,al
返回0
cmp al,0
jne L0
mov word ptr [entry],di
mov word ptr [entry +2],es
...
call [entry]
对handler 的处理如第二条。
5.?被DPMI 的保护模式下16 位应用程序访问的接口
与第4 条类似, 但选Protected Mode 标签, 即可生成My_ProtVendorEntry::handler
入口, 访问它的程序是:
int PASCAL WinMain(HANDLE h1,HANDLE h0,
LPSTR lpCmdLine,int nCmdShow){
char *id="my";
FARPROC entry;
_asm{
mov ax,168Ah
mov si,id
int 2Fh ; 取入口的选择符/ 偏移到es/di
cmp al,0
...
}
}
对handler 的处理如第2 条。
6 .?可被其他VxD 访问的接口
若your.VxD 欲调my.VxD 的做两数相减的minus 接口, 需在my.VxD 的VxD
service 页上输入原型
DWORD _cdecl minus(DWORD i,DWORD j), 再在MyDevice::minus 中, 写return(i -j);
your.mak 中, 需处理中间文件wrap.cpp:
OBJECTS=your.OBJ wrap.obj
...
wrap.OBJ:wrap.cpp my.h
wrap.cpp 中, 对带参数的VxD 服务, 需用VMM 宏指令VxDJmp 转入,
各参数进入wrap 时, 已按C 的调用约定入栈; 对不带参数VxD 服务,
可调用VMM 宏指令VxDCall( 接口名):
#include "my.h"
DWORD _cdecl MyDevice::minus(DWORD i,DWORD j){
VxDJmp(minus);
}
your.cpp 的某一函数f, 可用VMM 服务Get_DDB, 查my.VxD 是否已装入,
若未装入, 则用VxDLDR 服务
VxDLDR_LoadDevice 将其装入:
#define DEVICE_MAIN
#include "your.h"
Declare_Virtual_Device(YOUR)
#undef DEVICE_MAIN
#include "my.h" // 此行需在DEVICE_MAIN 外
VOID f(){
PDEVICEINFO pinfo;
PDDB pddb;
DWORD r;
pddb=Get_DDB(0,"MY ");
// 用空格补全长度小于8 的VxD 名
if (pddb==0) {// 未装入
r=VxDLDR_LoadDevice("my.VxD",
VxDLDR_INIT_DEVICE, &pinfo, &pddb);
if (r!=0) //VxDLDR_LoadDevice
未能成功装入my.VxD
return;
}
MyDevice::minus( 值1, 值2);
}