利用系统中的两个特殊钩子WH_JOURNALRECORD和WH_JOURNALPLAYBACK可以实现对屏幕上的所有操作进行录制并以相同的操作流程回放刚才的屏幕操作,本程序是基于以下论文《Windows Hooks中录制与回放钩子的运行机制剖析》并用C#实现的。本程序已通过调试。
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace HookTEST
{
public partial class Form1 : Form
{
public delegate int HookProc(int nCode, IntPtr wParam,IntPtr lParam);//钩子的声明格式
//声明钩子子程delegate
HookProc RecordProc = null, PlayBackProc = null;
private int hHook = 0; //钩子子程的指针
public bool bFlag = true; //在PlayBackProcedure()中,指示回放是否是在HC_SKIP后第一次进行HC_GETNEXT分支(要先了解回放的运行机制)
public int pos = 0; //在PlayBackProcedure()中指示当前要回放的消息在msgList中的位置
// msgList数组用于保存录制钩子捕获的消息以便于回放钩子对这些消息进行回放
ArrayList msgList = new ArrayList();
#region 根据Win32定义本程序中所用到的常量
//定义nCode在本程序中用到的值
public const int HC_ACTION=0;
public const int HC_SKIP=2;
public const int HC_GETNEXT = 1;
//定义钩子类型
public const int WH_JOURNALRECORD = 0;
public const int WH_JOURNALPLAYBACK = 1;
#endregion
#region (下面三个函数是录制回放操作的核心函数)
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
#endregion
public Form1()
{
InitializeComponent();
}
#region 录制子程
public int RecordProcedure(int nCode, IntPtr wParam, IntPtr lParam)
{
switch (nCode)
{
case HC_ACTION: //有鼠标或键盘动作
EVENTMSG curMSG = null;
try
{
curMSG = (EVENTMSG)Marshal.PtrToStructure(lParam, typeof(EVENTMSG));
}
catch(Exception e)
{
MessageBox.Show(e.ToString());
}
msgList.Add(curMSG);
break;
default:
break;
}
//return 1;
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
#endregion
#region 回放子程
public int PlayBackProcedure(int nCode, IntPtr wParam, IntPtr lParam)
{
int preTime = 0, nowTime = 0;
switch (nCode)
{
case HC_SKIP:
bFlag = true;
pos++;
return 0;
//break;
case HC_GETNEXT:
#region 判断是否回放完毕
if (pos >= (msgList.Count - 2))
{
bool ret = UnhookWindowsHookEx(hHook);
if (ret == true)
{
MessageBox.Show("回放完毕!");
}
pos = 0;
hHook = 0;
return 0;
}
#endregion
#region 复制msgList中的当前消息到lParam所指向的EVENTMSG中
EVENTMSG currentMSG = (EVENTMSG)msgList[pos];
try
{
Marshal.StructureToPtr(currentMSG, lParam, true);
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
#endregion
#region 计算系统回放该消息前要等待的时间
if ((pos > 0) && (bFlag == true))
{
bFlag = false;
EVENTMSG preMSG = (EVENTMSG)msgList[pos - 1];
preTime = preMSG.time;
nowTime = currentMSG.time;
return nowTime - preTime;
}
else
{
return 0;
}
#endregion
//break;
default:
break;
}
//return 0;
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
#endregion
/// <summary>
/// 开始录制
/// </summary>
private void button1_Click(object sender, EventArgs e)
{
if(hHook==0) //录制钩子还未安装,此时若按下按钮则表示开始录制,即进行钩子安装
{
RecordProc = new HookProc(this.RecordProcedure);
Process currentProcess = Process.GetCurrentProcess();
//安装钩子
hHook = SetWindowsHookEx(WH_JOURNALRECORD, RecordProc,currentProcess.MainModule.BaseAddress , 0);
if(hHook==0)
{
MessageBox.Show("SetWindowsHookEx Failed!");
return;
}
//清空msgList
msgList.Clear();
button1.Text = "停止录制";
button3.Enabled = false;
this.WindowState = FormWindowState.Minimized;
this.Hide();
//this.notifyIcon1.Visible = true;
}
else //已安装了钩子。此时若按下按钮则表示停止录制,即制裁钩子
{
bool ret = UnhookWindowsHookEx(hHook);
if(ret==false)
{
MessageBox.Show("UnhookWindowsHookEx Failed!");
return;
}
hHook = 0;
RecordProc=null;
button1.Text = "开始录制";
button3.Enabled = true;
}
}
/// <summary>
/// 开始回放
/// </summary>
private void button3_Click(object sender, EventArgs e)
{
if (msgList.Count > 0)
{
this.WindowState = FormWindowState.Minimized;
this.Hide();
if (hHook == 0)
pos = 0;
PlayBackProc = new HookProc(this.PlayBackProcedure);
Process currentProcess = Process.GetCurrentProcess();
hHook = SetWindowsHookEx(WH_JOURNALPLAYBACK, PlayBackProc, currentProcess.MainModule.BaseAddress, 0);
if (hHook == 0)
{
MessageBox.Show("SetWindowsHookEx Failed!");
return;
}
//button1.Enabled=false;
}
else
{
MessageBox.Show("No recorded MSG Now,please record first!");
}
//卸载回放钩子将在子程中进行
}
private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
{
this.Visible = true;
this.WindowState = FormWindowState.Normal;
//this.notifyIcon1.Visible = false;
}
}
/// <summary>
/// 自定义消息类型(调试了半天 原来是消息结构定义错了 原先我定义的消息结构是MSG,现在才知道MS为JOURNALPLAYBACK钩子定义了特定的消息结构)
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public class EVENTMSG
{
public UInt32 message;
public UInt32 paramL;
public UInt32 paramH;
public Int32 time;
public UIntPtr hwnd;
}
}