前回の続き、のようなもの。
JNAとJNIが大きく異なるポイントのひとつは、C言語で生成されたglue codeの有無だ。glueが無いJNAでは、dlopen等を経由して得られた関数ポインタとJavaメソッドの引数リストから、Cの関数呼び出しを行い、戻り値を自動でlava.lang.Object(JNIのC APIで言えばjobject)に変換しなければならない。
戻り値は1つなので、Java上で型を見て対応するnativeメソッドを条件分岐で呼び出すのも難しくないけど、引数は組み合わせが無限にあるので、そのようなことは出来ないので、Javaの引数リストjobjectArrayから、Cの引数リストを自動生成する必要がある。
jobjectArrayからjobjectの要素を取得するのはもちろん可能だし、各要素の型はjobjectのクラスを見て、BooleanやIntegerなどプリミティブな型とマッチングすれば良いので、それぞれの引数に対応するCの変数値を取得することは可能だ。しかし、それら取得された値からCの引数リストを生成する方法は自明ではない。
関数ポインタを経由した呼び出しについては、戻り値型は1つしか無いので、プリミティブな戻り値型に合わせて個別に定義すれば足りるし、引数リストは定義レベルでは省略することができるのでそれも問題ないのだけど、実際に関数呼び出しの際に渡す引数リストは、やはり実行時に生成しなければならない。
Cの可変長引数というとva_listが思い浮かぶけど、va_listで出来るのは可変長引数からの値の取得のみで、可変長引数リストの自作はサポートしていない。今回はjobjectArrayの内容を個別に解釈した結果をリストにしなければならないので、これでは実現できない。
実のところ、これは一般的な問題となっているようだ。stackoverflowに、この辺の問題を議論するスレッドがいくつかある: 例1, 例2
一般的には、コンパイラやアーキテクチャに依存した方法で、引数リストを生成しなければならないようだ。そしてそのためのライブラリがいくつか作られている。とりあえず、以下のような理由もあって、dyncallを使ってみることにした。
- dyncall - 割と最近のもの
- C/Invoke - やや古いライブラリで、ARMアーキテクチャがサポートされていない
- ffcall - GPLで提供されているので、幅広い組み込み用途には厳しいものがある
dyncallを使ってネイティブコードを実装したら、とりあえずlibdlとdyncallを使った自身のJNI用共有ライブラリのみで、Javaからlibcの関数を呼び出すことが出来たので、ひとつの目的は達成した。Javaコード, Cコード
ただ、dyncallを調べている過程で、bridjに行き着いて、これが同じようにdyncallを使ったinteropを実現していて、androidも最近カバーするようになったようなので、自前で実装する必要はない気がしてきている。もともとJNA実装はネイティブライブラリをandroiderにビルドさせるための釣り針みたいなものなので、自前で実装する必要が必ずしもあるわけではないのだ。JNAを使用した既存のライブラリがもし大量にあれば、それはなお移植しやすいかもしれないけど、そのニーズはあまり無いようにも思う。