第164章 ジャーナルレコードとプレイバック その1


今回は,ちょっと面白いフックをやります。システムメッセージ・キューを 監視してマウスや、キーボードの動きを記録します。そして、今度はこれを 再生します。実際にプログラムを作ると予想外のことが起こって、 大変めんどうです。わかってしまえばどうということもありませんが・・・。 今回は、原理と部分的なプログラムを解説します。



まず、記録のほうですがこれはWH_JOURNALRECORD フックを使います。

例によってSetWindowsHookEx関数を使います。プロシージャはどうなるかというと これも形の上ではいつもと同じです。

LRESULT CALLBACK JounalRecordProc(int nCode, WPARAM wParam, LPARAM lParam)

nCodeがマイナスの時は直ちにCallNextHookEx関数の戻り値を返します。

HC_ACTIONの時は、lParamの指しているEVENTMSG構造体の内容を バッファとかファイルにコピーします。

typedef struct tagEVENTMSG { // em UINT message; UINT paramL; UINT paramH; DWORD time; HWND hwnd; } EVENTMSG;

具体的には、

EVENTMSG MyEvent[500]; ........ EVENTMSG *lpEM; ....... if (nCode == HC_ACTION) { lpEM = (EVENTMSG *)lp; MyEvent[n].hwnd = lpEM->hwnd; MyEvent[n].message = lpEM->message; MyEvent[n].paramH = lpEM->paramH; MyEvent[n].paramL = lpEM->paramL; MyEvent[n].time = lpEM->time; n++;

と、こんな感じになります。lpはプロシージャの第2引数です。 nCodeがHC_ACTIONの時は、lpは現在処理中のメッセージをあらわすEVENTMSG 構造体へのポインタですから、各メンバを自分で用意した(MyEvent)バッファに コピーしていきます。上の例では配列を利用して500個までのメッセージを 記録できます。しかし、記録を止める時はどうすればよいのでしょうか。

何か止める時の合図となるキーを決めておきます。ヘルプによると VK_CANCEL(Ctrl+STOP)が推奨されいてます。しかし、VK_F2などでも よいと思われます。(VK_F1はヘルプ呼び出しに使われることが多いので不可)

if (lpEM->message == WM_KEYDOWN && LOBYTE(lpEM->paramL) == VK_CANCEL) { UnhookWindowsHookEx(hHook); return TRUE; }

こんな感じでVK_CANCELを感知してフックを終了させます。

この他にnCodeが、HC_SYSMODALONとかHC_SYSMODALOFFの場合がありますがこれは、 システム・モーダルダイアログボックスが出て来たり、破棄されたりした時に 使います。

次に、WH_JOURNALPLAYBACKフックですが、これが大変わかりにくいです。 プロシージャの形はいつもと同じです。

nCodeがHC_GETNEXTの時は、次に再生するメッセージをlParamの指している EVENTMSG構造体にコピーしなくてはいけません。

if (nCode == HC_GETNEXT) { lpem = (EVENTMSG *)lp; lpem->hwnd = MyEvent[n].hwnd; lpem->message = MyEvent[n].message; lpem->paramH = MyEvent[n].paramH; lpem->paramL = MyEvent[n].paramL; lpem->time = MyEvent[n].time + dwAdjust;

とこんな感じになりますが、timeメンバに気をつけてください。 dwAdjustを加えて時間を調整しています。 記録開始の時間をdwStartに記録しておきます。 ここで、時間とはシステム(Windows)が起動してからの時間です。 GetTickCount関数で知ることができます。 dwAdjustは再生を開始した時に

dwAdjust = GetTickCount() - dwStart;

で求めることができます。そして、nCodeがHC_GETNEXTの時の 戻り値はシステムの待ち時間にしなくてはいけません。 戻り値をdwReturnTimeとすると

dwReturnTime = lpem->time - GetTickCount();

とすることができます。 戻り値を0にするとメッセージが即座に処理されます。 ここで、問題があります。次々と記録したメッセージと 理屈の上でのtimeメンバとの間に誤差が生じて dwReturnの値が常にプラスとは限りません。 そこで

if (dwReturnTime < 0).......

というような処理を考えがちですが、これはだめです。 dwReturnTimeはDWORD値なのでint型に型キャストして下さい。

if ((int)dwReturnTime < 0) { dwReturnTime = 0; lpem->time = GetTickCount(); }

とこんな感じにするのが正解と思われます。

次にnCodeがHC_SKIPの時は次のマウスメッセージを lpの指す構造体にコピーする準備ををしなくてはいけません。 記録したメッセージが配列になっている場合は 配列の番号を1つ進めるなどの処理をします。 また、次のメッセージがない場合は、フックを終了させます。

if (nCode == HC_SKIP) { n++; if (MyEvent[n].hwnd == 0) { UnhookWindowsHookEx(hHook); return FALSE; } }

とこんな感じになります。戻り値は無視されるので 何でもいいです。記録を始める時に MyEvent配列をすべて0で初期化しておくと上のような ことができます。(配列の次のメンバが0ならもうこれで再生は終わり)

さて、これで一応の解説は終わりです。これらを参考に プログラムを作ってみてください。簡単そうでかなりめんどうです。 できあがったプログラムで遊んでみてください。 メモ帳を立ち上げて、文章を書いてこれを保存する動作を 記録することもできます。これを再生しているところを 誰かに見せると驚かれること間違いなしです。

しかし、この機能は16ビット時代にはOSにおまけでついていました。


[SDK第2部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 21/Dec/1998 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。