May 2011 Archives

前回の続き、のようなもの。

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を使用した既存のライブラリがもし大量にあれば、それはなお移植しやすいかもしれないけど、そのニーズはあまり無いようにも思う。

libdl via JNI

| No Comments | No TrackBacks

JNA実装を進めるために、まだ分かっていないことが多すぎる。とりあえず実装するためにはdlopenを使わなければならないだろうし、dlopenを使ったことは一度もない。というわけで、初めてコレを使ってみた。ついでに、CプログラミングもJavaプログラミングもほとんど忘れているので、いろいろ復習やキャッチアップにもなった。

まずdlopen/dlsym/dlcloseなどを定義して、javahでCヘッダを生成して、それを実装するCコードを書いて、soをビルドして、Javaでテストするところまでやってみた。ここまでのハマりどころは...

  • Ubuntuで実装していたので、サンプル呼び出しに使っていたlibc.soがELFとして読めないことに気づくまでしばらくかかった(libc.so.6でなければならなかった)。
  • ↑のような場合、dlopen()が失敗するにもかかわらず、JNIで呼び出したdlerror()がメッセージを返してくれない。しばらく考えたが、たぶんJNI自身が内部でlibdlの関数を呼び出しているので、それらが正常な呼び出しを行って結果的にエラーメッセージをリセットしているのだろう。dlopen()などを呼び出すJNI側の関数の中で、直後にdlerror()を呼び出してエラーメッセージを保持しておくことで、正しいエラーメッセージを取得できるようになった。
  • dlerror()が返す文字列(char*)は同じ内容をずっと保持するわけではないので、strdup()でコピーを保持しておく必要があった(そうしないとやがて文字列内容は破棄された)。この関係で、最後には最後のdlerrorメッセージのクリーンアップが必要になったので、libdlバインディングはインスタンスベースの実装に変更することになった(finalizerでクリーンアップを呼び出す)。

あと地味なところでは可変長引数だ。可変長引数はJava 1.5から導入されたようだ。とりあえず Object ... params のようにメソッドの引数を定義して javah でCヘッダを生成すると jobjectarray という型になった。これを何とかしてCの型に変換すれば良いようだ。この辺はこれからやることになる。

WallpaperService

| No Comments | No TrackBacks

先週末はちょっと仙台まで出向いてhackathonなるものに参加してきた。基本的に自分が意味あると思うものを作るのが一番生産的だと思うし、hackathonでそういうことが出来ると思っていないので、あまりその手のものには参加しようとしないのだけど、被災地に行って様子を見てくるという目的は少し達成できたように思う。

そんなわけで全然普段と関係ないことをやる予定だったのだけど、何かよく分からないうちに結局androidでlive wallpaperを作ることになった。そんでよく分かっていないWallpaperServiceをいじることになった。コレはSurfaceViewをいじるようになっていて、touch eventなども拾えるということで、割と色んなことができるのかと思ったのだけど、Activityなどを立ち上げるためのContext情報などが渡されないので、結局その辺は短時間ではどうするのか分からなかった(そしてその後まだ見ていない)。

プログラム自体は不完全にもほどがあるものだったにもかかわらず、デザインがよく出来ていたということで無駄に人気を得て地域代表ということになっていたのだけど、実際にはwebから写真などを引っ張ってきて表示するクライアントにならなければならず、サービスもまだ大手が開発中なので、しばらく凍結しておくことになるだろうと思う。

個人的には少なくとも自分が使いたくなるようなアプリでなければ意味ないと思うのだけど、まあそれは他の人が価値を見出すものなのかもしれないので、共同作業でひと様が喜ぶようなものを作る時はあまり気にしないことにしよう、と思うことにした。

Proxying in Java

| No Comments | No TrackBacks

JNAのスタブは1日でだいたい終わったので、昨日はその中から簡単に実装できそうな部分をつまみ食いしながら、API全体を俯瞰して、相関関係を推測してみた(編集中のものが随時反映される)。

Native.loadLibrary()からFunctionクラスのオブジェクトを取得するところまでは、ProxyとInvocationHandlerを使って実現できそうだ。InvocationHandlerインターフェースはLibrary.Handlerクラスが実装している。.NET RealProxyと似ている。

...ということは分かったので、その辺りまでの流れを何となく掴んで、proof of concept的なものを実装としてチェックインしておいた

JNA実装計画

| No Comments | No TrackBacks

Androidに必要なのはネイティブ化だと思っている。Javaのフレームワークに乗っからない限りアプリケーションを構築できないという点が、さまざまな開発者の足を引っ張っていると思う。Android 2.3でNativeActivityが導入されたことは、(十分ではないとは思うけど)たいへん喜ばしいことだと思った。最近になってC#が使えるようになったことの意義は大きい。これと同じことがPythonやRubyやLuaでも行えるようになるべきだ。問題はCのバインディングすら無いということだ。

