RubyJavaBridgeJa
ダウンロードはRubyForgeから
rjbの使い方
(JVM|JRE|j2sdk)の設定
Linux上のSunJVMを使う場合、変数 LD_LIBRARY_PATH にj2se共有オブジェクトへのパスを設定する必要があります。
例:sh, bash, zsh
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/i386:$JAVA_HOME/jre/lib/i386/client
例:csh, tcsh
setenv LD_LIBRARY_PATH $LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/i386:$JAVA_HOME/jre/lib/i386/client
Windowsの場合
共有オブジェクトはWin32がPATHやカレントディレクトリ、それからプログラム内部からの設定から自動的にロードできるので設定不要です。rjbはWin32の場合、JVMの位置をJAVA_HOME環境変数から自動的に探索します。(でも、これって脆弱性かも。UnixでのLD_LIBRARY_PATHの扱いはまさにプログラムが勝手に共有オブジェクトをロードできなくさせるための仕組みなのだから)
rjbの読み込み
require 'rjb'
JVMのロード
Rjb::load
- 0.1.0以降は必須ではなくなりました。Rjb::importが最初の呼び出しの場合、自動ロードします。
0.2.0では、オプションで2つの引数を指定可能です。
Rjb::load(classpath = '.', jvmargs = [])
- classpath
- CLASSPATH環境変数に対する追加のパスを指定します。省略時またはnilを指定した場合は'.'をCLASSPATHに追加します。
- jvmargs
- JVMへ与えるオプションを文字列の配列として指定します。-Dや-Xなどが該当します。ただし、-Djava.class.pathおよびシグナル関連のオプション(たとえばSPARC Solarisであれば-Xusealtsigs)についてはrjbが自動的に設定します。-Djava.class.pathについてはclasspathパラメータを利用してください。
RubyへJavaClassをインポート
str = Rjb::import('java.lang.String') # 変数'str'にJavaの'java.lang.String'クラスをインポート
この宣言をしたあとは、'str'変数はjava.lang.Stringクラスと結びついている。(strはRubyのClassオブジェクトのインスタンスとなる。普段Rubyでclass定義を行う場合は定数に対して定義するが、rjbではそれが変数に対してクラスが定義するような形)
オブジェクトのインスタンスを作る
instance = str.new
これは以下と同じである。
String instance = new String();
java.lang.Stringでは、以下のように、あなたは幾つかの引数とともにコンストラクタを呼び出すことを好むだろう。
String instance = new String("hiki is a wiki engine"); // JavaではStringオブジェクトは不変なのでこの呼び出しはナンセンスだが……
Javaが強い型付け言語なのに対して、Rubyはそうではありません。そのため、Rubyでの1がJavaでのbyteなのかshortなのかintなのかlongなのかそれともこれらの組み込み型クラスなのかrjbで自動的に判断するのには限界があります。特にオーバーロードされているとお手上げです。だから、どのメソッドを呼び出すのか知っているあなた自身でrjbに指示してやってください。(InvokingMethodWithoutTypeSignatureも参照)
instance = str.new_with_sig('Ljava.lang.String;', 'hiki is a wiki engine')
- klass#new_with_sig(sig, arg[, more args])
- 要素型の名前の符号化情報付きでコンストラクタを(祈りつつ)実行する
- sig
- 要素型の名前の符号化。 J2SEJavaDocのClass#getNameに要素型の名前の符号化の一覧が書いてある。
type name | encoded name |
boolean | Z |
byte | B |
char | C |
class or interface | Lclassname; |
double | D |
float | F |
int | I |
long | J |
short | S |
インスタンスメソッドの呼び出し(オーバーロードされていない場合)
Javaでは
String instance2 = instance.replaceAll("hiki", "rwiki");
rjbでは
s = instance.replaceAll('hiki', 'rwiki')
ただ、JavaではStringが返された場合、当然、それはStringクラスのインスタンスになりますが、rjbではRubyのStringのインスタンス(JavaのStringクラスのインスタンスではなく)に変換します。
オーバーロードされたメソッドの呼び出し(要素型の名前の符号化情報付き)
以下のように obj#_invoke を呼ばなければならない
instance2 = instance._invoke('replaceAll', 'Ljava.lang.String;Ljava.lang.String;', 'hiki, 'rwiki')
- obj#_invoke(name, sig, arg[, more args])
- メソッド名と要素型の名前の符号化情報付きでメソッドを呼び出す
- name
- 呼び出すメソッドの名前
- sig
- 要素型の名前の符号化情報。詳しくはJavaDocのClass#getNameを見てください。
メソッド名の変形(1.0.6以降)
Javaの典型的なキャメルケースによるメソッド名のエイリアスとしてRubyの典型的な_で結合したメソッド名を提供します。
jojb.fooBarBaz() == jobj.foo_bar_baz()
戻り値の型変換(1.0.7以降)
Rjbの既定の動作では、Javaのオブジェクトが返したオブジェクトはそのままRjbにインポートされたオブジェクトとして、呼び出し元に返されます。
この動作は、Rjb::primitive_conversion 擬似変数によって変更可能です。
int_obj = JInteger.valueOf('3') if int_obj.intValue == 3 # int_obj は java.lang.Integerクラスのオブジェクト ... Rjb::primitive_conversion = true int_obj = JInteger.valueOf('3') if int_obj == 3 # int_obj は、RubyのInteger(この場合はFixnum)クラスのオブジェクト
JavaのインタフェースにRubyオブジェクトを結びつける
Javaに対してイベントハンドラのようにこちら側の呼び出し可能なオブジェクトを与えるためにはインターフェイスへのバインドを実行します。 たとえば、Comparableインターフェイスを実装したオブジェクトを引数にとるJavaのメソッドを呼び出す場合、次の例のようにRjb::bindを使ってComparableインターフェイスを実装したRubyのオブジェクトを生成できます。
class Comparable def initialize(val) @value = val end def compareTo(oponent) return @value - oponent.to_i
end end cp = Comparable.new(3) cp = Rjb::bind(cp, 'java.lang.Comparable')
- bind(obj, name)
- JavaインタフェースにRubyオブジェクトを結び付けます。なおこれは非破壊的なオペレーションのため、元のインスタンスは変更されません。新たに戻り値としてJavaのインターフェイスにバインドされたオブジェクトが返送されます。
- obj
- rubyオブジェクト
- name
- Javaのインタフェース名
- return
- nameで指示されたインタフェースに結び付けられた新しいオブジェクト
Javaのフィールドにアクセスする
>ruby -rrjb -e "Rjb::import('java.lang.System').out.println('Just Another Ruby Hacker')" Just Another Ruby Hacker >
rjb-0.1.2からゲッタ、セッタともに実装したので、Pointも使えるようになりました。
require 'rjb' pnt = Rjb::import('java.awt.Point') p = pnt.new(0, 0) p.y = 80 puts "x=#{p.x}, y=#{p.y}" => x=0, y=80
Javaの例外をスローする
バインドしたオブジェクトからJavaの例外をスローするにはRjb::throwメソッドを利用します。
class Iterator def hasNext() true end def next() Rjb::throw('java.util.NoSuchElementException', '無い袖は振れないよ') end end
Rjb::throwは2引数のメソッドです。
Rjb::throw(classname, message)
- classname
- スローする例外クラス(java.lang.Throwableのサブクラス)の名前を指定します。
- message
- 例外に格納するメッセージを指定します。
$KCODEによる文字コードの推測
Rubyは文字コードとしてEUC-JP,Shift_JIS,UTF-8,NONEをサポートしていて、$KCODEで切り替えるようになっています。簡単に使いたい場合、$KCODEは恐らくデフォルトのNONEのままと思うのですが、その際のRuby側の文字コードを推測する部分のコードを変更しました。推測ルールは以下のとおりです。
$KCODE | 推測する方法 | 推測されたRuby側の文字コード |
E | euc-jp | |
S | cp932 | |
U | 無変換 | |
N | Windows && GetACP() == 932 | cp932 |
Windows && GetACP() != 932 | 無変換 | |
Locale && sjis | shift_jis | |
Locale && euc-jp | euc-jp | |
Locale && utf-8 | 無変換 | |
Locale && other | 無変換 | |
other | 無変換 |
推測された結果が「無変換」の場合、RjbはJava側に文字列を渡す際にJNIのNewStringUTF(UTF-8文字列を参照するポインタからjava.lang.Stringのインスタンスを生成する関数)を呼び出すので、RubyのStringが保持するバイト列はUTF-8でなければなりません。
サンプル
- ClipboardToFile
- クリップボード上のイメージをファイルへ出力するサンプル
制限事項
Javaのメインスレッド以外からのrjbへの呼び出しは非サポートです。
- 排他制御は行いません。RubyのHashを破壊する可能性があります。
- Rubyがメインスレッド以外のネイティブスレッドでの実行をサポートしていません。GCでのスタック取り違えによるクラッシュや同時にinternした場合の競合など種々の障害の原因となります。
特にAWTを利用する場合、EventListenerはワーカースレッドに通知されることがほとんどなので、rjbで正しくハンドルできることは期待できません。
謝辞
おお、ありがとうございます。ちょっと手を入れました。
質問と回答
TODO
- 非publicクラスのimport
- 非publicなコンストラクタ
とりあえず終了しているTODO
- 例外の整理
- rjbでの例外処理(after 0.1.2)
- 引数の型判断
- rt.jarによるimportの検証
- GCで死んでいた理由:RjbGcProblemWasSolved (0.1.4で解決のはず)
その他
- _invoke()とnew_with_sigに渡す型の文字列は";"区切りだけど、最後の";"はあってもなくても通るようにして欲しい。
- これは却下。;区切りというのは正しくなくて、クラス名はL([\w_]+\.)*[\w_]+;という規約だから。(boolean b, Z c)であればZLZ;。確かにLが前置されるから;が省略されていても判断できるけどそこまではしません。
Keyword(s):
References: