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);
ネイティブ関数呼び出し
LARGE_INTEGER frequency = new LARGE_INTEGER(); boolean isValid = QueryPerformanceFrequency(frequency);
実行
クラスパスにjna.jarとplatform.jarを含め、実行します。
高精度タイマーの周波数は14318180[Hz]です。
と表示されればOKです。