いずれにせよ、今でもAndroidアプリは主にJavaで書かれるだろうし、その中でネイティブコードを使うアドバンテージはなお大きいだろう。それならば、せめてネイティブコードを使う敷居を下げるべきだと思う。だとしたら、せめてJNAみたいなものがAndroidで問題なく使えるようになってほしい。そうすればCのglueを生成してビルドする必要も無くなるのだから。

Sun/OracleのJNAは彼らの権利の下でLGPLで配布されているので、組み込んで使うには抵抗がありそうだ。それならば、JNAに相当するものを自作してしまえば良いのではないか。というわけで、試しにスタブを作ってみた。作ってみて分かったのは、パブリックAPIでもそれなりの量になっているということだろうか。ただ、全て実装するのでなければ、それほどの量でもないように思う。1週間くらいでそれなりに使えるものが出来てもおかしくない。

もっとも、JNAはJNIの仕組みに基づいているので、パフォーマンスは当然ながら悪い(というか、非常に悪い)ようなので、ネイティブコードのメリットが活かされない可能性も高い。これは、dlopen/dlsymをJNI経由で呼び出さないければならない以上、不可避なのだろうと思う。加えて、Javaはマーシャリングの面でいろいろと不利なのではないか。JNAが不必要にIntegerなどのオブジェクトを生成していて重いという可能性もありそうだけど。

dalvikに特化してもう少し高速化できるのであれば、その道を探ったほうが良いかもしれない。

(almost) successful android ogg player

| No Comments | No TrackBacks

ここに書くのはひさしぶりになってしまったのだけど、先月というかGW中は趣味のコード書きに入ることができた。ひとつの懸案だったandroid版のループogg vorbis playerは、ある程度実用的なところまで実装できたので、ひと区切りつけてMarketで公開した。それについては別のところで書いたので、ここには落穂拾い的にもう少し細かい話を書き残しておこうと思う。

Playerの実装は、viewに影響する部分だけを切り離したクラスと、AudioTrackを操作する部分だけを切り離したクラスと、それらのコントローラーという形で分割して、MVC的なものにはなっている。ただ、状態管理が出来なくなったくらい、ロジックがきちんと整理されていない。managed-midiの設計はかなりクリーンで、実際に演奏命令をMIDI出力するPortMidiPlayerや、ビジュアルに連動しつつ演奏は行わないmldspのようなものを簡単に実現できているので、これと同じくらいクリーンなものにしたいとも思う(重要度はそんなに高くない)。

ファイルの選択にはAlertDialogを使用している。ファイル選択のUIは悩ましい問題だったのだけど、今回はファイルの選び方がある程度固定的だと考えて、intentとして外部に丸投げするのではなく、簡単なものを自分で実装するかたちにした。ファイルはまずoggファイルを含むディレクトリを選択した上で、そこからファイルを選択するかたちにしている。ディレクトリの検索は手動で全ディレクトリから(最低限初回時に)行う...というアプローチだ。UIとしてはせせこましいが、目的に沿った操作が自然とできるようにしている。ファイル名あるいは登録済み楽曲名を表示できるようにするようなカスタマイズを実現できたこともある。

オーディオ再生でヘッドフォンが外れた場合に音楽を一時停止させるには、AudioManager.ActionAudioBecomingNoisy というintentを使えば良いようだ。今回はこれで自前の一時停止機能を呼び出して使用している。

演奏バッファはlibvorbisidecをP/Invokeして取得している。これは問題なく速い。csvorbisで今回のプレイヤーに合わせて試してはいないのだけど、ここに以前書いた内容を見るに、デコードだけでも演奏時間の数倍かかっているようでは到底無理なので、あきらめようと思う。

演奏バッファの処理スレッドについては、今回はmonoランタイムのThreadで実装した。AsyncTaskを使うほどUIの更新は無いし、JavaのThreadにしたらマーシャリングが余計に発生しそうだったからだ。

今はより速度を犠牲にしない方法が無いか探っている。このエンジンが十分に軽量化出来れば、ゲームエンジンのBGM再生にも活用できそうなのだけど、音楽の再生だけでかなりCPUを食われてしまうので、割と真面目に解決したい課題だ。

ひとつの考えとしては、やはり全てJavaでやってしまうというものだ(Javaでも遅くなるということについては半ば確信に近いものがあるが...)。P/Invokeがごく簡単かつそれなりに高速に実現できるマネージドコードとは異なり、Javaの場合JNIで非効率そうなので、いまいちやる気になれないところだ。それと、JNIは面倒だ。これではAndroidでネイティブコードを使おうという気持ちにはなれないだろう。

もうひとつは、ループ再生機能を追加した独自のlibmediaplayer/libmediaplayerserviceをつくってしまって、それを利用するという案で、これなら少なくとも現在のMediaPlayer並の処理速度・負荷でいけるだろうと思っている。問題はndkの正攻法ではビルド出来ないだろうというところだ。この辺は、カスタムWebKitビルドがほしいと思っている事とも合わせて、もう少し可能性を探ってみたい。