私は非常に不満 (unsatisfied) です!
JNI を使用したコードのコンパイルとリンクは済んだのに、いざ使用する段になると、MRJ から UnsatisfiedLinkError が返されることがあります。他のプラットフォームでは動作済みのコードを移植した場合など、これはかなりがっかりすることです。さいわい、この問題と解決方法は、たいていの場合非常に簡単です。最もよくあるトラブルは次の通りです。
- 作成したメソッドがエクスポートされていない。
- ライブラリのコードフラグメント名が間違っている。
- ライブラリのコードが所定の場所に置かれておらず、MRJ が見つけられない。
それでは、1 つ 1 つを詳しく見ていくことにしましょう。
先頭ページに戻る
正しい項目のエクスポート
MRJ が読者のライブラリから正しい関数を見つけて呼び出しを行うには、エクスポートが必要です。ライブラリをリンクする際、リンカにどの関数をエクスポートするのかを知らせる必要があります。エクスポートの設定方法は、開発システム (おそらく MPW か CodeWarrior でしょう) のマニュアルに詳しく説明されていますが、基本的には、次の 2 つの方法があります。
.exp ファイルを使う方法:独立した「.exp」ファイルを使ってエクスポートを設定します。.exp ファイルはテキストファイルで、1 行に 1 つずつエクスポートする関数の名前を入れます。CodeWarrior を使用している場合は、プロジェクトに.exp ファイルを追加し、プロジェクト設定ダイアログの「PPC PEF」パネルへ行き、「Export」ポップアップを「Use .exp File」に設定します。MPW の ppclink ツールを使用している場合は「-@export」フラグを指定してください。
.exp ファイルを使用する場合、関数名をすべて正確に記し (ソースファイルから貼り付けるのが一番確実です)、ネイティブメソッドの追加、削除、名称変更を行ったり、パラメータを変更したら、ファイルに反映させなければなりません。
#pragma export を使う方法:C/C++の「pragma」を使ってエクスポートする関数をマークする方法で、私はこちらのほうが好みです。「#pragma export on」という行があると、コンパイラは宣言に遭遇したすべての関数をエクスポートするようマークします。「#pragma export reset」という行はこのモードをオフにします。次のように、JNI が生成するヘッダファイル (すべてのメソッドが中で宣言されている) をインクルードする間だけエクスポートモードをオンにすると便利です。
// MyNativeClass.c
#pragma export on
#include "MyNativeClass.h" // これが javah の生成したヘッダ
#pragma export reset
...関数の本体が続く...
|
エクスポートする関数をマークする方法としては他に、 に定義されている、標準の JNIEXPORT マクロと JNICALL マクロを使うものがあります。<jni.h>; JNIEXPORT は「#pragma export」をオンにするのと同じ働きがあります。このコードは次のようになります。
// MyNativeClass.c
#include "MyNativeClass.h" // これが javah の生成したヘッダ
JNIEXPORT jint JNICALL Java_com_foo_MyNativeClass_myMethod(JNIEnv* e) {
...
}
|
このあと、マークした関数をエクスポートするよう、リンカに指示する必要があります。CodeWarrior を使用している場合はプロジェクト設定ダイアログの「PPC PEF」パネルへ行って、「Export」ポップアップを「Use #pragma」に設定します。MPW の ppclink ツールを使用している場合は、マークされた関数はすべて自動的にエクスポートされます。
2 番目のテクニックを使う利点は、ネイティブメソッドの構成が変わっても、常に正しい関数がエクスポートされることです。必要な作業は javah を再実行 (CodeWarrior で JNI ヘッダを出力するオプションを使用している場合は、Java クラスを再コンパイル) するだけで、次にネイティブライブラリをビルドする際、エクスポート関数のリストは自動的に更新されます。
先頭ページに戻る
ライブラリ名の付け方
デベロッパが犯す 2 番目の大きな間違いは、ライブラリの命名が不正確なことです。ライブラリの名前は System.loadLibrary へ渡す名前と厳密に同じでなければなりません。(Metrowerks の Java VM はライブラリ名の最初に“java_”が必要ですが、MRJ では必要ありません。)
さらに、ライブラリの名前はライブラリのファイル名と同じものではありません。ファイル名は無視されます。重要なのはライブラリ名 (または「フラグメント名」) です。CodeWarrior ではこれは「PPC PEF」プロジェクト設定パネルで設定します。MPW の ppclink では「CODE>-fragname」フラグを使ってください。
注意:
MRJ 2.0 および MRJ 2.1 の早期提供版のなかには、ライブラリ名に非アスキー文字セット (アクセント付きの文字や登録商標マークなどの記号) を使うとうまく処理できず、間違った文字に変換されていました。この問題は MRJ 2.1 で解決済みです。
|
先頭ページに戻る
ライブラリの配置
最後に、ライブラリのビルドが完全に行われても、MRJ がそれを見つけられなければ仕方がありません。ライブラリファイルは CFM の検索パスに配置しなければなりません。基本的には、次の場所に配置する必要があります。
- アプリケーション自体の内部
- アプリケーションと同じフォルダ
- 機能拡張フォルダ
- 機能拡張フォルダ直下のフォルダ。例えば、MRJ Libraries フォルダ。直下のフォルダより下のフォルダは検索されません。したがって、MRJ Libraries のサブフォルダは検索されません。よくある失敗は、ライブラリを MRJClasses フォルダに配置してしまうことです。
「アプリケーション」とは、もちろん、JBindery で生成したアプリケーション、Microsoft Internet Explorer、Apple Applet Runner などです。(プロジェクトをアプリケーションとして保存せず、JBindery から直接コードを実行している場合、「アプリケーション」は JBindery そのものを示します。)
特定のアプリケーションでしか使用しないネイティブコードのライブラリは通常そのアプリケーションと同じフォルダに配置します。ネイティブコードが、MRJClasses フォルダにインストールした共有 Java ライブラリの一部である場合、ネイティブライブラリは (前述した理由から、MRJClasses フォルダではなく) MRJ Libraries フォルダに配置しなければなりません。
アプリケーションの内部にコードフラグメントを埋め込むのは、特に VFS (Virtual File System) と組み合わせた場合、アプリケーション全体を 1 つのファイルに入れることができるため、魅力的です。データフォークはすでに VFS に使用されているので、コードフラグメントの配置に最適な場所はリソースです。リンカにはコードリソースを生成するよう指示し、そのリソースをアプリケーションにコピーして、アプリケーションの‘cfrg’リソースに新しいエントリを追加し、そのコードフラグメントを指すようにします。
先頭ページに戻る
ロード処理のデバッグ
これまで説明した方法にすべてしたがったのに、どうしてもネイティブライブラリがロードできない場合、ロード処理のいくつかをデバッグする方法があります。(このためには MacsBug がインストールされていなければなりません。「Technote 1154:MacsBug で Java コードをデバッグする方法」に Java デベロッパ向けの MacsBug 入門があります。)
- アプリケーションがネイティブライブラリをロードしようとする前に、command キーを押しながら電源キーを押して、MacsBug に入ります。
- 次のコマンドを入力します。
tvb GetSharedLibrary ';dm r3 pstring'
それから「g」を入力してアプリケーションを続行します。
- 次に MRJ がネイティブライブラリをロードしようとする箇所で、ブレークポイントに当たり、MacsBug に入ります。MacsBug のコンソール表示に「
PowerPC TVector Break At GetSharedLibrary」という行が表示されており、その次の行にロード中のライブラリの名前が表示されてるはずです。MRJ は起動時に多数のライブラリをロードするため、これが懸案のライブラリとはかぎりません。そうでない場合は「g」を入力してアプリケーションを続行し、再びこの「3」の手順にしたがってください。
- 見つかったライブラリが読者の作成したものであったら、ライブラリ名の記述が IDE またはリンカに指定したものと正確に同じか、もう一度チェックしてください。もし違っていたら、一致させる必要があります。最後の「10」の手順を行ってから、ビルドし直し、もう一度試してみてください。
- 「
r8」を入力して R8 レジスタの値を表示します。この値は後で必要になります。
- 「
gtp lr」を入力して、GetSharedLibrary を飛び越します (ステップオーバー)。
- 「
error r3」を入力して、返されたステータスを調べます。ゼロ (noErr) の場合、ライブラリは無事にロードされ、初期化されています。こうなると、問題はエクスポート指定がなかったためということになります。
- エラーがゼロでなかった場合は、それはおそらく CFM エラーの範囲、
-28xx にあります。エラーコマンドを実行すると簡単な説明が表示されますが、詳細は『Inside Macintosh: PowerPC System Software』を参照してください。特によくあるエラーは -2804 (fragLibNotFound) で、読者のライブラリか、それをインポートするライブラリが見つからなかったことを表します。
- このエラーは、読者の共有ライブラリを指す場合と、それをインポートする別のライブラリを指す場合とがあります。「5」の手順で入力した「
r8」コマンドを思い出してください。ここで「dm XXXX pstring」を入力してください。「XXXX」は「r8」コマンドで返された 16 進数のレジスタの値です。ここで表示される文字列が、エラーの原因となったライブラリの名前です。
- 最後に「
tvc」でブレークポイントを解除し、「g」でアプリケーションを続行します。
先頭ページに戻る
参考文献
先頭ページに戻る
|