内容:很多的教学软件或系统监视软件可以自动记录回放用户的输入文字或点击按钮等操作操作,这个功能的实现是使用
了windows的hook函数。
windows提供api函数setwindowshookex来建立一个hook,通过这个函数可以将一个程序添加到hook链中监视windows
消息,函数语法为:
setwindowshookex(idhook: integer; lpfn: tfnhookproc; hmod: hinst; dwthreadid: dword)
其中参数idhook指定建立的监视函数类型。通过windows msdn帮助可以看到,setwindowshookex函数提供15种不同
的消息监视类型,在这里我们将使用wh_journalrecord和wh_journalplayback来监视键盘和鼠标操作。参数lpfn指定消
息函数,在相应的消息产生后,系统会调用该函数并将消息值传递给该函数供处理。函数的一般形式为:
hookproc (code: integer; wparam: wparam; lparam: lparam): lresult stdcall;
其中code为系统指示标记,wparam和lparam为附加参数,根据不同的消息监视类型而不同。只要在程序中建立这样
一个函数再通过setwindowshookex函数将它加入到消息监视链中就可以处理消息了。
在不需要监视系统消息时需要调用提供unhookwindowshookex来解除对消息的监视。
wh_journalrecord和wh_journalplayback类型是两种相反的hook类型,前者获得鼠标、键盘动作消息,后者回放鼠
标键盘消息。所以在程序中我们需要建立两个消息函数,一个用于纪录鼠标键盘操作并保存到一个数组中,另一个用于
将保存的操作返给系统回放。
下面来建立程序,在delphi中建立一个工程,在form1上添加3个按钮用于程序操作。另外再添加一个按钮控件和一
个edit控件用于验证操作。
下面是form1的全部代码
unit unit1;
interface
uses
windows, messages, sysutils, classes, graphics, controls, forms, dialogs,
stdctrls;
type
tform1 = class(tform)
button1: tbutton;
button2: tbutton;
button3: tbutton;
edit1: tedit;
button4: tbutton;
procedure formcreate(sender: tobject);
procedure button1click(sender: tobject);
procedure button2click(sender: tobject);
procedure button3click(sender: tobject);
private
{ private declarations }
public
{ public declarations }
end;
var
form1: tform1;
eventarr:array[0..1000]of eventmsg;
eventlog:integer;
playlog:integer;
hhook,hplay:integer;
recok:integer;
canplay:integer;
bdelay:bool;
implementation
{$r *.dfm}
function playproc(icode:integer;wparam:wparam;lparam:lparam):lresult;stdcall;
begin
canplay:=1;
result:=0;
if icode < 0 then //必须将消息传递到消息链的下一个接受单元
result := callnexthookex(hplay,icode,wparam,lparam)
else if icode = hc_sysmodalon then
canplay:=0
else if icode = hc_sysmodaloff then
canplay:=1
else if ((canplay =1 )and(icode=hc_getnext)) then begin
if bdelay then begin
bdelay:=false;
result:=50;
end;
peventmsg(lparam)^:=eventarr[playlog];
end
else if ((canplay = 1)and(icode = hc_skip))then begin
bdelay := true;
playlog:=playlog+1;
end;
if playlog>=eventlog then begin
unhookwindowshookex(hplay);
end;
end;
function hookproc(icode:integer;wparam:wparam;lparam:lparam):lresult;stdcall;
begin
recok:=1;
result:=0;
if icode < 0 then
result := callnexthookex(hhook,icode,wparam,lparam)
else if icode = hc_sysmodalon then
recok:=0
else if icode = hc_sysmodaloff then
recok:=1
else if ((recok>0) and (icode = hc_action)) then begin
eventarr[eventlog]:=peventmsg(lparam)^;
eventlog:=eventlog+1;
if eventlog>=1000 then begin
unhookwindowshookex(hhook);
end;
end;
end;
procedure tform1.formcreate(sender: tobject);
begin
button1.caption:='纪录';
button2.caption:='停止';
button3.caption:='回放';
button4.caption:='范例';
button2.enabled:=false;
button3.enabled:=false;
end;
procedure tform1.button1click(sender: tobject);
begin
eventlog:=0;
//建立键盘鼠标操作消息纪录链
hhook:=setwindowshookex(wh_journalrecord,hookproc,hinstance,0);
button2.enabled:=true;
button1.enabled:=false;
end;
procedure tform1.button2click(sender: tobject);
begin
unhookwindowshookex(hhook);
hhook:=0;
button1.enabled:=true;
button2.enabled:=false;
button3.enabled:=true;
end;
procedure tform1.button3click(sender: tobject);
begin
playlog:=0;
//建立键盘鼠标操作消息纪录回放链
hplay:=setwindowshookex(wh_journalplayback,playproc,
hinstance,0);
button3.enabled:=false;
end;
end.
代码添加完毕后,运行程序,点击“纪录”按钮开始纪录操作,这时你可以在文本控件中输入一些文字或者点击
“范例”按钮,然后点击“停止”按钮停止纪录,再点击“回放”按钮就可以讲先前所做的操作回放。
在上面的程序中,hookproc是纪录操作的消息函数,每当有鼠标键盘消息发生时,系统都会调用该函数,消息信
息就保存在地址lparam中,我们可以讲消息保存在一个数组中。playproc是消息回放函数,当系统可以执行消息回放
时调用该函数,程序就将先前纪录的消息值返回到lparam指向的区域中,系统就会执行该消息,从而实现了消息回放。
//以下是VC做的小例子
// replayView.cpp : implementation of the CReplayView class
#include "stdafx.h"
#include "replay.h"
#include "replayDoc.h"
#include "replayView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
LRESULT CALLBACK RecHook(int code,WPARAM wParam,LPARAM lParam);
LRESULT CALLBACK PlayHook(int code,WPARAM wParam,LPARAM lParam);
HHOOK recHook,playHook;
EVENTMSG EventArray[1000];
int recordedEvent=0;
int playedEvent=0;
/
// CReplayView
IMPLEMENT_DYNCREATE(CReplayView, CEditView)
BEGIN_MESSAGE_MAP(CReplayView, CEditView)
//{{AFX_MSG_MAP(CReplayView)
ON_COMMAND(ID_FUNCTION_START, OnFunctionStart)
ON_COMMAND(ID_FUNCTION_STOP, OnFunctionStop)
ON_COMMAND(ID_FUNCTION_REPLAY, OnFunctionReplay)
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CEditView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CEditView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CEditView::OnFilePrintPreview)
END_MESSAGE_MAP()
/
// CReplayView construction/destruction
CReplayView::CReplayView()
{
// TODO: add construction code here
}
CReplayView::~CReplayView()
{
}
BOOL CReplayView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
BOOL bPreCreated = CEditView::PreCreateWindow(cs);
cs.style &= ~(ES_AUTOHSCROLL|WS_HSCROLL); // Enable word-wrapping
return bPreCreated;
}
/
// CReplayView drawing
void CReplayView::OnDraw(CDC* pDC)
{
CReplayDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
}
/
// CReplayView printing
BOOL CReplayView::OnPreparePrinting(CPrintInfo* pInfo)
{
// default CEditView preparation
return CEditView::OnPreparePrinting(pInfo);
}
void CReplayView::OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo)
{
// Default CEditView begin printing.
CEditView::OnBeginPrinting(pDC, pInfo);
}
void CReplayView::OnEndPrinting(CDC* pDC, CPrintInfo* pInfo)
{
// Default CEditView end printing
CEditView::OnEndPrinting(pDC, pInfo);
}
/
// CReplayView diagnostics
#ifdef _DEBUG
void CReplayView::AssertValid() const
{
CEditView::AssertValid();
}
void CReplayView::Dump(CDumpContext& dc) const
{
CEditView::Dump(dc);
}
CReplayDoc* CReplayView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CReplayDoc)));
return (CReplayDoc*)m_pDocument;
}
#endif //_DEBUG
/
// CReplayView message handlers
void CReplayView::OnFunctionStart()
{
// TODO: Add your command handler code here
recordedEvent=0;
recHook=SetWindowsHookEx(WH_JOURNALRECORD,(HOOKPROC)RecHook,(HINSTANCE)AfxGetApp()->m_hInstance,0);
}
void CReplayView::OnFunctionStop()
{
// TODO: Add your command handler code here
UnhookWindowsHookEx(recHook);
}
void CReplayView::OnFunctionReplay()
{
// TODO: Add your command handler code here
playedEvent=0;
playHook=SetWindowsHookEx(WH_JOURNALPLAYBACK,(HOOKPROC)PlayHook,(HINSTANCE)AfxGetApp()->m_hInstance,0);
}
LRESULT CALLBACK RecHook(int code,WPARAM wParam,LPARAM lParam)
{
static int recOK=1;
if(code<0)
return CallNextHookEx(recHook,code,wParam,lParam);
else if(code==HC_SYSMODALON)
recOK=0;
else if(code==HC_SYSMODALOFF)
recOK=1;
else if(recOK && (code==HC_ACTION))
{
EventArray[recordedEvent]= *((PEVENTMSG)lParam);
recordedEvent++;
if(recordedEvent==1000)
{
UnhookWindowsHookEx(recHook);
}
}
return 0;
}
LRESULT CALLBACK PlayHook(int code,WPARAM wParam,LPARAM lParam)
{
static BOOL fDelay;
static int playOK=1;
if(code<0)
return CallNextHookEx(playHook,code,wParam,lParam);
else if(code==HC_SYSMODALON)
playOK=0;
else if(code==HC_SYSMODALOFF)
{
playOK=1;
}
else if(playOK && (code==HC_GETNEXT))
{
if(fDelay)
{
fDelay=FALSE;
return 50;
}
*((PEVENTMSG)lParam)=EventArray[playedEvent];
}
else if(playOK && (code==HC_SKIP))
{
fDelay=TRUE;
playedEvent++;
}
if(playedEvent>=recordedEvent)
{
UnhookWindowsHookEx(playHook);
}
return 0;
}