淺談 VxD 回撥 Win32 應用程式 -- 趙世平 (轉)

worldblog發表於2007-12-08
淺談 VxD 回撥 Win32 應用程式 -- 趙世平 (轉)[@more@]

 
淺談 VxD 回撥 應用
作者:趙世平

95 / 98 的虛擬外殼裝置( Virtual Device )提供了 VxD 直接回撥 Win16 應用程式的 VxD 服務,但是沒有提供 VxD 直接回撥 Win32 應用程式的 VxD 服務。不過 Windows 95 / 98 還是提供了兩種 VxD 回撥 Win32 應用程式的方法。方法之一是使用 VWIN32.VXD 提供的“非同步過程( APC )”功能。 Win32 應用程式首先動態載入 VxD ,並使用 DeviceIoControl 函式將回撥函式的地址傳遞給 VxD ,然後 Win32 應用程式呼叫 SleepEx / WaitForMultiplesEx / WaitForSingleObjectEx 函式,將 Win32 應用程式自身置為“掛起”狀態,這時 VxD 可以透過 VWIN32.VXD 提供的 _VWIN32_QueueUserApc 服務呼叫 Win32 應用程式的回撥函式。該方法較簡單,目前大多數回撥 Win32 應用程式的 VxD 均使用該方法[例如( RAV )的實時 VxD ( RAV_IO.VXD )即使用該方法回撥 Win32 查毒/防毒程式],《 Windows 95 System Programming 4 》一書的配套光碟中有一個名為 IFSMONITOR 的例項程式詳細講述了該方法,由於該書的配套光碟在國內很多 上都可以找到,此處就不再詳述了。 

方法之二是一種比較靈活的方法,這種方法充分利用了 Win32 應用程式的多執行緒特點和執行緒間通訊的事件機制。 Win32 應用程式設定兩個執行緒並定義一個事件,主執行緒負責動態載入/解除安裝 VxD 和透過 DeviceIoControl 函式與 VxD 通訊,輔助執行緒透過 ResetEvent 函式和 WaitForSingleObject / WaitForSingleObjectEx 函式暫時掛起, VxD 可以透過 VWIN32.VXD 提供的 Win32 事件服務中的 _VWIN32_SetWin32Event 服務喚醒輔助執行緒,從而間接實現 VxD 回撥 Win32 應用程式。由於 VWIN32.VXD 提供了與 Win32 幾乎完全對應的 Win32 事件服務,所以該方法極其靈活,甚至可以透過定義兩個事件實現 VxD 在回撥 Win32 應用程式時與 Win32 應用程式完全同步。 

筆者為了驗證上述方法,編寫了一個動態載入/解除安裝 VxD 的 Win32 應用程式和一個回撥 Win32 應用程式的 VxD 。其中 Win32 應用程式用 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(h: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.Tenate 

end; 


procedure TTimerTestMain.Start1Click(Sender: TObject); 

var 

 Kernel32:THandle; 

 OpenVxDHandle:TOpenVxDHandle; 

 VxDEvent:THandle; 

 cb:Long; 

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; overr; 

  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  


#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  

#include  


#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<

 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 入口地址,原因是 公司在 KERNEL32.DLL 中加入了 Anti-ing 程式碼,而且大部分未公開 API 的引出函式名被去掉了,也就是說透過函式名也不能獲取這些未公開 API 的入口地址, OpenVxDHandle 函式是個例外)。 VxD 透過 VWIN32.VXD 提供的 Win32 事件服務中的 _VWIN32_SetWin32Event 服務(在 VToolsD 中是呼叫 VWIN32_SetWin32Event 函式)喚醒輔助執行緒,從而間接實現 VxD 回撥 Win32 應用程式。 

該程式當中斷每發生 2000 次(大約 1 — 2 秒)時喚醒輔助執行緒, Win32 應用程式視窗中的計數器的計數值會不斷增大,該程式稍加修改,即可實現精度高達 1ms 的高精度定時。 
 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-989859/,如需轉載,請註明出處,否則將追究法律責任。

相關文章