S-JIS[2007-09-16/2008-07-14] 変更履歴

Java Win32API実行クラス

JavaでMS-WindowsのWin32API(の一部)を呼び出すクラスとそのラッパー、及びRobotの拡張クラス等のユーティリティーです。
詳細はJavadoc参照。

hmwin32.jarとhmwin32.dllとソース(565kB) [/2008-07-14]
hmwin32.jarとhmwin32.dllとソース(旧)(556kB) [/2008-07-08]


製作した経緯とライブラリの概要

Javaには、ウィンドウの操作(マウスカーソルを動かしたりクリックしたりキーを押したり)をプログラムから行うRobotというクラスがあります。
これを使うと、自動的に画面上の特定位置(ボタン)をクリックして動作させる といった事が出来ます。
ただ、Robotで用意されているのはキーを押すのと離すのが別メソッドだったりして微妙に不便なので、RobotUtilというRobotを拡張したユーティリティークラスを作ってみました。

しかしもっと大きな問題があって、Robotで動かす対象に特定のウィンドウを指定することが出来ません。
最前面にあるウィンドウに対して処理を行うので、自動実行の開始時に対象となるウィンドウを手動で表に出す必要があります。
また、ボタンクリックによる画面の変更があっても、それを簡単に認識する方法がありません。

これを解決する為には、ウィンドウの状態を取得する為にWin32APIを呼び出せばよいです。
なんとEclipseの場合、SWTOSというWin32API用のクラスが入っていて、これを使えばけっこうな量のWin32APIが呼び出せます。
ただ、使うためにちょびっと面倒な準備が必要で、かつ、使いたかったWin32APIが このOSクラスには若干足りませんでした。

そこで、Win32APIを呼び出すクラスをJNIで自作しました。それがapiパッケージです。[2007-10-01]
(当初はOSクラスに無いものだけ用意しましたが、SWTのdllを準備するのが面倒なので、結局自分が使いたいものは全て自分で作成しました。 なので、現状ではOSクラスは不要になりました)

ついでにVC++のCWndクラスを真似て、JWndというクラスも作ってみ ました。
さらに、それらを使ってある程度の機能をまとめてRobotに似せたWindowsRobotクラスも作りました。


使用準備

このWin32APIクラスは単なるJNI呼び出しなので、実行する際にはhmwin32.dll実行時パスに追加する必要があります。
あと、このライブラリーはMTセーフではありません


IEでGoogle検索する例

