June 2011 Archives

missing struct by value support in bridj

| No Comments | No TrackBacks

bridjのandroid対応は前回までで完了して、新バージョンのリリースも公開されたので、今度は別の方面で足りない部分をどうにかしようとしている。まず気づいたのが、tremoloのov_open_callbacks()が正しく呼び出せないということだ。どうやら、この引数にov_callbacks構造体がポインタなしで入っているのが引き金になっているようだ。現在のbridjはどうやらなまの構造体を引数や返り値に使用できないようだ(構造体へのポインタはOK)。limitationsに列挙されていた。

サポートされていないなら動かないのが想定通りの動作だ、と分かったので、そうなると実装する作業が必要になってくる。というわけで、今日はどの辺から着手すれば良いのかを探ることになった。それでソースを追っていて分かったのだけど、もしかしたら面倒なのかもしれない。

そもそも、ネイティブコードの呼び出しで使用されているdyncallには、構造体引数を処理するコードが存在していなかったようだ。structまわりをサポートするコードが、bridjの作者自身によってcontributeされていることが分かった(しかもほんの2ヶ月くらい前の話だ)。そしてそのコード自体、どうも不完全な状態で放置されている感じだ。そもそもdyncallに追加されたコードが、bridjから呼び出されていない。設計に問題があったのか、途中で放置しているのかもよく分からない。

structのparameter by valueとreturn by valueが面倒なのは、ひとつには構造体がJNIEnvで使用できる型になっていないということがある(Javaに無い; ポインタは単なる整数としてjlongで返せる)。JNAではサポートしているようだけど、これは多分JNIでは対応する値を含むMemoryを返すようにして、Javaコード側でフィールドにマッピングしているのだろうと思う(JNAのソースは読まないことにしているので、実際にはどうだか分からない)。構造体用にJavaオブジェクトを生成してメモリを読まなければならないとしたら不要なコストがかかっているのかもしれない。そもそも、C#なら値渡しはスタックで参照渡しはヒープと単純に割り切れるけど、ここではそうはならないかもしれない。

作者に聞いてみようと思うけど、もう少しソースを読み込んで分かってる面をしてから臨みたいと思っている。

sf2xrniのrenoise 2.5対応

| No Comments | No TrackBacks

最近になって、しばらく前に作って公開してそれっきりだったsf2xrniを使う人が出てきて、期待通りに動作してくれないというので、いろいろフィードバックを受けて一時期は毎日のようにマイナーリリースを出していた。

特に大きかったのは、Renoise 2.5に合わせて、使用しているNRenoiseToolsがいろいろ書き換えられたのと、それに伴ってファイル変換まわりで書かなければならないコードが少し増えたところだ。とはいえ、この作業自体は機能拡張を伴うことが無かったので、あくまで必要な修正にとどまった。

Renoise 2.7になってからは、velocityに合わせて再生するサンプル音声を切り替える機能が実装されたようで、sf2の同機能をsf2xrniでも対応してほしいというリクエストが来たのだけど、まだNRenoiseToolsが2.7に対応していない。というわけで、これが対応するのを待っている状態だ。

言葉で書くと短いのだけど、NRenoiseToolsにもNAudioにもドキュメントのようなものがほとんどなく、ソースを読んで調べているので、それなりに時間がかかっている。

NRenoiseToolsは基本的にxrns/xrniファイルに含まれるxmlファイルに対応するスキーマからXmlSerializerのコードを生成している。これを書いていて今気づいたのだけど、もしかしたら単にrenoiseのリリースに含まれるスキーマをxsd/sgenに食わせてやれば、それだけで対応できるのかもしれない。まあ2.7に対応してくれというレポートはcodeplexで登録してしまったので、それだけ簡単な作業ならそのうち来るのではないかと思う(作者がrenoiseをアップデートできているうちは)。

tremolo binding on bridj

| No Comments | No TrackBacks

今日ようやく腰を落ち着けてdirentのSEGV問題とtremoloのSEGV問題に取り組んで、両方とも解決することが出来た。

direntの方は非常に単純な話で、access()関数のフラグ引数の使い方を間違えていた...ディレクトリの内容を参照しようと思ったら、X_OKだけでなくR_OKも必要だった。これは、JNI抜きでCの同等コードを書いて動かしてみて判明した。

tremoloの方はもう少しややこしかった。とりあえずAndroidでデバッグしようと思うと大変なので、tremoloバインディングをテストするLinuxスタンドアロン版のコンソールツールを作成して、それをgdbでデバッグするという方法で確認した。gdbではオブジェクトが渡されるべきところにNULLが渡っていたので、gdb側ではよく分からないと思ってeclipseでandroid版を動かしているうちに気がついた。

