torutkのブログ

ソフトウェア・エンジニアのブログ

Windows APIをJavaから呼び出す(JNA)

昨日の日記(id:torutk:20121019)で、nativeメソッドSystem.nanoTime()のWindowsでの実装を調べ、その実装で使用しているWindows API QueryPerformanceCounterをC/C++コードで動かすことで分解能(周波数)とAPIの処理時間を取得しました。

そのためには、Windows APIを呼び出せるC/C++開発環境*1をセットアップする必要があります。また、Javaにはネイティブコード(C/C++)を呼び出すためのJNIという仕組みが標準装備されていますが、これもC/C++開発環境が必要となります。

そこで、既存のCライブラリ(Windows APIなど)をJavaから直接呼び出すことができるJNAライブラリを使用してみました。JNAを使えば、JNIのようにC/C++コードを書かなくて済みます。

JNAライブラリの入手

JNAライブラリのプロジェクトサイトは現時点では次のURL(GitHub上におかれている)のようです。

元々は、java.netのプロジェクトだったのですが、SunがOracleに買収された後のエンジニアンリグ系公開サイトの混乱で、GitHubに移ったようです。

とりあえずビルド・実行に必要なライブラリファイルは次の2つです。

  • jna.jar
  • platform.jar

この2つのファイルは、上述URLにアクセスし、リポジトリトップディレクトリのファイル一覧の下にあるREADME文章の途中、"Download"節にリンクがあります。本日時点では、Ver.3.5.0となっています。

これを、C:\Program Files\Java\jna-3.5.0\などのフォルダを作成しその中にコピーします。

Java API Docやソースなど一式をダウンロード

上述URLのページ上部右にある[Tags]というリンクを辿ります。この[Tags]の隣に[Downloads]というリンクがありますが、こちらを辿るとGitHubリポジトリの最先端を一式ダウンロードするときのリンクです。バージョン番号が付与されたリリース物一式をダウンロードするときは[Tags]です。

[Tags]を辿ると、バージョン番号ごとに.zipファイルが一覧されています。本日時点では最新は[3.5.0.zip]となっているのでこれをダウンロードします。

ダウンロードしたzipファイルを開くと、distディレクトリの下にjna.jar、platform.jarが含まれています。

JNA経由でQueryPerformanceCounterを呼び出すJavaコード

JNAでは、Cの関数を呼び出す2つの方式があります。

今回は、ダイレクト・マッピングを使用しました。

まずコードの全貌
import com.sun.jna.Native;
import com.sun.jna.Structure;

public class QueryPerformanceCounterSample {
    static {
        Native.register("Kernel32"); // ネイティブライブラリのロード
    }

    // Windows API 'QueryPerformanceFrequency'で使用する型LARGE_INTEGER(unionのtypedef)のJava対応型定義
    public static class LARGE_INTEGER extends Union {
        public long QuadPart;
    }
    
    // Windows APIのQueryPerformanceFrequencyを呼び出すnativeメソッド定義
    public static native boolean QueryPerformanceFrequency(LARGE_INTEGER frequency);

    public static void main(String[] args) {
        LARGE_INTEGER frequency = new LARGE_INTEGER();
        boolean isValid = QueryPerformanceFrequency(frequency);
        if (!isValid) {
            System.err.printf("このプラットフォームは高精度タイマーをサポートしていません。%n");
            return;
        }
        System.out.printf("高精度タイマーの周波数は%d[Hz]です。%n", frequency.QuadPart);
    }
}
ネイティブライブラリのロード

JNAでは、まずネイティブライブラリをロードする必要があります。ライブラリファイルのサーチパス(Windowsの場合、環境変数PATHおよびカレントディレクトリ)に置かれている場合、ファイル名(拡張子の.dllは不要)を指定します。

    static {
        Native.register("Kernel32"); // ネイティブライブラリのロード
    }
ネイティブ関数の引数・戻り値に使用するユーザー定義型(構造体、共用体)の定義

LARGE_INTEGERはunion型で64bit整数を上位/下位32bitずつアクセスできる構造となっています。

typedef union _LARGE_INTEGER {
    struct {
        DWORD LowPart;
        LONG HighPart;
    } u;
    LONGLONG QuadPart;
} LARGE_INTEGER;

JNAでは、Cのunion型に対するマッピングとして、com.sun.jna.Unionを用意しています。
Java側では上位/下位32bitずつアクセスする必要はないので、64bit整数のフィールドだけ定義することにします。

    public static class LARGE_INTEGER extends Union {
        public long QuadPart;
    }
ネイティブの関数に対応したnativeメソッドの定義

ネイティブコードの関数に対応するnativeメソッドを定義する必要があります。ここで定義するnativeメソッドがJNAのマッピングルールに基づきネイティブの関数定義に変換され、ロードしたネイティブライブラリの中から検索されます。適合するネイティブ関数がない場合、実行時にエラーとなります。

Windows APIのQueryPerformanceFrequencyは修飾(__declspecや__stdcall)を除くと次の定義となっています。

BOOL QueryPerformanceFrequency(
    LARGE_INTEGER *lpFrequency
);
  • BOOLはintのtypedefで、0か1を取ります。
  • LARGE_INTEGERは上述のとおりで、関数の実行結果を詰めるためポインタ渡しになっています。

このCの関数は次のようにJavaのnativeメソッドで定義します。

    public static native boolean QueryPerformanceFrequency(LARGE_INTEGER frequency);
  • 戻り値BOOLは、intでもbooleanでもマッピングできるようですが、ここでは意味を重視してbooleanにしています。
  • Cでのユーザー定義型のポインタ渡しは、そのままJavaマッピングできます。なお、Cでのユーザー定義型の値渡しは一工夫が必要になります。
  • メソッド名は、大文字・小文字を含めてCの定義に合わせます。
ネイティブ関数呼び出し
        LARGE_INTEGER frequency = new LARGE_INTEGER();
        boolean isValid = QueryPerformanceFrequency(frequency);
実行

クラスパスにjna.jarとplatform.jarを含め、実行します。

高精度タイマーの周波数は14318180[Hz]です。

と表示されればOKです。

*1:WindowsでのC/C++開発環境は、有償のVisual Studioだけでなく、無償のツールとしてVisual Studio Express、およびIDE統合開発環境)が付いていないコマンドラインコンパイラを含むWindows SDKなどがあります。