C# 螢幕操作錄製與回放

Just4life發表於2013-09-06

利用系統中的兩個特殊鉤子WH_JOURNALRECORDWH_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;
     }
}



相關文章