浅谈 VxD 回调 Win32 应用程序 -- 赵世平

类别:VC语言 点击:0 评论:0 推荐:

 
浅谈 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 的高精度定时。 
 

本文地址:http://com.8s8s.com/it/it3463.htm