浅谈设备驱动程序回调Win32应用程序
(第1篇——VxD)
(2005修订版,本文档适用于Windows 9x VxD,包括Windows 95/98/Me VxD,但是方法的思想适用于Windows NT/2000/XP及其以后版本NT Driver/WDM驱动程序)
作者:TBsoft Software Studio(2000)
修订:TBsoft Software Studio(2005)
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均使用该方法(例如某些反病毒软件的实时监控VxD即使用该方法回调Win32查毒/杀毒程序),此处就不再详述了。
方法之二是一种比较灵活的方法,这种方法充分利用了Win32应用程序的多线程特点和线程间通信的事件机制。Win32应用程序设置两个线程并定义一个事件,主线程负责动态加载/卸载VxD和通过DeviceIoControl函数与VxD通信,辅助线程通过ResetEvent函数和WaitForSingleObject/WaitForSingleObjectEx函数暂时挂起,VxD可以通过VWIN32.VXD提供的Win32事件服务中的_VWIN32_SetWin32Event服务唤醒辅助线程,从而间接实现VxD回调Win32应用程序。由于VWIN32.VXD提供了与Win32 API几乎完全对应的Win32事件服务,所以该方法极其灵活,该方法的思想也完全可以用于NT Driver/WDM驱动程序。
注意:VxD回调Win32应用程序是不可能实现同步的,也就是说,不可能在VxD回调Win32应用程序时中止VxD的执行,等到回调完成再继续执行VxD,因为VxD回调的Win32应用程序必然又要访问VMM,而VMM是不可重入的。
笔者为了验证上述方法,编写了一个动态加载/卸载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函数将Win32应用程序的事件句柄转换成VxD事件句柄,然后通过DeviceIoControl函数传递给VxD。
OpenVxDHandle函数在SDK文档中未公开,但是在DDK文档中公开了,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应用程序窗口中的计数器的计数值会不断增大。