読者です 読者をやめる 読者になる 読者になる
スポンサーリンク

コマンドラインからマウスを操作する方法 (rundll32.exeで動くDLLの作成法)

コマンドプロンプト hack windows Win32 作品

Windows上でアプリケーションを自動で操作するためには,

  • プログラムによってマウスポインタを任意の場所へ動かし
  • クリックさせる

といった制御が必要になる。


この「マウスの自動操作」はとても汎用的な操作なので,どんなプログラミング言語からもライブラリとして呼び出せたり,いっそコマンドプロンプトから実行できれば便利だ。


そこで,以下では

  1. マウスを動かす関数を集めてDLL化し,
  2. そのDLLをコマンドラインから実行できるようにし,
  3. その実行をバッチにして,アプリケーションの定型処理を自動化する


といった手順で,「コマンドラインからのマウス操作」を実現してみる。

(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-vista

RunDLL32.exeの最も役に立たない使い方
http://konuma.txt-nifty.com/blog/2006/11/rundll32exe_ebd1.html

MHTMLファイルを印刷する。
http://scripting.cocolog-nifty.com/blog/2010/09/mhtml-6002.html

Win7の「ヘルプとサポート」で特定のページを開く。
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.html

VS.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座標がいくつになるのかを調べる。

その手順:

  1. コマンドプロンプト上で rundll32.exe MouseControll.dll,_GetMouseXY@16 と打ちこみ,そのまま実行しないでおく。
  2. alert.htmlを開く。ダイアログが表示されるのを待つ。
  3. 「OK」ボタンの上にマウスを移動させる(押さないままにする)。
  4. Alt + Tabキーで,アクティブなアプリケーションを切り替え,コマンドプロンプトを画面最前部に出す。
  5. Enterキーを押下し,先ほど入力したコマンドを実行する。
  6. ボタンの真上にある状態のマウスポインタの座標が取得できる。




ここで取得した座標を使って,マウスをその座標に移動させ,クリックするようなコードを書く。

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:処理が予想外に遅延したときや,エラーが起こったときは困るが…。