コマンドラインからマウスを操作する方法 (rundll32.exeで動くDLLの作成法)
Windows上でアプリケーションを自動で操作するためには,
- プログラムによってマウスポインタを任意の場所へ動かし
- クリックさせる
といった制御が必要になる。
この「マウスの自動操作」はとても汎用的な操作なので,どんなプログラミング言語からもライブラリとして呼び出せたり,いっそコマンドプロンプトから実行できれば便利だ。
そこで,以下では
- マウスを動かす関数を集めてDLL化し,
- そのDLLをコマンドラインから実行できるようにし,
- その実行をバッチにして,アプリケーションの定型処理を自動化する
といった手順で,「コマンドラインからのマウス操作」を実現してみる。
(1)コマンドラインからDLLを実行する方法
DLLとは関数のライブラリであり,複数のアプリケーションで1つのDLLを共有して同時に呼び出せるのが特徴。
通常プログラマはDLLを,自分のプログラムのソースコード中から呼び出す。
しかし実は,プログラムを書かずとも,DLLの中身の関数をコマンドラインから実行できる。
コマンドプロンプトからrundll32.exeを呼び出せばよい。
RunDll32.exe DLLファイル名,関数名 引数
の形式で記述する。
ためしに,WindowsのカーネルDLLである user32.dll を呼び出してみる。
(※user32.dll の役割については既に「ウィンドウをきっかけに Windows の内部の仕組みを探る」のシリーズ中で力説した。)
RunDll32.exe user32.dll,LockWorkStation
user32.dll 中の LockWorkStation なる関数を呼び出し,その場で実行している。
これを打ち込むと,ユーザアカウント選択(+パスワード入力)の画面に切り替わる。
rundll32.exe の様々な使い方については下記ページが詳しい。
rundll32.exe のショートカットコマンドの一覧
http://www.vista123.net/content/list-rundll32-shortcut-commands-windows-vistaRunDLL32.exeの最も役に立たない使い方
http://konuma.txt-nifty.com/blog/2006/11/rundll32exe_ebd1.htmlMHTMLファイルを印刷する。
http://scripting.cocolog-nifty.com/blog/2010/09/mhtml-6002.htmlWin7の「ヘルプとサポート」で特定のページを開く。
http://scripting.cocolog-nifty.com/blog/2013/02/win7-932e.html
- rundll32.exe shell32.dll,LaunchMSHelp_RunDLL mshelp://〜
(2)rundll32.exe で自作DLLを呼び出す方法
では,どんなDLLであっても全ての関数を呼び出せるのか,というとそうではない。
Rundll32.exe から呼び出せる関数には宣言形式が決められている。
[INFO] Windows の Rundll と Rundll32 インターフェイス
http://support.microsoft.com/kb/164787/ja
Rundll または Rundll32 に適切な種類の DLL が渡されない場合、エラー メッセージが表示されることなく、プログラムの実行に失敗することがあります。
32 ビット DLL の場合
void CALLBACK
EntryPoint(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow);
上記の宣言形式に従っていない関数は,たとえ呼び出せはしたとしても,正常には動かないケースがある。
例として,ダイアログボックスを表示する関数 MessageBox を呼び出してみる。
下記のコードを temp.js で保存してダブルクリックで実行。
var WSHShell = WScript.CreateObject("WScript.Shell"); WSHShell.Run('Rundll32.exe user32.dll,MessageBoxA test test');
実行時の画像:
タイトルに「test test」と出るが,本文が文字化けしている。引数が正しく渡っていないのだ。
でもこれは何か表示されるだけマシで,コマンドプロンプトから
Rundll32.exe user32.dll,MessageBoxA test test
と打ちこんでも何も起こらない。
やはり,コマンドラインから常に正常に呼び出せるようにするためには,要求仕様通りの形式で関数を宣言する必要がある。
では実際にメッセージボックスが表示されるDLLを作り,コマンドラインで呼び出してみよう。
下記のコードを test.c で保存:
#include <windows.h> __declspec(dllexport) void CALLBACK MyMsgBox(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow) { MessageBox( hwnd, lpszCmdLine, "タイトル", MB_OK ); }
Visual C++ がインストールされている環境であれば,DLL作成のコンパイルオプションとして /LD を付け
cl /LD test.c user32.lib
でコンパイルでき,
- test.obj (生の機械語)
- test.lib (インポートライブラリ。暗黙な(静的でない)リンクの場合にexeから参照される)
- test.exp (エクスポートファイル。DLLに書き出す(エクスポートする)関数の情報が記録される)
- test.dll (実行に使うファイル)
の4つのファイルが生成される。
参考:
ダイナミック リンク ライブラリ(DLL)の基礎知識 / DLLの作り方(VC++編)
http://exlight.net/devel/windows/dll/windll.htmlリンカ入力としての .exp ファイル
http://msdn.microsoft.com/ja-jp/library/se8y7dcs.aspx
ここですぐに rundll32.exe から呼び出したくなるが,それはできない。
まずは,DLLの内部関数名を調べる必要がある。
DLLファイルの中身を調べるために,コマンドプロンプトから
dumpbin /exports test.dll
とする。
(※dumpbin.exeの使い方については「逆コンパイル + 逆アセンブル のための5つの無料ツール」を参照)
出力はこんな感じになるはず。
D:\temp>dumpbin /exports test.dll Microsoft (R) COFF/PE Dumper Version 9.00.21022.08 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file test.dll File Type: DLL Section contains the following exports for test.dll 00000000 characteristics 4920BC0C time date stamp Mon Nov 17 09:34:20 2008 0.00 version 1 ordinal base 1 number of functions 1 number of names ordinal hint RVA name 1 0 00001000 _MyMsgBox@16 Summary 2000 .data 2000 .rdata 1000 .reloc 7000 .text
これを見ると,先ほど作ったMyMsgBoxという関数は,DLL中では
- 相対アドレス(RVA)が 00001000 の部分に書き込まれており,
- 「_MyMsgBox@16」 という名称に置き換えられて内部処理されている
という事がわかる。
ソースコードの関数名と違う名前になって _ とか @ が付いている理由は,「引数が違うけど同名」である関数の衝突を避けるため。
参考:
逆アセのスス乂:MSDN技術資料
http://www.interq.or.jp/chubu/r6/reasm/PE_FORMAT/1.htmlVS.NET C 言語編2 - win32 DLL、__declspec(dllexport)、DllExport、.def
http://homepage2.nifty.com/sak/w_sak3/doc/syspc/vc_net02.htm
これでDLL内部の関数名がわかったので,その名前を使って rundll32.exe から呼び出すことができる。
rundll32.exe test.dll,_MyMsgBox@16 Hello,World!
実行画像:
ちなみに,DLL内部の名前を正しく指定しないと「エラーが発生しました。エントリがありません」と表示される。
(3)マウスを操作するDLLを作成する
Windows の持つ各機能は Windows API から呼び出すことができ,Windows API の実体は各種カーネルDLLにある。
そこで,マウスを自動操作するためには,『「それら WIndows API のDLLを呼び出す」ようなDLLを作って,それをさらに rundll32.exe から呼び出す』ようにすればよい。
MouseControll.c で下記のソースコードを保存:
#include <windows.h> // マウス座標を設定する関数 __declspec(dllexport) void CALLBACK SetMouseXY(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow) { int x = 0, y = 0; //char out[100]; // コマンドラインから読み取り sscanf( lpszCmdLine, "%d,%d", &x, &y ); //sprintf( out, "x=%d,y=%d", x, y ); //MessageBox( hwnd, out, "デバッグ用", MB_OK ); SetCursorPos( x, y ); } // マウス座標を取得する関数 __declspec(dllexport) void CALLBACK GetMouseXY(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow) { char out[100]; POINT pos; // Point構造体 // 取得 GetCursorPos( &pos ); // 表示 sprintf( out, "(x, y) = (%d, %d)", pos.x, pos.y ); MessageBox( hwnd, out, "マウス位置", MB_OK ); } // クリック動作をエミュレートする関数 __declspec(dllexport) void CALLBACK LeftClick(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow) { // 下げて mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); Sleep(10); // 上げる mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); }
プログラムの内容は下記を参考:
●Win32API(C言語)編 第7章 マウスカーソルの操作
http://www.geocities.jp/ky_webid/win32c/007.html
mouse_event()でマウスやキーボードを自動で動かす
http://orangeknowledge.jpn.org/tips/sdk002.html
※本当はSendInput関数を使うのが推奨
http://msdn.microsoft.com/ja-jp/library/cc410921.aspx
上記コードをコンパイルする。
cl /LD MouseControll.c user32.lib
DLLファイルを呼び出すと,マウスが移動する。
rundll32.exe MouseControll.dll,_SetMouseXY@16 100,200
なお画面の座標は,左上が (x,y) = (0,0) となっている。
上記のDLLは,下記URLからダウンロードする事ができます。
http://www.name-of-this-site.org/coding/dll/MouseControll.dll
(4)バッチに組み込んで,アプリケーションを自動操作する
前項で可能になったコマンドライン操作を,バッチファイルに組み込む。
まず自動操作したい対象として,下記のようなWebページ(alert.html)を考えよう。
<html> <head><title>alertテスト</title></head> <body> ようこそ <script> alert("テスト1"); setTimeout(function(){alert("テスト2");}, 3000); </script> </body> </html>
このページを訪れた人は,毎回ページを開くたびに,「テスト1」というダイアログを閉じなければならない。
それだけでなく,JavaScriptタイマーで3秒後には「テスト2」というダイアログが表示されるので,これもまたわざわざ閉じなければならない。
非常に煩雑な作りのページだ。
このページを定期的に巡回したいという場合,マウスが勝手に動いて,ダイアログを閉じてくれたら嬉しい。
では,そういうバッチを作ろう。
まず,ブラウザでこのページを開いた時に,ダイアログの「OK」ボタンのXY座標がいくつになるのかを調べる。
その手順:
- コマンドプロンプト上で rundll32.exe MouseControll.dll,_GetMouseXY@16 と打ちこみ,そのまま実行しないでおく。
- alert.htmlを開く。ダイアログが表示されるのを待つ。
- 「OK」ボタンの上にマウスを移動させる(押さないままにする)。
- Alt + Tabキーで,アクティブなアプリケーションを切り替え,コマンドプロンプトを画面最前部に出す。
- Enterキーを押下し,先ほど入力したコマンドを実行する。
- ボタンの真上にある状態のマウスポインタの座標が取得できる。
ここで取得した座標を使って,マウスをその座標に移動させ,クリックするようなコードを書く。
WSHで実装する場合(.jsファイルとして保存):
var WShell = WScript.CreateObject("WScript.Shell"); // Webページを開く WShell.Run("alert.html"); WScript.Sleep(1000); // 念のため待つ // マウス位置をセット WShell.Run("rundll32.exe MouseControll.dll,_SetMouseXY@16 637,394"); WShell.Run("rundll32.exe MouseControll.dll,_LeftClick@16"); // ウィンドウがアクティブになる WShell.Run("rundll32.exe MouseControll.dll,_LeftClick@16"); // alertが押される WScript.Sleep(5000); WShell.Run("rundll32.exe MouseControll.dll,_LeftClick@16"); // alertが押される
.bat ファイルで実装する場合:
rem Webページを開く start alert.html ping localhost -n 1 > nul rem マウス位置をセット rundll32.exe MouseControll.dll,_SetMouseXY@16 637,394 rundll32.exe MouseControll.dll,_LeftClick@16 rundll32.exe MouseControll.dll,_LeftClick@16 ping localhost -n 5 > nul rundll32.exe MouseControll.dll,_LeftClick@16
どちらの場合も,バッチファイルをダブルクリックするとWebページが開かれ,マウスカーソルがスッと動き,alertが2つともカチカチと自動でクリックされる。
これはなかなか壮観だ。
※ちなみに .bat ファイルのほうでは,「コマンドプロンプトで処理を指定時間秒だけ sleep させるために ping を使う」という裏技を利用した。
DOSプロンプト活用相談室LOG / BATファイル中で指定秒間WAITをかける方法
http://www.fpcu.jp/dosvcmd/bbs/log/cat3/pausechoice/2-0952.html
Webページに限った自動巡回ならBadBoyやSeleniumといったツールがあるが,上記の手順ならば,アプリケーションの種類は問わない。
自動印刷したり,Skypeを自動発信したりと色々できる。
WSHのSendKeysメソッドと組み合わせればキーの打鍵もエミュレートできる。
GUIアプリケーションの回帰テストなどにも使えるか。
応用はアイデア次第だろう。*1
補足
なぜDLLまで作るかというと,WSHにはマウス操作の関数が無いのだ。
Win32APIには SetCursorPos のようなメソッドがちゃんとあるにも関わらず。
アプリケーションをVBS(WSH?)で操作したい
http://oshiete1.goo.ne.jp/qa2648826.html→SendKeysのキー操作で何とかするべし
コマンドプロントでキーボード(マウス)の操作をさせたいです
http://okwave.jp/qa1959809.html→同
シェアウェアのCOMコンポーネント
http://www.tamasoft.co.jp/toas/index.html
この不満を解消するために,一般にはUWSCというフリーソフトが使われる。
WSHのような形式の独自スクリプトを実行し,マウスやキーボードの定型処理を自動化できる。
UWSCの本家
http://www.uwsc.info/
しかし,できればこういった独自形式ではなく,汎用性のある仕方で済ませたい。
もしDLLやコンソールのEXEで目的の機能を実装するようにすれば,シェル呼び出しさえできれば,どんなプログラミング言語からも利用できる。
そしてEXEもよいが,DLLならば,ソースコードは関数を並べただけの形で済む。
(1つの.exe で実現する場合は,「どの機能を実行したいのか」を解釈・選択させる部分を作り込む必要があるだろう。)
まとめ
DLL作成
- DLLの外部公開関数: __declspec(dllexport)
- cl /LD ソースファイル
- dumpbin /exports DLL名.dll
コマンドラインで呼び出せるようにする
- rundll32.exe DLLファイル名,関数名 引数
- void CALLBACK 関数名(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
マウス
- マウス操作するWinAPI関数: SetCursorPos( x, y ); / GetCursorPos( &pos );
追記
このエントリーをさらに発展させたDLLが,以下のサイトで公開されている。
WSH JScriptを使いこなそう 〜マウス操作〜
http://3rd.geocities.jp/kaito_extra/Source/MouseCtrl.html
関連する記事:
コマンドプロンプトから,Win32 APIや任意のDLLを呼び出して実行しよう (コマンドプロンプトから画面キャプチャする方法の仕組みを理解) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20100804/p1
画面のスクリーンショットを,Excelブック内に自動的に保存するバッチ - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20100425/p1
ウィンドウをきっかけに Windows の内部の仕組みを探る (前半)フレームワークからDLLまでのスケール - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20081108/1226172930
*1:処理が予想外に遅延したときや,エラーが起こったときは困るが…。