S-JIS[2007-10-22/2008-07-14] 変更履歴

Java IERobotクラス

JavaでMS-WindowsのWebBrowserコントロールを使ってInternetExplorerを操作する、Robotクラスのようなユーティリティーです。
詳細はJavadoc参照。

アーカイブ(hmwin32.jarとhmwin32.dllとソース) [/2008-07-14]


製作した経緯と使用上の注意

WindowsRobotではウィンドウが自由に操作できるものの、ブラウザーで入力したりボタンを押したり、あるいは入力されている文字列を取得したりするのが大変です。そこで、WebBrowserコントロールを使おうと思い立って作ったのが当ライブラリーです。

当ライブラリーでは、IWebBrowser2やIHTMLDocument・IHTMLElementといったCOM(ActiveX)オブジェクトに、JNIを介してアクセスします。
それぞれ、COMオブジェクトを模したJavaクラスを用意しました(よく使いそうなものだけ^^;)が、それらのオブジェクトを使わなくなった際には、明示的に破棄メソッドを呼び出して破棄する必要があります。
また、使用に先立ってCOM自体の初期化メソッドを一度だけ呼び出し、最後に終了メソッドを呼び出す必要があります。

JavaのオブジェクトのライフサイクルはJavaVM(GC)が管理しています。したがって、通常のJavaオブジェクトの廃棄に関してプログラマーはほとんど意識しなくて構いません。
しかし当ライブラリーではWindowsのCOMオブジェクトのポインターを内部で保持している為、明示的に破棄(参照カウンターのデクリメント)を行う必要があります。
これぞ普段は使わないファイナライザーの出番かと思いましたが、ファイナライザーは必ず呼ばれる事は保証されていないみたいなので、当てに出来ないようです。 仕方がないので、「取得したオブジェクトを使い終わったらdispose()を呼び出す必要がある」という仕様にしました。
でもそれだと忘れる事がよくありそうなので、最後にCOM全体を終了するメソッドを呼び出す必要もあるし、その時に(明示的に破棄されなかった)残っている全オブジェクトを破棄するようにしました。(その為に、生成したオブジェクトは全てリストに保存してdispose()が呼ばれたときに破棄してリストからも削除するようにしています) さすがに最後の一回のuninitialize()くらいは忘れないでしょ(笑)
ただ、それまでは破棄しないでいるとどんどん溜まり続けるので、下手するとOutOfMemoryになるかもしれません(リストに入れてるから、GCの対象にならない)。まぁ、このライブラリーの目的はWebアプリの画面遷移等の試験なので、その程度なら大丈夫かもしれませんが。

これらのオブジェクト管理を多少なりとも軽減する為、Robotに似せたIERobotというクラスも作ってみました。
IERobotの破棄は明示的に行う必要がありますが、その際に取得していたオブジェクトを自動的に破棄します。
また、 navigate()等によって画面遷移した場合も、取得していた関連オブジェクトを自動的に破棄します。
(なので、IERobotから取得したCOMオブジェクトに関しては自分では破棄しないで下さい。
 さらに、“IERobotから取得したCOMオブジェクト”から別のCOMオブジェクトを取得する場合は、addChildにtrueを指定すると IERobotの管理下に入ります

COMの参照カウンターの破棄は、弱参照リストを使って管理するように修正しました。[2008-07-14]
すなわち、取得したオブジェクトは全て弱参照リストに登録します。
普通にJava内で参照されなくなったら、GCによりファイナライザーが呼び出され、そこで破棄(参照カウンターのデクリメント)を行います。その対象は自動的に弱参照リストから削除されます。(したがって、プログラマーが個々にdispose()を呼び出す必要は無くなりました)
最後のCOM全体の終了時に 弱参照リストに残っていたオブジェクトを全て破棄します。これにより、取得した全てのCOMオブジェクトが解放されます。

[2008-01-19]
当ライブラリーを使用するには、最初にComMgr#initialize()を呼び出す必要があります。まぁこれは呼び出すのを忘れると後続ですぐに例外が発生するから分かるでしょうけど。
そして最後にComMgr#uninitialize()を呼び出す必要があります。
通常のアプリケーションでは(実行時に使用されたリソースはアプリケーションが終了すれば解放されるので)終了用メソッドを呼び出すのを忘れてもなんとかなっているかもしれませんが、当ライブラリではCOMオブジェクトを使用しており、参照カウンターのデクリメントをしないとWindowsがどうなるか分からない為、必ずuninitialize()を呼び出すようにすべきです。
特にfinallyでuninitialize()を呼び出すように実装したとしても、tryブロック内でSystem.exit()を使ってしまうとfinallyは呼び出されないので注意が必要です。


使用準備

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


IERobotでGoogle検索する例

IERobotを使って、Googleのトップページを開いて自動的に検索キーワードを入力して検索する例です。 (→WindowsRobotを使った例

import宣言

import jp.hishidama.robot.IERobot;
import jp.hishidama.win32.com.ComMgr;
import jp.hishidama.win32.mshtml.IHTMLInputTextElement;

main()
COMの初期化と終了処理を入れておく。
一番発生する可能性のある例外はHResultExceptionだが、NativeException等も発生する可能性がある。

	public static void main(String[] args) {
		ComMgr.initialize();
		try {
			execute(); //確実にfinallyを実行させる為に、System.exit()は使用しないこと!
		} finally {
			ComMgr.uninitialize();
		}
	}

IEに対する自動操作本体

	public static void execute() {
		// IE取得
		IERobot robot = IERobot.findIE("Yahoo! JAPAN");
		if (robot != null) {
			System.out.println("Yahooウィンドウ発見!");
		} else {
			robot = IERobot.create();
			if (robot == null) {
				throw new RuntimeException("IE生成失敗");
			}
			System.out.println("IE新規作成");
		}

		try {
			execute(robot);
		} finally {
			// 最後にIERobotを明示的に破棄する必要がある
			robot.dispose();
		}
	}
	public static void execute(IERobot robot) {

		// デバッグの為の設定
		robot.setRethrow(true);   // 発生した例外をそのままスローする
		robot.setDebugMode(true); // 例外発生時にスタックトレースをコンソール出力する

		// IEを表示して最前面に移動
		robot.setVisible(true);
		robot.setForeground();

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

		// Googleへ遷移
		robot.navigate("http://www.google.co.jp");
		robot.waitDocumentComplete(100, 80); // 100ms×80で最大8秒待つ

		// 検索文字列を入力
		// Googleの検索ボックスのnameは「q」
		IHTMLInputTextElement search = (IHTMLInputTextElement) robot.getElementByName("q", 0);
		search.setValue("Java WebBrowserコントロール");

		// サブミット
		robot.submit();
		try {
			robot.waitDocumentComplete(100, 80); // 100ms×80で最大8秒待つ
		} catch (RuntimeException e) {
			System.out.println("遷移失敗:" + robot.getTitle());
			throw e;
		}
		System.out.println("遷移成功:" + robot.getTitle());

		// リンクを表示してみる
		List links = robot.getLinkList();
		for (int i = 0; i < links.size(); i++) {
			IHTMLAnchorElement a = (IHTMLAnchorElement) links.get(i);
			System.out.println("[" + i + "]" + a.getInnerText());
			System.out.println(a.getHref());
		}
	}

ラジオボタンユーティリティー

ラジオボタンを操作するユーティリティーを作りました。[2007-10-24]

	// Googleの「日本語」を選択する例
	public void sampleRadio(IERobot robot) {
		IHTMLInputRadioUtil r = robot.getRadioByName("lr");

		r.setChecked("lang_ja", true);
	}

コンボボックス・リストボックスユーティリティー

コンボボックス・リストボックス(selectタグ)を操作するユーティリティーを作りました。[2007-10-24]

	public void sampleComboBox(IERobot robot) {
		IHTMLSelectUtil combo = robot.getSelectByName("combo1", 0);

		combo.setSelected("値");
		System.out.println(combo.getSelectedIndex());
	}

テキスト入力ユーティリティー

inputタグの値を入出力するユーティリティーを作りました。[2007-10-28]

inputタグに対して値を入出力する基本は、setValue()とgetValue()です。これは単純なのでユーティリティーなど特に必要としません(爆)
しかしファイルアップロードの為のファイル名選択(type="file"のinput)では、getValue()で値を取ることは出来てもsetValue()で値を設定することができません(エラーにはならないが実際にはセットされない)。
ウィンドウの外部から文字を送信することによって値を設定することは出来るので、それがこのユーティリティーの使用目的です。

	public void sampleInputFile(IERobot robot) {
		IHTMLInputValueUtil file = robot.getInputByName("file1", 0);

		file.sendValue("C:\\temp\\test.txt");
		System.out.println(file.getValue());
	}

ファイルダウンロードユーティリティー

IEでファイルをダウンロードする為のユーティリティーを作りました。[2007-11-01]
(動作確認はIE6 SP2)

IEでzipやlzh等のファイルにリンクされているURLをクリックすると、ファイルのダウンロードになります。
この際、ファイルを開くか保存するか確認するダイアログや、保存場所を指定するダイアログダウンロード状況を示すダイアログなど、色々なダイアログが開きます。
当ユーティリティーでは、これらのダイアログのボタンを自動的にクリックしてファイルのダウンロードを行います。
ダイアログの操作ばっかりだから、WebBrowserコントロールとはほとんど全く無関係^^;

ついでに、セキュリティーの警告としてダウンロードをブロックして「ダウンロードするかどうか」を確認する帯(実体はボタン)も“ダウンロード可”に設定できるようにしました。(ちなみに“ダウンロード不可”に設定する方法はよく分からないので未実装(爆))
ある意味とっても危険なツールになっちゃったかも?(苦笑)

	public void sampleDownload(IERobot robot) {
		// ダウンロードサイトへ移動
		robot.navigate("http://www.ne.jp/asahi/hishidama/home/soft/java/hmwin32.html");
		robot.waitDocumentComplete(100, 80);
		robot.setForeground();

		// ファイルのリンクをクリック
		clickTargetLink(robot, "hmwin32.zip");

		for (int i = 0; i < 100; i++) {
			// ダウンロードをブロックされたかどうか確認
			boolean block = robot.isBlockingFileDownload();
			System.out.println("ダウンロード拒否状態:" + block);
			if (block) {
				// ブロックされた時はダウンロードを許可に設定して
				robot.setBlockingFileDownload(true);
				robot.waitDocumentComplete(100, 10);
				// 再度リンクをクリック
				clickTargetLink(robot, "hmwin32.zip");
			}

			// ダウンロード用ダイアログを探す
			FileDownloadProgressDialog pd = findDownloadDialog(robot, "hmwin32.zip");
			if (pd != null) {
				// ダウンロードダイアログが見つかった場合
				System.out.println("dialog :" + pd);
				System.out.println("close  :" + pd.isCloseDownloaded());
				// pd.setCloseDownloaded(false);
				// System.out.println("confirm:" + pd.getConfirmDialog());
				// System.out.println("save   :" + pd.getSaveDialog());

				// ダウンロード実行
				FileDownloadRobot dr = new FileDownloadRobot(pd);
				dr.download(FileDownloadRobot.CONFIRM_SAVE, "C:\\hmwin32.zip", 10 * 1000);
				return; //ダウンロード完了(タイムアウト時はTimeoutException)
			}

			robot.delay(100);
		}
		System.out.println("ダウンロード失敗><");
	}
	// 指定されたファイルのリンクをクリックする(ファイルのダウンロード用ダイアログが開くことを想定している)
	public void clickTargetLink(IERobot robot, String fliename) {
		List list = robot.getLinkList();
		for (int i = 0; i < list.size(); i++) {
			IHTMLAnchorElement a = (IHTMLAnchorElement) list.get(i);
			String href = a.getHref();
			if (href != null && href.endsWith(fliename)) {
				a.click(); // 見つけたリンクをクリック!
				robot.delay(500);
				return;
			}
		}
		throw new RuntimeException("link not found: " + filename);
	}
	// 指定されたファイル名の「ファイルのダウンロード」ダイアログを探す
	public FileDownloadProgressDialog findDownloadDialog(IERobot robot, String filename) {
		List list = robot.getFileDownloadDialog();
		for (int i = 0; i < list.size(); i++) {
			FileDownloadProgressDialog pd = (FileDownloadProgressDialog) list.get(i);
			if (filename.equals(pd.getName())) {
				return pd;
			}
		}
		return null;
	}

JNI側(VC++)のダイアログ技術


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