WindowsRobotとRobotUtilを使って、IE6.0Googleのトップページが開いている状態から、自動的に検索キーワードを入力して検索する例 です。(→IERobotを使った例

import宣言

import java.awt.AWTException;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.util.List;

import jp.hishidama.robot.RobotUtil;
import jp.hishidama.win32.JWnd;
import jp.hishidama.win32.api.Win32Exception;

で使うために、Robotのインスタンスを用意しておく。

	protected static RobotUtil robot;
	static {
		try {
			robot = new RobotUtil();
		} catch (AWTException e) {
			throw new RuntimeException(e);
		}
	}

開始するに当たり、全体をtry〜catchで囲っておく。
ウィンドウを扱うWin32APIの場合、途中でウィンドウを閉じちゃったりしたらエラーになる。
が、今回の例では個々のエラーハンドリングは端折っている(爆)
そこでせめて全体をcatchして、GetLastError()によるエラーが取れればそのメッセージを表示するようにした。

	public static void main(String[] args) {
		JWnd.setThrowLastError(true);
		try {
			execute();
		} catch (Win32Exception e) {
			int    err = e.getLastError();
			String msg = e.getMessage(); //WinBase.FormatMessage(err)と同等
			System.out.println("Win32 LastError[" + err + "]:" + msg);
			throw e;
		}
	}

ところで、Win32APIのLastErrorはJNI経由ではクリアされてしまうらしい。[2007-10-01]
仕方が無いので、DLL内でグローバル変数を用意してそこに一旦GetLastError()の値を保存し、別メソッドでその値を取ってこようかと思った。
けど、例外をスローさせた方が使う側でエラーチェックを省けるので、Win32Exceptionという例外を作って、それをスローするようにした。[2007-10-02]
JWndのデフォルトでは今まで通り戻り値でチェックできるようにして、setThrowLastError()でtrueを指定した場合のみ例外をそのまま投げるようにした。こういう場合、javadocコメントにどう書くか、ちょっと悩ましい(苦笑)

ウィンドウに対する自動操作本体

	public static void execute() {
		WindowsRobot ie = new WindowsRobot();

		// Googleのトップページを表示しているIEを探す
		String title = "Google - Microsoft Internet Explorer";
		JWnd wnd = ie.findWindow(title, 200, 2);
		if (wnd == null) {
			System.out.println("探索失敗:\"" + title + "\"");
			return;
		}

		// 操作の為の準備
		ie.setJWnd(wnd);
		ie.setAutoDelay(200);
		System.out.println("探索成功:\"" + ie.getTitle() + "\"");

		// IEを最前面に表示する
		ie.setForegroundWindow();

		// サイズを変更する
		// ie.setLocation(32, 16);
		ie.setSize(768, 640);
		// ie.setBounds(16, 32, 1024, 768);

		// ウィンドウの中央にある領域を取得
		Rectangle ieRect = ie.getBounds();
		Point pt = ieRect.getLocation();
		pt.x += ieRect.width / 2;
		pt.y += ieRect.height / 2;
		wnd.ScreenToClient(pt);
		JWnd body = wnd.ChildWindowFromPoint(pt);

		Rectangle rect = body.getWindowRect();
		System.out.println("主要領域:" + rect);



		// 検索文字列を入れるボックスをクリック
		// Googleのデザイン変更によって位置は変わるかも…
		robot.clickL(rect.x + rect.width / 2, rect.y + 188);

		// 既に入っている文字を全選択
		robot.sendCtrlString("A"); // Ctrl+A

		// 選択した文字を消去
		robot.sendKeyCode(KeyEvent.VK_DELETE);

		// コピー&ペーストを使って、検索文字列を入力
		robot.pasteString("java Win32API");

		// Enterキーを押下することによってsubmit、つまり検索実行
		robot.sendChar('\n'); // ENTERキー



		// 画面遷移を待つ(200ms×40で、最大8秒待つ)
		title = "java Win32API - Google 検索 - Microsoft Internet Explorer";
		if (!ie.waitForTitle(title, 200, 40)) {
			System.out.println("遷移失敗:\"" + title + "\"");
			return;
		}
		System.out.println("遷移成功:\"" + ie.getTitle() + "\"");
	}

変更履歴

更新日 変更内容
2007-09-24 DLL作成側のコンパイラーをVC++4.0からVC++2005に変えた。
これによりファイルサイズが増えた…マニフェストなんかも埋め込んでるみたいだし。
2007-10-01 SWTのOSクラスを使うのをやめた。
Win32APIのパッケージ構成を変えた。
Java側で保持するHWNDを64bitに変えた。どうせDLLは32bitアプリだから意味ないんだけど(爆)
2007-10-02 Win32Exceptionを作り、DLL内のGetLastError()によって0以外が返った場合はその例外をスローするよう変更。
2007-10-22 NativeExceptionを作り、DLL内でアクセス違反等が発生した場合にその例外をスローするよう変更。
MSHTMLを扱うIERobotを作成。
2007-10-24 MSHTMLのラジオボタンコンボボックス(リストボックス)を操作するユーティリティークラスを作成。
2007-10-28 MSHTML関連のクラス名を変更。「HtmlHoge」→「IHTMLHoge」
MSHTMLのファイル選択エリアへ入力するユーティリティークラスを作成。
2007-11-01 IEでファイルのダウンロードを行うユーティリティークラスを作成。
2007-11-04 MSHTML関連でVariantクラスIDispatchクラスを導入。
2007-11-05 MSHTML関連で適当にHTMLのタグのクラス(IHTMLインターフェース)を追加。
2007-11-06 IEでファイルのダウンロードを行うユーティリティークラスを修正。
(保存ダイアログでパス名・ファイル名を入力する際にもフォーカスが当たっていないとダメなようだ)
MSHTML関連のIDispatchで型情報を取得するメソッドを追加。
2007-11-07 CoInitializeEx()を追加。アパートメントの指定が出来るけど、 あまり使わないと思われる(爆)
2008-02-02 石橋さんから報告のあったバグを修正。
IHTMLDocument#getFrames()の戻り値の型をIHTMLElementCollectionからIHTMLFramesCollectionに訂正。
IHTMLFramesCollection#item()の戻り値の型をIHTMLElementからIHTMLWindowに訂正。
2008-07-08 石橋さんから報告のあったバグを修正。
IHTMLFormElement#getElements()の戻り値の型をIHTMLElementCollectionからIHTMLFormElementに訂正。
どうも、FORMのgetElements()の戻り値は自分自身(FORM)のようだ…不思議。
2008-07-14 MSHTML関連の“COMの参照カウンター”の解放を、Javaの弱参照機能を使って行うよう修正。
これに伴い、「boolean child」を引数に持っていたメソッドは、その引数を削除したメソッドを作成。
今までのメソッドは全て非推奨に変更。

自作ソフトへ戻る / IERobotへ行く / 技術メモへ行く
メールの送信先:ひしだま