top / index / prev / next / target / source

2005-12-28 diary: SWTを用いた JavaからWin32APIへのアクセス サンプル

いがぴょんの日記 日記形式でつづる いがぴょんコラム ウェブページです。

old-v2

SWTを用いた JavaからWin32APIへのアクセス サンプル

SWTを用いると 各種Win32APIを呼び出しできることを知りました。びっくりしました。極めつけは Javaからレジストリへのアクセスが可能であるということです。

SWTを用いた JavaからWin32APIへのアクセス

いがぴょんの日記メーリングリストに流れていた会話の流れから、SWTを用いると 各種Win32APIを呼び出しできることが明らかになりました。これには全くもってびっくりしました。この事実は SWTに濃密にハマっている人の間でしか知られていない、しかし非常に重要な機能だと思います。

この使い方の発見にいたる経緯はこうです。

私の方で動作することを確認した環境は下記のようになります。

純粋に JavaからSWTを用いるために、下記の設定を行いました。(Eclipse RCPを駆動させるための一般的な設定と同様のものです)

ここでポイントなのは、Win32API呼び出しを行うのにもかかわらず、自前では JNIを全く作成していないという点です。話を進める過程で C言語コンパイラを全く必要としません。SWTに含まれるAPIで全て代用してしまっているのです。

まず最初のサンプルは、Windowsに 現在表示されているウインドウの中から タイトルメッセージをもとにウインドウハンドルを取得し、そのウインドウのサイズを変更し、またウインドウに文字を送出するサンプルです。(文字の送出については、ウインドウハンドルと実際の文字エリアとが一致しているアプリでのみ動作するようです) SwtSample1.java

/**
 * SWTを用いた Javaからウインドウ検索をおこなうサンプルプログラム。
 */
import java.util.Properties;

import org.eclipse.swt.internal.win32.OS;
import org.eclipse.swt.internal.win32.POINT;
import org.eclipse.swt.internal.win32.RECT;
import org.eclipse.swt.internal.win32.TCHAR;
import org.eclipse.swt.internal.win32.WINDOWPLACEMENT;

/**
 * SWTを用いたウインドウ検索のサンプルプログラム。
 * 
 * @author 渡辺義則
 */
public class SwtSample1 {
    /**
     * SWTを用いて JavaからWin32API呼び出しをおこないます。
     * 
     * 実行に先立ち「秀丸エディタ」を起動しておきます。<br>
     * ウインドウタイトルが「(無題) - 秀丸」の状態にしておきます。ウインドウは「非最大化」にします。<br>
     * 
     * 実行すると ウインドウのサイズが変更され、またエディタ上に Helloを入力された状態に変わります。
     */
    static void process() {
        int hWnd = OS.FindWindow(null, new TCHAR(OS.CP_INSTALLED, "(無題) - 秀丸",
                true));
        if (hWnd == 0) {
            System.out.println("ウインドウの検索に失敗しました。処理中断します。");
        } else {
            RECT rect = new RECT();
            OS.GetWindowRect(hWnd, rect);
            System.out.println("rect=" + rect.left + " " + rect.top + " "
                    + rect.right + " " + rect.bottom);
            RECT client = new RECT();
            OS.GetClientRect(hWnd, client);
            POINT pt = new POINT();
            OS.ClientToScreen(hWnd, pt);
            System.out.println("client pos=(" + (pt.x - rect.left) + " "
                    + (pt.y - rect.top) + ") size=(" + client.right + " "
                    + client.bottom + ")");
            // ウィンドウのサイズを変更します。
            WINDOWPLACEMENT wndpl = new WINDOWPLACEMENT();
            OS.GetWindowPlacement(hWnd, wndpl);
            // ウインドウの大きさを縦方向に 100 縮めます。
            wndpl.bottom -= 100;
            OS.SetWindowPlacement(hWnd, wndpl);
            // アプリケーションにキーイベントを送ります。
            OS.SendMessage(hWnd, OS.WM_CHAR, 'H', 0);
            OS.SendMessage(hWnd, OS.WM_CHAR, 'e', 0);
            OS.SendMessage(hWnd, OS.WM_CHAR, 'l', 0);
            OS.SendMessage(hWnd, OS.WM_CHAR, 'l', 0);
            OS.SendMessage(hWnd, OS.WM_CHAR, 'o', 0);
        }
    }

