Create  Edit  Diff  FrontPage  Index  Search  Changes  Login

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 nameencoded name
booleanZ
byteB
charC
class or interfaceLclassname;
doubleD
floatF
intI
longJ
shortS

インスタンスメソッドの呼び出し(オーバーロードされていない場合)

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() == 932cp932
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でなければなりません。

サンプル

制限事項

Javaのメインスレッド以外からのrjbへの呼び出しは非サポートです。

  • 排他制御は行いません。RubyのHashを破壊する可能性があります。
  • Rubyがメインスレッド以外のネイティブスレッドでの実行をサポートしていません。GCでのスタック取り違えによるクラッシュや同時にinternした場合の競合など種々の障害の原因となります。

特にAWTを利用する場合、EventListenerはワーカースレッドに通知されることがほとんどなので、rjbで正しくハンドルできることは期待できません。

謝辞

おお、ありがとうございます。ちょっと手を入れました。

質問と回答

RjbQandA

TODO

  • 非publicクラスのimport
  • 非publicなコンストラクタ

とりあえず終了しているTODO

その他

  • _invoke()とnew_with_sigに渡す型の文字列は";"区切りだけど、最後の";"はあってもなくても通るようにして欲しい。
    • これは却下。;区切りというのは正しくなくて、クラス名はL([\w_]+\.)*[\w_]+;という規約だから。(boolean b, Z c)であればZLZ;。確かにLが前置されるから;が省略されていても判断できるけどそこまではしません。

関連するかも知れない問題

RjbMiscProblems

Last modified:2011/07/24 23:52:24
Keyword(s):
References: