2012年11月17日土曜日

JNI 呼び出しで気をつけること

 とてもくだらないことで何時間も費やしてしまった…。いろいろ調べてるうちに誤情報とかもあって混乱したので、JNI 呼び出しの気をつけることをまとめてます。

●Java 側の定義
 定義は以下のような形で行います。

package com.sample;
class MyClass {
   public native void  my_method( int arg );
}

 C 側の関数の実体を定義していなくてもコンパイルは通ります。これは実行時に、現在読み込んでいる実行モジュール (.so) から関数を探すようになっているためで、コンパイルが通ったからといって呼び出しが成功するとは限りません。

●C 側の関数名は自分で書かずに javah を使用する
 Java から呼び出す関数名の定義にはいくつか約束事があり、それを全部理解しているならいいのですが、いろいろわかりにくいため javah でプロトタイプ宣言を自動作成するのが一番確実です。
 例えば、'_'(アンダーバー) は JNI ではパッケージ名の区切り記号として使用するため、関数名にアンダーバーが含まれる場合には特殊な置き換えが必要になります。
 また、クラスにメンバ変数があるかどうかによって、関数の第2引数の型が変化するようです。(jobject だったり jclass だったり)
 これらの細かい仕様は javah でヘッダを出力するようにすればすべて自動でやってくれます。それから、初心者の方がよくはまりがちな extern "C" もちゃんと一緒に出力してくれます。

 javah -classpath <パス名> <クラス名>

 <パス名>はコンパイル済みの Java のクラスのあるパスを指定します。eclipse でコンパイルしているなら「プロジェクトのパス/bin/classes」になります。
 <クラス名>はパッケージ名も含めたフルクラス名です。上の例なら com.sample.MyClass になります。

●Java 側で JNI モジュールを読み込む
 JNI メソッドを呼び出すより前に、必ず JNI モジュールを読み込んでいる必要があります。ほとんどの場合は、起動と同時に読み込むことになると思うので、起動時に実行するクラスで、

class Main
{
    static {
        System.loadLibrary( "MyModule" );
    }
}

と書くことになります。
 注意点としては、読み込むモジュールのファイル名は必ず "lib???.so" というファイル名である必要があり、loadLibrary で指定する名前は、このファイル名から "lib" と ".so" を除いた部分であるという点です。上の例なら、"libMyModule.so" というファイルが読み込まれます。

2012年5月6日日曜日

Mercurial サーバで苦戦した

最近、世間では Subversion はどんどんユーザ数を減らし、git または Mercurial あたりに移行していっているプロジェクトが多いようです。僕もこの波に乗って、しばらくローカルマシンでテスト運用していたのですが、十分実用に耐えうるものと判断し、乗り換えることにしました。
サーバマシンは Apache が動いていればよく、cgi のみで動作可能とのこと。しかし、ググって調べた通りにやっても Clone はできるものの Push がなかなかうまくいかず難儀しました…。以下は僕のたどり着いた成功手順です。


1.Python のインストール
Windows の場合、Python をインストールする必要があります。後でインストールする Mercurial のパッケージに対応したバージョンをインストールしてください。これを書いている時点で対応している Python のバージョンは 2.6 のみでした。
Mac OSX の場合はすでにインストールされているので必要ありません。


2.Mercurial のインストール
Mercurial にはいくつかの配布形態があり、サーバに必要なファイルが含まれていないパッケージもあるようなので気をつける必要があります。
 Windows の場合 Mercurial パッケージまたは TortoiseHg をインストールし、これともう一つ Python の名前のついたパッケージをインストールしてください。僕がインストールしたのは「Mercurial 2.1.2 Python 2.6 package」というパッケージです。
 Mac OSX の場合は、パッケージからのインストールと MacPorts からのインストールと、あともう一個何か方法があったかと思いますが、MacPorts の場合 Python 用のファイルがどうも見あたらなかったので、パッケージをダウンロードしてきてインストールするのがいいようです。パッケージのインストールだと、Python の site-packages フォルダに自動的に必要なファイルがインストールされます。ちなみに僕がインストールしたのは「Mercurial-2.1.2-py2.7-macosx10.7.zip」です。
  どちらの環境でも、インストール後にコマンドラインから Python を起動し、
>>> import mercurial
と打ち込んでエラーがでなければ成功です。(Python の終了は CTRL+D)


3.リポジトリルートフォルダの準備
 ここでは、複数のリポジトリを扱うサーバを想定していますので、そのルートフォルダを準備します。例では「hgrepo」という名前のフォルダを作成します。そして、「.hg」のある各プロジェクトのフォルダはこのフォルダ以下に作るようにします。
 それから、このフォルダには Mercurial サーバを運用する二つのファイル、設定ファイルの「.config」と cgi の「hgweb.cgi」を置くことにします。


4.「.config」の作成
ssl の無効化と push を有効にする設定と、ルートへのパスの設定です。※[web]はリポジトリごとに指定することもできます。その場合、各 .hg フォルダ以下に hgrc というファイルを作ってそこに記述します。
[web]
push_ssl = false
allow_push = *

[paths]
/ = /Users/foo/hgrepo/*

5.「hgweb.cgi」の作成
hgweb.cgi のテンプレートは Mercurial のソースパッケージや、その他のパッケージにも含まれていたりしますが、大きなファイルではないので以下のものをコピペして変更でいいかと思います。環境によって書き換える必要があるのは、1〜3行目だけです。


Windows:
#!C:/Python26/python.exe
config = "C:/hgrepo/.config"
import sys; sys.path.insert(0, "C:/Program Files/TortoiseHg/library.zip")

from mercurial import demandimport; demandimport.enable()
from mercurial.hgweb import hgweb, wsgicgi
application = hgweb(config)
wsgicgi.launch(application)

Mac OSX:
#!/usr/bin/python
config = "/Users/foo/hgrepo/.config"
import sys; sys.path.insert(0, "/Library/Python/2.7/site-packages/mercurial")

from mercurial import demandimport; demandimport.enable()
from mercurial.hgweb import hgweb, wsgicgi
application = hgweb(config)
wsgicgi.launch(application)


6.Apache の設定
httpd.conf に以下のものを追加します。
ScriptAlias /hg /Users/foo/hgrepo/hgweb.cgi
<Directory "/Users/foo/hgrepo">
    AddHandler cgi-script .cgi
    Options +ExecCGI
    Order allow,deny
    Allow from all
</Directory>
重要な点は Alias ではなく ScriptAlias にすることです。Alias だと「http://localhost/hgrepo」にアクセスしたときは cgi が動作しますが、hgrepo 以下にあるリポジトリ、例えば「http://localhost/hgrepo/myproj」へのアクセスは cgi を使用しない通常の http 通信になってしまいます。ScriptAlias にすることで、hgrepo フォルダ以下へのアクセスはすべて cgi  を使用したアクセスになります。

以上で Apache を起動すれば動作するはずです。
ブラウザから「http://127.0.0.1/hgrepo」として cgi の動作を確認してみてください。
もしエラーが表示されたら、まず Apache のエラーログを確認しましょう。
僕が何度か遭遇したエラーは No such file or directory の類いで、hgweb.cgi の1行目のパスが Python のインストールされたパスになっていなかったりしたために動作しませんでした。パスはちゃんと確認しましょう…。