    public static void main(String[] args) {
        // 実行時ライブラリへのパスを確認
        Properties props = System.getProperties();
        System.out.println(props.getProperty("java.library.path"));

        // SWTによるWinAPIコールを試みます。
        process();
    }
}

次のサンプルは、Windowsのデバイスコンテキストを用いて描画を行うサンプルです。画面上に黒い線が ずずずっと描画されます。(驚かれませぬよう。) SwtSample2.java

/**
 * SWTを用いたJavaからGDI描画をおこなうサンプルプログラム。
 */
import org.eclipse.swt.internal.win32.OS;

/**
 * SWTを用いたGDI描画のサンプルプログラム。
 * 
 * @author 渡辺義則
 */
public class SwtSample2 {
    /**
     * SWTを用いて Javaから Win32API呼び出しをおこないます。
     * 
     * 実行すると 画面上に黒い線がたくさん描画されます。
     */
    static void process() {
        int hDC = OS.GetDC(0);
        try {
            for (int i = 0; i < 1000; i += 5) {
                OS.MoveToEx(hDC, 0, i, 0);
                OS.LineTo(hDC, 1000, 1000 - i);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            OS.ReleaseDC(0, hDC);
        }
    }

    public static void main(String[] args) {
        // SWTによるWinAPIコールを試みます。
        process();
    }
}

最後のサンプルは衝撃的です。Windowsのレジストリにアクセスして、DirectXのバージョンを取得するというものです。 SwtSampleReg.java

/**
 * SWTを用いた Javaからレジストリをアクセスするサンプルプログラム。
 */
import java.io.StringWriter;

import org.eclipse.swt.internal.win32.OS;
import org.eclipse.swt.internal.win32.TCHAR;

/**
 * SWTを用いて Java言語から レジストリへのアクセスを行います。<br>
 * ここではDirectXのバージョンを取得するサンプルとなっています。
 * 
 * @author 渡辺義則
 */
public class SwtSampleReg {
    /**
     * SWTを用いた Javaからレジストリにアクセスして DirectXのバージョンを取得するサンプル
     */
    static void process() {
        final String entry = "SOFTWARE\\Microsoft\\DirectX";
        final int[] hKey = new int[1];
        try {
            int rc = OS.RegOpenKeyEx(OS.HKEY_LOCAL_MACHINE, new TCHAR(
                    OS.CP_INSTALLED, entry, true), 0,
                    0xF003F/* KEY_ALL_ACCESS */, hKey);
            if (rc != 0) {
                throw new IllegalArgumentException("RegOpenKeyExの呼び出しに失敗しました。"
                        + formatMessage(rc));
            }
            TCHAR buf = new TCHAR(OS.CP_INSTALLED, 256);
            final int[] len = new int[] { 256 };
            rc = OS.RegQueryValueEx(hKey[0], new TCHAR(OS.CP_INSTALLED,
                    "Version", true), 0, null, buf, len);
            if (rc != 0) {
                throw new IllegalArgumentException(
                        "RegQueryValueExの呼び出しに失敗しました。" + formatMessage(rc));
            }
            System.out.println("レジストリ上のDirectXのバージョンは["
                    + buf.toString(0, buf.strlen()) + "]です。");
        } finally {
            if (hKey[0] != 0)
                OS.RegCloseKey(hKey[0]);
        }
    }

