浅谈 VxD 回调 Win32 应用程序
作者:赵世平
Windows 95 / 98 的虚拟外壳设备( Virtual Shell Device )提供了 VxD 直接回调 Win16 应用程序函数的 VxD 服务,但是没有提供 VxD 直接回调 Win32 应用程序的 VxD 服务。不过 Windows 95 / 98 还是提供了两种 VxD 回调 Win32 应用程序的方法。方法之一是使用 VWIN32.VXD 提供的“异步过程调用( APC )”功能。 Win32 应用程序首先动态加载 VxD ,并使用 DeviceIoControl 函数将回调函数的地址传递给 VxD ,然后 Win32 应用程序调用 SleepEx / WaitForMultipleObjectsEx / WaitForSingleObjectEx 函数,将 Win32 应用程序自身置为“挂起”状态,这时 VxD 可以通过 VWIN32.VXD 提供的 _VWIN32_QueueUserApc 服务调用 Win32 应用程序的回调函数。该方法较简单,目前大多数回调 Win32 应用程序的 VxD 均使用该方法[例如瑞星杀毒软件( RAV )的实时监控 VxD ( RAV_IO.VXD )即使用该方法回调 Win32 查毒/杀毒程序],《 Windows 95 System Programming 4 》一书的配套光盘中有一个名为 IFSMONITOR 的实例程序详细讲述了该方法,由于该书的配套光盘在国内很多 FTP 服务器上都可以找到,此处就不再详述了。
方法之二是一种比较灵活的方法,这种方法充分利用了 Win32 应用程序的多线程特点和线程间通信的事件机制。 Win32 应用程序设置两个线程并定义一个事件,主线程负责动态加载/卸载 VxD 和通过 DeviceIoControl 函数与 VxD 通信,辅助线程通过 ResetEvent 函数和 WaitForSingleObject / WaitForSingleObjectEx 函数暂时挂起, VxD 可以通过 VWIN32.VXD 提供的 Win32 事件服务中的 _VWIN32_SetWin32Event 服务唤醒辅助线程,从而间接实现 VxD 回调 Win32 应用程序。由于 VWIN32.VXD 提供了与 Win32 API 几乎完全对应的 Win32 事件服务,所以该方法极其灵活,甚至可以通过定义两个事件实现 VxD 在回调 Win32 应用程序时与 Win32 应用程序完全同步。
笔者为了验证上述方法,编写了一个动态加载/卸载 VxD 的 Win32 应用程序和一个回调 Win32 应用程序的 VxD 。其中 Win32 应用程序用 Delphi 5.0 编写,选用 Delphi 5.0 的原因是 Delphi 5.0 实现多线程非常容易,而且不必将大量代码用在 Win32 应用程序界面上; VxD 使用 VToolsD 2.03 编写,是一个挂接实时钟中断( IRQ 8 )的 VxD ,该 VxD 参照 VToolsD 2.03 中的 CHIME 实例程序编写。程序代码如下:
Win32 应用程序工程文件( TMR_TEST.DPR ):
program TMR_TEST;
uses
Forms,
TT_MAIN in 'TT_MAIN.pas' {TimerTestMain},
TMR_CLBK in 'TMR_CLBK.pas';
{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TTimerTestMain, TimerTestMain);
Application.Run;
end.
Win32 应用程序主窗体/主线程( TT_MAIN.PAS ):
unit TT_MAIN;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Menus, SyncObjs, TMR_CLBK, StdCtrls;
type
TOpenVxDHandle=function(hSource:THandle):THandle;stdcall;
TTimerTestMain = class(TForm)
MainMenu1: TMainMenu;
File1: TMenuItem;
Exit1: TMenuItem;
Callback1: TMenuItem;
Start1: TMenuItem;
Label1: TLabel;
procedure FormShow(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Exit1Click(Sender: TObject);
procedure Start1Click(Sender: TObject);
private
{ Private declarations }
Handle1:THandle;
TimerCallback1:TTimerCallback;
public
{ Public declarations }
end;
const
TIMER_DIOC_SET_VXD_EVENT=101;
var
TimerTestMain: TTimerTestMain;
implementation
{$R *.DFM}
procedure TTimerTestMain.FormShow(Sender: TObject);
begin
Event1:=TEvent.Create(nil,True,False,'');
Handle1:=CreateFile('\\.\TIMER.VXD',0,0,nil,0,FILE_FLAG_DELETE_ON_CLOSE,0);
if Handle1=INVALID_HANDLE_VALUE then MessageBox(Handle,' 不能打开 TIMER.VXD ! ',' 错误 ',MB_ICONSTOP or MB_OK)
end;
procedure TTimerTestMain.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
if Handle1<>INVALID_HANDLE_VALUE then CloseHandle(Handle1)
end;
procedure TTimerTestMain.Exit1Click(Sender: TObject);
begin
Release;
Application.Terminate
end;
procedure TTimerTestMain.Start1Click(Sender: TObject);
var
Kernel32:THandle;
OpenVxDHandle:TOpenVxDHandle;
VxDEvent:THandle;
cb:Longword;
begin
if Handle1<>INVALID_HANDLE_VALUE then
begin
TimerCallback1:=TTimerCallback.Create(True);
TimerCallback1.Resume;
Kernel32:=LoadLibrary('KERNEL32.DLL');
if Kernel32<>0 then
begin
OpenVxDHandle:=GetProcAddress(Kernel32,'OpenVxDHandle');
VxDEvent:=OpenVxDHandle(Event1.Handle);
DeviceIoControl(Handle1,TIMER_DIOC_SET_VXD_EVENT,@VxDEvent,SizeOf(VxDEvent),nil,0,cb,nil);
FreeLibrary(Kernel32)
end
end
end;
end.
Win32 应用程序辅助线程( TMR_CLBK.PAS ):
unit TMR_CLBK;
interface
uses
Classes, SysUtils, Windows, SyncObjs;
type
TTimerCallback = class(TThread)
private
{ Private declarations }
procedure UpdateLabelCaption;
protected
procedure Execute; override;
end;
var
Event1:TEvent;
implementation
uses TT_MAIN;
{ Important: Methods and properties of objects in VCL can only be used in a
method called using Synchronize, for example,
Synchronize(UpdateCaption);
and UpdateCaption could look like,
procedure TTimerCallback.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end; }
{ TTimerCallback }
procedure TTimerCallback.UpdateLabelCaption;
begin
TimerTestMain.Label1.Caption:=Trim(IntToStr(StrToInt(TimerTestMain.Label1.Caption)+1))
end;
procedure TTimerCallback.Execute;
begin
{ Place thread code here }
while not Terminated do
begin
Event1.ResetEvent;
Event1.WaitFor(INFINITE);
Synchronize(UpdateLabelCaption)
end
end;
end.
VxD 头文件( TIMER.H ):
// TIMER.h - include file for VxD TIMER
#include <vtoolscp.h>
#define DEVICE_CLASS TimerDevice
#define TIMER_DeviceID UNDEFINED_DEVICE_ID
#define TIMER_Init_Order UNDEFINED_INIT_ORDER
#define TIMER_Major 1
#define TIMER_Minor 0
class TimerInterrupt:public VHardwareInt
{
public:
TimerInterrupt(VOID);
~TimerInterrupt();
virtual VOID OnHardwareInt(VMHANDLE hVM);
private:
VOID (*m_callback)();
BYTE m_originalA;
BYTE m_originalB;
};
class TimerEvent:public VGlobalEvent
{
public:
TimerEvent(VOID);
virtual VOID handler(VMHANDLE hVM,CLIENT_STRUCT* pRegs,PVOID refData);
};
BYTE ReadRegister(BYTE reg);
VOID WriteRegister(BYTE reg, BYTE value);
VOID TimerHandler(VOID);
class TimerDevice : public VDevice
{
public:
virtual BOOL OnSysDynamicDeviceInit();
virtual BOOL OnSysDynamicDeviceExit();
virtual DWORD OnW32DeviceIoControl(PIOCTLPARAMS pDIOCParams);
private:
TimerInterrupt *pTimerInterrupt;
};
class TimerVM : public VVirtualMachine
{
public:
TimerVM(VMHANDLE hVM);
};
class TimerThread : public VThread
{
public:
TimerThread(THREADHANDLE hThread);
};
VxD 源程序( TIMER.CPP ):
// TIMER.cpp - main module for VxD TIMER
#define DEVICE_MAIN
#include "timer.h"
Declare_Virtual_Device(TIMER)
#undef DEVICE_MAIN
#include <vsd.h>
#include <vdebug.h>
#define STATREG_A 0xA
#define STATREG_B 0xB
#define STATREG_C 0xC
#define ENABLE_INTERRUPT 0x40
#define TIMER_DIOC_SET_VXD_EVENT 101
static int Count=0;
static HANDLE VxDEvent=NULL;
TimerInterrupt::TimerInterrupt():VHardwareInt(8,0,0,0)
{
m_callback=TimerHandler;
m_originalA=ReadRegister(STATREG_A);
m_originalB=ReadRegister(STATREG_B);
}
TimerInterrupt::~TimerInterrupt()
{
WriteRegister(STATREG_A,m_originalA);
WriteRegister(STATREG_B,m_originalB);
forceDefaultOwner(8,0);
}
VOID TimerInterrupt::OnHardwareInt(VMHANDLE hVM)
{
if(m_callback!=NULL) m_callback();
ReadRegister(STATREG_C);
sendPhysicalEOI();
}
TimerEvent::TimerEvent():VGlobalEvent(NULL)
{
}
VOID TimerEvent::handler(VMHANDLE hVM,CLIENT_STRUCT* pRegs,PVOID refData)
{
dout<<"handler !"<<endl;
if(VxDEvent!=NULL)
{
VWIN32_SetWin32Event(VxDEvent);
}
else
{
VSD_Bell();
}
}
VOID TimerHandler(VOID)
{
Count++;
if(Count==2000)
{
(new TimerEvent)->call();
Count=0;
}
}
BYTE ReadRegister(BYTE reg)
{
BYTE r;
_asm {
pushfd
cli
mov al, reg
or al, 80h
out 70h, al
jmp _1
}
_1:
_asm jmp _2
_2:
_asm {
in al, 71h
mov r, al
jmp _3
}
_3:
_asm jmp _4
_4:
_asm {
xor al, al
out 70h, al
popfd
}
return r;
}
VOID WriteRegister(BYTE reg, BYTE value)
{
_asm {
pushfd
cli
mov al, reg
or al, 80h
out 70h, al
jmp _1
}
_1:
_asm jmp _2
_2:
_asm {
mov al, value
out 71h, al
jmp _3
}
_3:
_asm jmp _4
_4:
_asm {
xor al, al
out 70h, al
popfd
}
}
TimerVM::TimerVM(VMHANDLE hVM) : VVirtualMachine(hVM) {}
TimerThread::TimerThread(THREADHANDLE hThread) : VThread(hThread) {}
BOOL TimerDevice::OnSysDynamicDeviceInit()
{
BYTE statreg;
pTimerInterrupt=new TimerInterrupt();
if(pTimerInterrupt!=NULL)
{
if(!pTimerInterrupt->hook())
{
pTimerInterrupt=NULL;
return FALSE;
}
else
{
statreg=ReadRegister(STATREG_B);
statreg|=ENABLE_INTERRUPT;
WriteRegister(STATREG_B,statreg);
ReadRegister(STATREG_C);
pTimerInterrupt->physicalUnmask();
VEvent::initEvents();
}
}
else
{
return FALSE;
}
return TRUE;
}
BOOL TimerDevice::OnSysDynamicDeviceExit()
{
BYTE statreg;
if(VxDEvent!=NULL)
{
VWIN32_CloseVxDHandle(VxDEvent);
VxDEvent=NULL;
}
statreg=ReadRegister(STATREG_B);
statreg&=~ENABLE_INTERRUPT;
WriteRegister(STATREG_B,statreg);
pTimerInterrupt->physicalMask();
if(pTimerInterrupt!=NULL) delete pTimerInterrupt;
return TRUE;
}
DWORD TimerDevice::OnW32DeviceIoControl(PIOCTLPARAMS pDIOCParams)
{
switch(pDIOCParams->dioc_IOCtlCode)
{
case TIMER_DIOC_SET_VXD_EVENT:
VxDEvent=*((HANDLE *)pDIOCParams->dioc_InBuf);
break;
default:
break;
}
return 0;
}
VxD 的基本原理与 VToolsD 2.03 中的 CHIME 实例程序相同,这里不再详述,只讲述一下 VxD 回调 Win32 应用程序的实现:
Win32 应用程序有一个主线程和一个辅助线程,并定义了一个事件(这里通过 Delphi 5.0 的 TEvent 类定义),主线程负责动态加载/卸载 VxD ,辅助线程通过 ResetEvent 函数和 WaitForSingleObject / WaitForSingleObjectEx 函数暂时挂起(这里通过 Delphi 5.0 的 TEvent 类的方法实现),但是 Win32 应用程序的事件句柄不能直接被 VWIN32.VXD 提供的 Win32 事件服务使用,必须先通过 KERNEL32.DLL 中的未公开 API —— OpenVxDHandle 函数转换成 VxD 事件句柄(该函数在 SDK 文档中未公开,但是在 DDK 文档中公开了),然后通过 DeviceIoControl 函数传递给 VxD 。 Win32 SDK 没有为未公开 API 提供头文件和引入库,但是可以通过 LoadLibrary 函数、 GetProcAddress 函数和 FreeLibrary 函数动态获取 OpenVxDHandle 函数的入口地址,从而进行间接调用(注意! GetProcAddress 函数对于 KERNEL32.DLL 只能通过函数名获取 API 入口地址,不能通过函数序号获取 API 入口地址,原因是 Microsoft 公司在 KERNEL32.DLL 中加入了 Anti-Hacking 代码,而且大部分未公开 API 的引出函数名被去掉了,也就是说通过函数名也不能获取这些未公开 API 的入口地址, OpenVxDHandle 函数是个例外)。 VxD 通过 VWIN32.VXD 提供的 Win32 事件服务中的 _VWIN32_SetWin32Event 服务(在 VToolsD 中是调用 VWIN32_SetWin32Event 函数)唤醒辅助线程,从而间接实现 VxD 回调 Win32 应用程序。
该程序当中断每发生 2000 次(大约 1 — 2 秒)时唤醒辅助线程, Win32 应用程序窗口中的计数器的计数值会不断增大,该程序稍加修改,即可实现精度高达 1ms 的高精度定时。