次の問題はややこしくて、JNAeratorで生成された構造体に対応するクラスで(もう覚えていない)例外が投げられるというので、調べてみたらそのクラスのフィールド定義が不足していて(型定義を解析できないとそうなるらしい)、結果的にCの構造体とサイズが合わないというものだった。JNAeratorに食わせるCヘッダに、不足かつ必要な部分を補強して生成したコードを渡したら上手く使えるようになった。

というわけで、java版でも無事JNIを使用しないコードを書いてoggファイルを再生することが出来た。実のところそれだけが目的ならJNIを使えば瞬殺で書けたわけだけど。

あとbridjを広めるためには、bridjとJNAeratorの使い方などを簡単にまとめて公開する必要があるだろう。これは別途少しずつ文章をまとめている。

Androidでdlopenするファイルパス

| No Comments | No TrackBacks

BridJ hackingは作者の人と引き続き連携して進めている。少しずつ先に進むのでなかなか楽しい。昨日はBridJが内部的に使っているdyncallにsoファイルをフルパスで渡さなければならない問題を解決した(これまで動いていたと思っていた部分に、どうやらスタブでごまかしていたものがあったようだ)。

dyncallはCのコードで、内部的にはdlopen()を呼び出しているだけなので、JavaのSystem.loadLibrary()とは異なり、自分で共有ライブラリのパスを解決してフルパスを渡してやる必要がある。Debianなどでは*.soがシンボリックリンクになっていないので、明示的にLDScriptを追跡してやる必要があるのだけど、BridJはそこまで実装してあった。なにげに細かいところまで作り込まれている。

Androidの場合、共有ライブラリはリソースとして.apkに含まれていて、Javaアプリケーション側ではストリームを開くことが出来るだけで、実際のアプリケーションは*.soファイルのパスを知ることは無い。少なくともAndroid 2.2まではそうなっていた(Android 2.3には、ApplicationInfoにnativeLibraryDirというフィールドが存在していて、多分これがネイティブライブラリのパスになっている)。

実際には、少なくともエミュレーターの場合は、apkが未解凍のままで置いてあることはなく、/data/data/アプリケーションパッケージ名/ というディレクトリが作成されて、そこに展開されたファイルが格納されている、ようだ(ClassLoader.class.getClassLoader().getResource("lib/armeabi/libbridj.so") のような呼び出しで、URIを得ることが出来る)。エミュレーターの場合、これとは別に/data/data/アプリケーションパッケージ名/.apk! のようなディレクトリもあるのだけど、手持ちのHTC Desireではこれが別のパスになっていたので、このパッケージのほうのURIは使えなかった。ともあれ、ライブラリのファイルパスはこの方法で得られたので(Androidのアプリケーションパッケージ名を得るために、ユーザーに追加コードを書かせる必要が生じたが)、これで.soファイルをロードして、パッケージ同梱の共有ライブラリもロードすることができた。

実際に自分のアプリケーションがちゃんと動作するかどうかは、これから試してみないと分からない。

あと、ライブラリローダ部分をハックしていてハマったのは、同じネイティブライブラリをフルパス無しでもう一度読み込もうとすると、なぜかSEGVが発生してしまうというものだ。もしかしたらこれが自分のアプリケーションでも発生していたのかもしれない(違う気もする)。System.loadLibrary()でファイル名を渡して2度呼び出しても、そのようなエラーが生じることはなかったので、ネイティブライブラリの手動読み込みを妨げる何かしらの問題がbionicあるいはdalvikにあるのかもしれない。

前回の続き。bridjの開発者にも、自分でwebを探していたときに発見したandroidのbugreport (enhancementだけど)を示された。やはりdalvik.systemとdx.jarを使用してClassLoaderを実装するのが一番手っ取り早そうだ。

この辺に本格的に取り組むのであれば、そもそもコード生成に使用しているasmのコードジェネレータにdalvik対応を追加すれば、jvm bytecode -> dalvik bytecode というルートを辿る必要がなくなり、だいぶコード生成コストを抑えることが出来るのだけど、とりあえずコード生成はもともと高速ではなく、頻発するものではないので、今回は妥協することにした。

