2010年10月31日日曜日

Eclipse CDT と MinGW (64ビット環境)で JNI のデバッグ

ふと、Eclipse で JNI 呼び出し DLL のデバッグがしたくなった。
つまんない事で嵌ったので、備忘録として書いておく。

検証環境:
  • Windows Vista Business (64ビット) SP2
  • Eclipse classic 3.6.1(64ビット) Build id: M20100909-0800
  • Eclipse CDT 7.0.1.201009141542
  • JDK 1.6.0_16(64ビット)
  • MinGW-w64 (mingw-w64-bin_i686-mingw_20100702_sezero.zip)
  • GDB (x86_64-w64-mingw32-gdb-7.1.90.20100730.zip)
  • Make (make-3.82-20100827.zip)

以前に、CDT の設定をして、うまく動いていたと思ったのに、久しぶりに Eclipse を起動したら、MinGW を認識してくれなった。CDT のアップデートを行ったからだろうか?
しょうがないので、サポート外Toolchain から MinGW を選択した。path に MinGW/bin が設定されていれば、デフォルトの include ディレクトリや lib ディレクトリは自動的に設定されるようだ。
まあ、ここら辺の調査は宿題としておこう。

まあ、何事にも共通するが、初めてやるものは、最小限の状態から試すことが大事。

JNI で Java ソースと C++ のソースを作成済みとする。
プロジェクトは、Java と C++ で分ける必要がある。Java プロジェクトには、JNI インターフェースを作成するソースを格納。これは、そのままテスト駆動用とする。C++ プロジェクトには、JNI インターフェースのヘッダファイルと、ヘッダに定義されている関数の実装を記述したソースを格納。

C++ プロジェクトの設定

C++ プロジェクトのプロパティを開く
「C/C++ Build」-「Settings」で「Tool Settings」タブを開く

「GCC C++ Compiler」で以下を設定
  • 「Preprocessor」の「Defined symbols(-D)」に”_JNI_IMPLEMENTATION_”を追加。これは、jni.h の中に定義されている。未設定でも動いたが、ヘッダの該当箇所あたりに記述してあるメソッドを利用するときは設定する必要があるのかもしれない。
  • 「Includes」の「Include paths(-I)」に JDK をインストールしたディレクトリの include と include/win32 を追加。
  • 「Debugging」の「Debug Level」を”Default(-g)”に設定。

「All options」を見るとこんな感じ
-D_JNI_IMPLEMENTATION_ -I"C:\appli\Java\jdk1.6\include" -I"C:\appli\Java\jdk1.6\include\win32" -O2 -g -Wall -c -fmessage-length=0


「MinGW C++ Linker」で以下を設定
  • 「Miscellaneous」の「Linker flags」に”-static”と”--kill-at”を追加。static は、静的リンクをしたい場合につける。kill-at はシンボルの後ろに @ を付けたくないときに付ける。両方とも無くてもかまわない。たぶん。
  • 「Shared Library Settings」の「Shared(-shared)」チェックボックスをチェック

「All options」を見るとこんな感じ
-static --kill-at -shared


「Settings」画面の「Build Artifact」タブで以下を設定
  • 「Artifact Type」を”Shared Library”を設定。ただ、この設定がどこに影響があるのか不明。
  • 「Artifact name」を”${ProjName}”に設定。出力ファイルのベースファイル名になる。
  • 「Artifact extension」を”dll”に設定。出力ファイルの拡張子になる
  • 「Output prefix」を空にする。出力ファイル名の先頭に付加される。

以上で C++ プロジェクトへの設定は完了
ビルドすると、Default ディレクトリに dll ファイルが生成されるはず。

Java デバッグの設定
  • DLL 読み込みのために以下のいずれかのように DLL のディレクトリを設定する。
    • 「VM arguments」に”-Djava.library.path=../hello-jni/Default”のように指定。
    • 「Environment」に”PATH”を追加して、”../hello-jni/Default”のように指定。


C++ アタッチデバッグの設定
  • C++ プロジェクトをクリックして、選択状態にする。
  • 「Run」メニューから「Debug Configurations...」を選択。
  • 「C/C++ Attach to Application」 を右クリックして、コンテキストメニューから「New」を選択。
  • 作成されたデバッグ設定をクリック。
  • 「Main」タブの「C/C++ Application」が”Default\ DLL ファイル名”、「Project」がC++ プロジェクト名になっていることを確認。
  • 「Close」ボタンを押す。

デバッグ開始


デバッグを開始する手順は次のようにする。
  • Javaのソースにブレイクポイントを設定。ただし、 System.loadLibrary で 作成した DLL のロード後の場所に設定すること。
  • Java のデバッグを開始。
  • C++ のデバッグを開始。アタッチするプロセス ID を設定するダイアログが開くので、上で実行したプロセス(javaw.exe)の ID を tasklist コマンドか、タスクマネージャで調べて入力する。javaw.exe プロセスは、eclipse 本体もあるので、間違って eclipse 本体のプロセスを指定しないこと。
  • C++ ソースにブレイクポイントを設定する。ここ重要、必ず C++ デバッグ開始直後の状態(GDB が起動して、一時停止の状態)でブレイクポイントを設定すること。ブレイクポイントを解除しなくても無効にして有効にすれば問題ない。ブレイクポイントのマークをよく見ると、有効な場合は、丸の上にチェックが付いている。無効な場合には、丸だけ。さらに、ソース上のブレイクポイント行に!マークが付き”Breakpoint attribute problem: installation failed”と表示される。
  • C++ デバッグを再開(Resume(F8))する。再開後に C++ のブレイクポイントの設定はできない。C++ のブレイクポイントで一時停止した状態にならないとブレイクポイントの設定はできない。
  • Java デバッグを再開すると C++ のブレイクポイントで停止する。はず。


いろいろやってると、eclipse が反応しなくなるときがある。そんなときは、GDB のプロセスが残ってる場合があるので、taskkill コマンドかタスクマネージャから gdb.exe プロセスを停止するといい。