    /**
     * OSから与えられた戻り値をもとにメッセージを取得します。
     * 
     * @param rc
     *            Win32APIから返却されるDWORD値
     * @return OSから得られたメッセージ
     */
    public static String formatMessage(final int rc) {
        final int[] lpMsgBuf = new int[2048];
        final int retCode = OS.FormatMessage(OS.FORMAT_MESSAGE_FROM_SYSTEM, 0,
                rc, 0, lpMsgBuf, lpMsgBuf.length, 0);
        if (retCode == 0) {
            throw new IllegalArgumentException(
                    "FormatMessageの呼び出しに失敗しました。処理中断します。(" + OS.GetLastError()
                            + ")");
        }

        return lpmsgbuf2String(lpMsgBuf) + "(" + rc + ")";
    }

    /**
     * LPMSGBUFをjava.lang.Stringに変換します。
     * 
     * @param lpMsgBuf
     *            C言語上としての文字列
     * @return java.lang.String化された文字列
     */
    public static String lpmsgbuf2String(final int[] lpMsgBuf) {
        final StringWriter result = new StringWriter();
        for (int index = 0; index < lpMsgBuf.length; index++) {
            if (lpMsgBuf[index] == 0) {
                // NULLが現れたら中断。
                break;
            }
            result.write(lpMsgBuf[index]);

            if (lpMsgBuf[index] / 0x10000 == 0) {
                // NULLが現れたら中断。
                break;
            }
            result.write(lpMsgBuf[index] / 0x10000);
        }
        result.flush();
        return result.toString();
    }

    public static void main(String[] args) {
        // SWTによるWinAPIコールを試みます。
        process();
    }
}

※こちらのサンプルについては、多少 いがぴょんにより加筆が加えられています。 こんなことが Java + SWTだけで実現できるなんて全く知りませんでした。C言語コンパイラなどを一切利用していません。これは衝撃です。大村忠史さんのクエストに感謝します。そして 回答を考えている間に このような発見をされた 渡辺義則さん(A-san)は素晴らしいです。もう脱帽です。Javaと Win32API そして SWTの全てに精通されている渡辺義則さんだからこそこの解にまでたどり着くことが可能なのですもの。

渡辺義則さん(a-san)の日記のほうに、この件と同じ件に関する日記があります。もちろん a-sanの日記の方がオリジナル版となります。

そして、ここまで書いた後で 日記にも記載があると気がつきました (苦笑)

いずれにしても SWT経由で Win32APIを呼び出すことが出来るという この発見は 重要なものであると考えます。このAPIは SWTパッケージ内の更にinternalパッケージ内に位置づけられているので、Eclipseがバージョンアップする際などに仕様が変更される可能性はあります。しかしそれを差し引いても 自前でJNIを使うのに比べて遙かに安全で そして手軽さがあるので、レジストリアクセスなどの必要がある場合には積極的に利用していきたいと思います。

関連しそうなリソース

関連する日記

SWTを用いたリッチクライアント時代のセキュリティ観点

SWTを用いると .NET Frameworkで実現できるような Windowsアクセスの機能性の多くが Javaで実現できることになります。一方で SWTでここまでの機能性が実現出来てしまうのだとすると、SWTを用いたリッチクライアント時代では SWTに関するセキュリティに 関心を持ち始める必要があることに気がつかされました。SWTを用いると確かに非常に多くの機能性を得ることが出来ます。しかし一方で SWTの機能性ゆえにセキュリティには気をつかわなくてはならないのです。何年後かにはこの観点は重要なものになっていくことでしょう。あるいは SWTにもセキュリティポリシーの機能性が実装されるべきなのかも知れません。

私が考えつく 現時点で対応しておくべき注意点は、SWTのJARやDLLを 通常のJava実行環境のパスには入れてはいけないこいうとです。Java言語の実行環境に精通している人ほど swt-win32-XXXX.dllなどの大きいファイルを 共通化してファイルサイズ削減に努めると思いますが、今まで挙げた機能がSWTに備わっている以上、意識していな場合には SWTを実行環境に加えてしまうのは大変危険です。

クラシック音楽ネタ: チャイコフスキー作曲「悲愴」

2005/12/08放送 日本テレビ(4ch) 読売交響楽団+広上淳一 を視ました。広上さんのテンポ設定などの絶妙さなどが 素晴らしいと思いました。


この日記について