dxのコードはあまり分かっていないこともあって、ややハマるところがあったが、とりあえず生成時にエラーが出ないところまでは実現できた。ただ、今度は自分のアプリケーションではSEGVが出るようになって、原因が自分のコードにあるのかbridjにあるのかこのdexジェネレータにあるのか判然としない。androidでsegvに遭遇したことも無かったので、何となく泥沼のデバッグをしなければならそうな気がしている。

ちなみにdexとは関係ないが、androidに特化した変更をどのようにbridjのmavenベースのビルドに反映するかも苦労した、というか、今でも歪なままだ。android.jarを参照するdexコードジェネレータは、bridj本体のビルド中にはビルド出来ない。これをビルドするためには、別途androidのプロジェクトが必要になる。幸いなことにbridjの内部構造に依存するコードジェネレータではなかったので、dexの部分だけを別のプロジェクトとして切り出した

この別プロジェクトのビルドで得られる.classを、今回はビルドターゲットのディレクトリに直接コピーしている(!)。mavenでファイルのコピーのような命令があれば、それを呼ぶだけで良いのだろう。bridjのビルドは全プラットフォームに共通で行われるので、このandroidに特化したクラスもとりあえず全プラットフォーム向けにパッケージされている(多分これはfilterするだけで良い)。この辺はメンテナと相談して、どのようなパッケージングにするか考えたほうが良さそうだ。

さらに面倒なことに、利用者はdx.jarを自分のアプリケーションに参照として組み込まなければならない。bridj.jarにはdx.jarは含まれないからだ。今のところmavenにもdxは存在していないようだ。

ただ、前述のとおり、まだSEGVの原因が判明していない=使えるのか分からないので、もう少し調べてからにしようと思っている。

libcore ClassLoader.defineClass()

| No Comments | No TrackBacks

AndroidのClassLoader.defineClass()はUnsupportedOperationExceptionを投げる。これがbridjのCallbackNativeImplementerで使用されていて、どう対応したものか考えなければならない。

AndroidではDexClassLoaderという、dalvikのクラスファイル(dex)を処理できる専門のclass loaderが用意されていて、こちらはdefineClass()を実装しているらしい。他にClassLoaderから派生しているクラスを探してみたけど、SecureClassLoader.defineClass()は内部的にClassLoader.defineClass()を呼び出しているだけだった。defineClass()はprotectedなので、自分で派生クラスを実装しない限り呼び出せない。DexClassLoaderもdefineClass()をオーバーライドしていない。

dalvikのソースはどうなっているんだろうと思ってAOSPを眺めてみたが、ClassLoaderから内部的に呼び出されているVMClassLoaderはdalvik.gitのdalvik/vm/native/javalangVMClassLoaderで例外を投げている。AOSPはharmonyから取っているんだろうと思ってharmonyのソースを覗いてみたら、そもそもClassLoaderはreturn nullしているだけだった。

カスタムClassLoaderを実装しないと実現できないような気がしているが、実装はdrlvmのネイティブコードの中で行われているようだけど、それが使えるかどうかまではさらに見てみないと分からないし、そもそもカスタムClassLoaderで実現できるかどうかは分からない。

開発元にはいろいろ面倒くさそうだよとフィードバックしておいたけど、どうしたものかな。ひとつには、そもそもコードジェネレータじゃなくてProxyとInvocationHandlerを使えないものかと思っている。ただCallbackNativeImplementerが何をしているか分からないので何とも言えない。パフォーマンスも悪い可能性が高い。

bridjと格闘し続けているのだけど、着実に先に進められている。とりあえずbridjのjava側のソースをいじって、何とかbridjを使用したネイティブコードの呼び出しを行うことができた。パッチは開発者に送ったので、もしかしたらそのうち反映されるかもしれない。

まず*.soファイルはアプリケーション本体の libs/armeabi に追加するようにして、bridjのjarからは削除することにした(今のところ手作業でやっている)。そして、bridj本体がlibbridj.soをembedded resourceから読み込むようにしているのを、androidだけ分岐して直接loadLibrary()を読み込むように変更した。これでlibbridj.soは正しく読み込むことができた。

ただ、まだbridj本体に問題があって、あまり先に進めていない。自分のアプリケーションでは、dirent.hに含まれるlibcの機能を利用しているのだけど、dirent.hから自動生成したコードに含まれるscandir()の引数に使用するコールバック関数あるいはその引数に対応するインターフェースをbridjで登録しようとして失敗してしまう。多分関数ポインタまわりのサポートが不十分なのだろう。他にも2ヶ月くらい前に開発MLで報告している人がいた。

これ自体は自分のアプリケーションには必要ないのでとりあえずコメントアウトして先に進めたけど、いずれ自分のコードでも必要になるかもしれないので、自分で修正しないといけないかもしれない。とりあえずそれ以前に自分のアプリケーションでbridjエラーを起こしているコードも修正しなければならない。

前回の続き。bridjのネイティブスタックは何とかビルドすることができた。基本的に、bridjのビルドのハマりどころは、dyncallにandroid対応パッチを当てた後のビルド部分にあると思う。

ハマりどころのひとつは、android.gmakeやandroidndk.gmakeが、patchの当て方の問題で、正しい場所に新規作成されていなかったことだ(パッチを当てるときに -p* を指定していなかった。公式サイトでも < を指示しているようだが...)。これで後になってmakeが、ビルドすべき中間ファイルのビルドルールを発見できないというエラーになった。

もうひとつは、dyncallをビルドする指示を出すMake.shでandroid NDKのtoolchainのディレクトリを指す部分が開発者の環境そのままになっていて、しかもそれがdarwin専用になっていたところにあった。android ndkのディレクトリ構成の深い部分が、プラットフォームによって異なるのだけど、上の方だけ自分のlinux環境に合わせたらパスが間違っていたというわけだ。

dyncallのandroidビルドは(現状)既にサポート対象であるarm32と、やはり既にサポート対象であるlinux環境向けに、android ndkを使って行う。bridjが野良パッチを使っているのは、多分x86サポートなどが含まれていないというのと、まだ作者環境固有の内容になっていてクリーンアップが必要だということがあろう。

ともあれ、dyncallを含むbridjのネイティブ部分は、何とかビルドすることができた。

しかし、今度はそれでビルドできたbridjのnightly jarファイルを自分のプロジェクトに組み込んでビルドしようとしても、やはりsoが読めないという先日のエラーでeclipseがビルドを拒否する。調べてみると、どうもandroidだとjarに.soを含めて利用すること自体出来ないようだ

ということは、生成した.soはアプリケーション本体のlibに含めなければならないということだろうか。bridjは確かリソースディレクトリを開いてその中からbridj自身のsoを読み込んでいたと思うので、読み込み方法を変えるとなると、共有ライブラリの読み込み周辺もいろいろ修正しなければならなくなる気がする。まだまだ調べなければならなそうだ。

stuck at bridj on android

| No Comments | No TrackBacks

今週はおおかた旅行に出ているのと、週末に勉強会のしゃべる方の役割をかかえているのとで、あまり自分のコードを書けていないのだけど、自作のJNIもどきエンジンを作るより既成品を利用した方が早いだろうと考えて、bridjを自分のコードで使ってみることにした。残念ながらまだ成果は無い。

bridjを利用するinterop layerは、JNAeratorのbridj対応版を使って生成するのだけど、JNAeratorはJava Web Startで書かれていて(ひさしぶりに見た)、基本的にひとつのヘッダファイルを解析してJavaクラスを生成するかたちになっている。最初、ひとつしかファイルが渡せないのかと思って、includeを手作業で展開したものを変換してみたのだけど、実際にはそれで良いようだった(足りない部分はスタブインターフェースが生成されたりするし、多くの場合Cなら独自型へのポインタがthisのために使用される)。生成されるファイルのパッケージ名がやや怪しかったので、いろいろ手を入れてみて、なんとかtremolo(とlibogg)のヘッダからそれらしいJavaコードは生成できた。

しかしandroidで実際に動かそうとすると、どうやらUnsatisfiedLinkErrorが出て終了してしまう。調べてみたら、androidをビルドでサポートしているはずのbridjのjarにネイティブライブラリが一切含まれていなかった。これでは動作するはずがない。仕方なくソースをチェックアウトしてmavenでビルドしたのだけど、これに含まれるprebuildの共有ライブラリがunsupportedとみなされて(理由は分からない)、apkに含められずに失敗してしまう。公式サイトに3.0以降対応などと書かれているところを見ると、たぶん何かしらの理由で3.0以降専用ビルドになってしまっているのだろう。

仕方ないので、ネイティブライブラリから自前でビルドしようとしているのだけど、これが成功しない。そもそもビルドの方法がドキュメントされていないし、内部で使用しているdyncallのパッチは当てられないし、mavenのビルドで正しくandroid NDKを指すやり方も不明だ。

これなら自前でdyncallをビルドしてJNAもどきも実装したほうが良い気がしているのだけど、それにしてもdyncallのビルドは必要になるので、そこから調べなければならなそうだ。