现在大家对vmm和vxd有了一定的了解,接下来我们来看一看如何编写vxd代码。首先,你必须具备Windows 95/98 Device Driver Development Kit。Window95 ddk只有MSDN 订户才能拿到,但Windows98 ddk却可以免费从Microsoft公司取得。尽管Windows 98 ddk是面向WDM的,但你还是可以用它来开发VxD程序。你可以从 http://www.microsoft.com/hwdev/ddk/install98ddk.html?下载Window98 ddk。
你可以下载整个软件包(大约30M),也可以只下载你感兴趣的部分。如果你没有下载整个软件包,那么别忘了下载other.exe 里面的Window95 ddk documentation。Windows98 ddk 包含了6.11d版的MASM。你需要把它升级为最新版。
Window9x DDK包含了一些Masm32包所不具有的重要库文件。
在LE文件中,代码和数据被存放在几类运行属性不同的段中。以下是一些可用的段类。
并不意味着你的VxD程序必须包含以上所有的段,你可以选择你的VxD程序需要的段。例如,如果你的VxD程序不进行实模式初始化,那么就不必包含RCODE段。
大多数时候,你要用到LCODE, PCODE和PDATA段。作为一个VxD程序编写者,为你的代码和数据选择合适的段取决于你自己的判断。总的来说,你应该尽可能多的使用PCODE和PDATA因为这样VMM就可以在需要的时候把段调入调出内存。另外,硬件中断程序及其所用到的服务必须放在 LCODE段里。
你不能直接地使用这些段类,你要用这些段类来定义段,这些段的定义被存放在模块定义文件(.def)中。下面是一个标准的模块定义文件:
VXD FIRSTVXD SEGMENTS _LPTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _LTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _LDATA CLASS 'LCODE' PRELOAD NONDISCARDABLE _TEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _DATA CLASS 'LCODE' PRELOAD NONDISCARDABLE CONST CLASS 'LCODE' PRELOAD NONDISCARDABLE _TLS CLASS 'LCODE' PRELOAD NONDISCARDABLE _BSS CLASS 'LCODE' PRELOAD NONDISCARDABLE _LMGTABLE CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL _LMSGDATA CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL _IMSGTABLE CLASS 'MCODE' PRELOAD DISCARDABLE IOPL _IMSGDATA CLASS 'MCODE' PRELOAD DISCARDABLE IOPL _ITEXT CLASS 'ICODE' DISCARDABLE _IDATA CLASS 'ICODE' DISCARDABLE _PTEXT CLASS 'PCODE' NONDISCARDABLE _PMSGTABLE CLASS 'MCODE' NONDISCARDABLE IOPL _PMSGDATA CLASS 'MCODE' NONDISCARDABLE IOPL _PDATA CLASS 'PDATA' NONDISCARDABLE SHARED _STEXT CLASS 'SCODE' RESIDENT _SDATA CLASS 'SCODE' RESIDENT _DBOSTART CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _DBOCODE CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _DBODATA CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _16ICODE CLASS '16ICODE' PRELOAD DISCARDABLE _RCODE CLASS 'RCODE' EXPORTS FIRSTVXD_DDB @1第一个声明定义了VxD的名称,一个VxD的名称必须是全部大写的,我曾经试过用小写,结果VxD除了把自己载入内存外什么也不干。
_LTEXT | VxD_LOCKED_CODE_SEG |
_PTEXT | VxD_PAGEABLE_CODE_SEG |
_DBOCODE | VxD_DEBUG_ONLY_CODE_SEG |
_ITEXT | VxD_INIT_CODE_SEG |
_LDATA | VxD_LOCKED_DATA_SEG |
_IDATA | VxD_IDATA_SEG |
_PDATA | VxD_PAGEABLE_DATA_SEG |
_STEXT | VxD_STATIC_CODE_SEG |
_SDATA | VxD_STATIC_DATA_SEG |
_DBODATA | VxD_DEBUG_ONLY_DATA_SEG |
_16ICODE | VxD_16BIT_INIT_SEG |
_RCODE | VxD_REAL_INIT_SEG |
每个宏都有它相对应的结束宏,例如,如果你要在你的源文件中定义一个_LTEXT段,你应该这样写:
VxD_LOCKED_CODE_SEG(把你的代码写在这里)
VxD_LOCKED_CODE_ENDS
.386p include vmm.inc DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER Begin_control_dispatch FIRSTVXD End_control_dispatch FIRSTVXD end
这段源程序给人的第一印象就是:它并不像一个汇编源程序。那是因为它用了很多宏。让我们来分析一下源程序以便你能很快理解它。
.386p告诉编译器我们要使用包括CPU特权指令的80386指令系统。你也可以使用.486p或者.586p.
include vmm.inc你的每个VxD源程序都必须包含imm.inc,因为它包含了你在源程序里所要用到的宏的定义。你还可以根据需要包含其他的库文件。
DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER正如我们刚才说的,VMM通过VxD程序的设备描述块(DDB)来获取它所需要知道的关于VxD的所有信息。一个设备描述块是一个结构,它包含了许多关于VxD的重要信息,比如VxD的名字,它的设备ID,它的VxD服务函数入口(如果有的话),等等。你可以在imm.inc里查一查这个结构,它被定义为VxD_Desc_Block。你必须在.DEF 文件里导出这个结构。这个结构有22个数据,但是你只用填写其中的几个。然后vmm.inc包含的一个宏会为你初始化并填写这些数据。这个宏叫做DECLARE_VIRTUAL_DEVICE。它的格式如下:
Declare_Virtual_Device Name, MajorVer, MinorVer, CtrlProc, DeviceID, InitOrder, V86Proc, PMProc, RefData
你可以看到:VxD源程序中的标号是不区分大小写的,你可以用大写,小写或者混合起来用都可以。让我们来看一下Declare_virtual_device里的每一个参数。
000000000H DEBUG_INIT_ORDER EQU 000000000H DEBUGCMD_INIT_ORDER EQU 000000000H PERF_INIT_ORDER EQU 000900000H APM_INIT_ORDER EQU 001000000H你可以看到:VMM, DEBUG和 DEBUGCMD是首先加载的VxD程序,然后是PERF和APM。初始化顺序值越低的VxD程序越先被加载。如果你的VxD程序在初始化时需要用到其他VxD程序提供的服务,那么你必须把初始化顺序的值设得比你所要调用的那个VxD程序的大,这样,当你的VxD程序加载时,你所要的VxD就已经在内存中为你准备好了。如果不想去管你的VxD的初始化顺序,就把这个参数填写为UNDEFINED_INIT_ORDER 。
下来是 Begin_Control_Dispatch宏。
Begin_control_dispatch FIRSTVXD End_control_dispatch FIRSTVXD这两个宏定义了设备控制函数,当VxD的控制消息发生时,VMM就调用这个函数。你必须填写设备控制函数名字的前半部分,在本例中,我们用的是 FIRSTVXD。这个宏会在你输入的前半部分后加上_Control作为设备控制函数的名字。这个名字一定要和你在Declare_virtual_device 宏中给参数CtrlProc填的名字一致。设备控制函数总是放在锁定段(VxD_LOCKED_CODE_SEG)内的。上面定义的设备控制函数什么也不干。你需要说明你的VxD程序要响应什么控制消息,以及处理这个消息的函数,你可以用Control_Dispatch宏来实现这一点。
Control_Dispatch message, function例如,如果你的VxD程序只要处理Device_Init 消息,你的设备控制程序要这样写:
Begin_Control_Dispatch FIRSTVXD Control_Dispatch Device_Init, OnDeviceInit End_Control_DispatchFIRSTVXDOnDeviceInit就是要处理Device_Init消息的函数的名字。你可以给你的函数取任何你想取的名字。
ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32 firstvxd.asm
-coff 表明COFF数据格式
-c
只汇编,不调用连接程序来连接,这样我们就可以在调用link.exe的时候使用跟多的参数。
-Cx 保存公共,外部标记。
-D<text> 定义一个文本宏,例如,-DBLD_COFF定义了一个文本宏BLD_COFF,这个宏用来作为编译的条件。如果你有兴趣,你可以在库文件中查找BLD_COFF,自己亲眼看看它对汇编过程起什么作用。上面的命令行定义了三个文本宏:BLD_COFF,IS_32和MASM6。如果你对C编程熟悉的话,你会知道这些定义相当于完成以下功能:
#define BLD_COFF #define IS_32 #define MASM6
link -vxd -def:firstvxd.def firstvxd.obj
-vxd 表明我们要根据obj文件来生成一个VxD文件。
-def:<.DEF file> 指定该VxD文件的模式定义文件。
我觉得用makefile很方便,如果你不喜欢用makefile,你也可以创建批处理文件来自动完成编译过程。我的makefile如下:
NAME=firstvxd $(NAME).vxd:$(NAME).obj link -vxd -def:$(NAME).def $(NAME).obj $(NAME).obj:$(NAME).asm ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32 $(NAME).asm