[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
10.1 基本的概念 CNIの使い方の紹介 10.2 パッケージ C++へのパッケージのマッピング 10.3 プリミティブ型 C++においてJavaの型を取り扱う 10.4 インタフェース C++へのJavaインタフェースのマッピング 10.5 オブジェクトとクラス C++クラスとJavaクラス 10.6 クラスの初期化 10.7 オブジェクト割り当て C++におけるJavaオブジェクトの作成 10.8 配列 C++においてJava配列を操作する 10.9 メソッド C++におけるJavaメソッド 10.10 文字列 Java Stringオブジェクトに関する情報 10.11 C/C++との相互運用 CNIとC++の相互運用 10.12 例外処理 10.13 同期化 JavaとC++の間の同期化 10.14 呼び出し C++からJavaランタイムを起動する 10.15 リフレクション C++からリフレクションを使う
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
gcj
はG++と同一のコンパイラ技術
(GNU C++コンパイラ)
を使っていますので、
両方の言語の共通部分において同一のABI
(オブジェクト表現、および、呼び出し規約)
を使うようにすることが可能です。
CNIにおける主たるアイデアは、
JavaオブジェクトはC++オブジェクトであり、
すべてのJavaクラスはC++クラスである
(しかしその逆ではない)
というところにあります。
そこで、
JavaとC++を統合するにあたって最重要なタスクは、
余計な非互換性を除去することにあります。
CNIコードは通常のC++ソースファイルとして書きます。
(Java/CNIを認識するC++コンパイラ、
具体的には最近のバージョンのG++を使わなければなりません。)
CNI C++ソースファイルには以下の記述がなければなりません。
#include <gcj/cni.h> |
#include <java/lang/Character.h> #include <java/util/Date.h> #include <java/lang/IndexOutOfBoundsException.h> |
gcjh
によって自動的に生成されます。
CNIは、
C++からJavaオブジェクトやJavaプリミティブ型を使うのを簡単にするための関数やマクロをいくつか提供しています。
これらのCNI関数やマクロの名前は一般的にはJv
という接頭語で始まります。
例えば、
JvNewObjectArray
です。
他のライブラリとの衝突を回避するためにこの慣習が使われています。
CNIの内部関数の名前は_Jv_
という接頭語で始まります。
これらの関数を呼び出すべきではありません。
もしそうする必要があるのであれば私たちに連絡をください。
代わりとなる解決策を見つけるよう努力します。
(このマニュアルでは、
_Jv_AllocBytes
を1つの例として取り上げています。
CNIは、
本来はJvAllocBytes
という関数を提供すべきであると思います。)
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
char*
)
を引数として取るメソッドを宣言することはできません。
また、
Javaのデータ型以外のデータ型のメンバ変数を宣言することもできません。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
java.lang.String
というJavaクラスは、
java
パッケージのサブパッケージであるjava.lang
パッケージに属しています。
このクラスのC++版はjava::lang::String
です。
これは、
java
ネームスペースの中のjava::lang
ネームスペースに属しています。
以下にこのことを表現する方法を示します。
// クラスを宣言する。これはおそらくヘッダファイルの中で行なわれるであろう。 namespace java { namespace lang { class Object; class String; ... } } class java::lang::String : public java::lang::Object { ... }; |
gcjh
ツールは、
必要となるネームスペース宣言を自動的に生成します。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
package
宣言は、
それ以下のクラス宣言が指定されたパッケージに属することを指定するものです。
したがって、
完全なパッケージ名を明示的に指定する必要はなくなります。
package
宣言に続けてゼロ個以上のimport
宣言を指定することができます。
これによって、
あるパッケージに属する1個のクラス、
あるいは、
すべてのクラスを、
単にその識別名だけで指定することができるようになります。
C++は、
これに似たものをusing
宣言とusing
指示子によって提供しています。
Javaにおいては、
import package-name.class-name; |
package-name.class-name
の省略表現としてclass-nameという形でクラスを参照できるようになります。
C++において同じことを達成するには、
以下のようにしなければなりません。
using package-name::class-name; |
import package-name.*; |
using namespace package-name; |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Javaの型 | C/C++の型 | 説明 |
char | jchar | 16ビットUnicode文字 |
boolean | jboolean | 論理値(true/false) |
byte | jbyte | 8ビット符号付整数 |
short | jshort | 16ビット符号付整数 |
int | jint | 32ビット符号付整数 |
long | jlong | 64ビット符号付整数 |
float | jfloat | 32ビットIEEE浮動小数点数 |
double | jdouble | 64ビットIEEE浮動小数点数 |
void | void | 値なし |
jint
)
を使うべきです。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
boolean
に対応するのはjava.lang.Boolean
クラスです。
こうしたクラスの扱いを簡単にするために、
GCJはJvPrimClass
マクロを提供しています。
Class
オブジェクトへのポインタを返します。
JvPrimClass(void) => java.lang.Void.TYPE |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
interface A { void a(); } interface B extends A { void b(); } |
B
の変数を宣言している場合、
その変数をまずA
にキャストしない限りa()
を呼び出すことはできません。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
java.lang.Object
を継承しています。
C++には唯一のルートクラスというものは存在しませんが、
私たちは、
Javaのjava.lang.Object
クラスに対応するC++クラスjava::lang::Object
を使っています。
その他のすべてのJavaクラスは、
java::lang::Object
を継承する、
対応するC++クラスにマップされます。
インタフェース継承
(implements
キーワード)
は、
現在のところC++へのマッピングには反映されません。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
byte
、
short
、
char
、
boolean
)
は32ビットに拡張されないということです。
Java VMは、
8ビットおよび16ビットの型がVMのスタック上や一時レジスタに置かれるときには、
それを32ビットに拡張します。
gcjh
が生成したクラスのヘッダファイルをインクルードすれば、
Javaクラスのフィールドに自然な方法でアクセスすることができます。
例えば、
以下のようなJavaクラスがあるとしましょう。
public class Int { public int i; public Integer (int i) { this.i = i; } public static zero = new Integer(0); } |
#include <gcj/cni.h>; #include <Int>; Int* mult (Int *p, jint k) { if (k == 0) return Int::zero; // 静的メンバに対するアクセス return new Int(p->i * k); } |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
String
オブジェクトの割り当てなど、
実装固有のアクションが発生する可能性もあります。
GCJコンパイラは、
必要なときに確実にクラスが初期化されるようにするために、
適切なところにJvInitClass
の呼び出しを挿入します。
C++コンパイラは、
これらの呼び出しを自動的に挿入してはくれません。
すなわち、
クラスが確実に初期化されるようにするのはプログラマの責任なのです。
しかし、
Javaシステムが想定している慣習により、
非常に簡単に実現できます。
まずlibgcj
が、
オブジェクトのインスタンスが作成される前に確実にクラスが初期化されるようにします。
これはnew
オペレーションの責任の一部です。
JavaコードおよびC++コードの両方で対処されます。
(G++コンパイラがJavaクラスのnew
を見つけると、
そのオブジェクトを割り当てるためにlibgcj
の中のルーチンを呼び出します。
そのルーチンが、
クラスの初期化に対処します。)
こうして、
クラスとそのすべての基底クラスが初期化済みであることが分かっているので、
インスタンスフィールドにアクセスしたり、
インスタンス
(非静的)
メソッドを呼び出したりしても安全なのです。
静的メソッドの呼び出しも安全です。
Javaコンパイラが、
そのクラスが初期化済みであることを確認するコードを静的メソッドの先頭に追加しているからです。
しかし、
C++コンパイラはこの追加コードを挿入してはくれません。
したがって、
CNIを使って静的なネィティブメソッドを書くのであれば、
(そうしなくても安全であるという確信があれば別ですが)
そのメソッドの中でまず最初にJvInitClass
を呼び出す責任は、
あなたにあります。
静的フィールドへのアクセスも、
そのフィールドのクラスが初期化済みであることを必要とします。
Javaコンパイラのコードは、
フィールドの値を取得したり設定したりする前にJv_InitClass
を呼び出すコードを生成します。
しかし、
C++コンパイラはこの追加コードを挿入してはくれません。
したがって、
C++のコードから静的フィールドにアクセスする前にそのクラスが確実に初期化されているようにする責任は、
あなたにあります。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
new Type ( ... ) |
java::util::Hashtable *ht = new java::util::Hashtable(120); |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
java.lang.Object
を継承しています)。
配列値を取る変数は、
配列オブジェクトへのリファレンス
(ポインタ)
を持つ変数です。
C++コードからJavaの配列を参照するのには、
JArray
テンプレートが使われています。
これは以下のように定義されています。
class __JArray : public java::lang::Object { public: int length; }; template<class T> class JArray : public __JArray { T data[0]; public: T& operator[](jint i) { return data[i]; } }; |
typedef
に対応するtypedef
がいくつか存在します。
それぞれが、
関連する型のオブジェクトを保持する配列の型です。
typedef __JArray *jarray; typedef JArray<jobject> *jobjectArray; typedef JArray<jboolean> *jbooleanArray; typedef JArray<jbyte> *jbyteArray; typedef JArray<jchar> *jcharArray; typedef JArray<jshort> *jshortArray; typedef JArray<jint> *jintArray; typedef JArray<jlong> *jlongArray; typedef JArray<jfloat> *jfloatArray; typedef JArray<jdouble> *jdoubleArray; |
array
で指定される配列の要素に対するポインタを取得することができます。
例えば、
int[]
を構成する整数型要素に対するポインタは、
以下のようにして取得することができます。
extern jintArray foo; jint *intp = elements (foo); |
klass
は配列要素の型、
init
は配列の各スロットにセットされる初期値です。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
JvNewTypeArray |
JvNewBooleanArray |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
gcjh
によって生成されるヘッダファイルには、
適切なメソッド定義が含まれています。
基本的には、
生成されたメソッドは、
Javaメソッドと同一の名前と
対応する型を持っていて、
自然な方法で呼び出されます。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
gcjh
によって生成された複数のオーバロードメソッドをもとに、
期待どおりのメソッドを選択するでしょう。
一般的なアセンブラやリンカは、
C++オーバロードのことを知っていません。
したがって、
標準的な実装方法は、
メソッドの引数の型をアセンブリレベルの名前の一部にエンコードすることです。
このエンコードのことをマングル(mangling)と呼びます。
また、
エンコードされた名前のことをマングルドネーム(mangled name)と呼びます。
これと同じ機構がJavaにおけるオーバロードの実装にも使われます。
C++とJavaの相互運用性のためには、
JavaコンパイラとC++コンパイラが同一のエンコードスキーマを使うことが重要です。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
.
オペレータではなく::
オペレータが使われます。
以下に例を示します。
jint i = java::lang::Math::round((jfloat) 2.3); |
#include <java/lang/Integer> java::lang::Integer* java::lang::Integer::getInteger(jstring str) { ... } |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
new
オペレータを使ったオブジェクト割り当ての一環として暗黙のうちに呼び出されます。
以下に例を示します。
java::lang::Integer *x = new java::lang::Integer(234); |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
// 最初にJavaオブジェクトを作成する java::lang::Integer *x = new java::lang::Integer(234); // 次にメソッドを呼び出す jint prim_value = x->intValue(); if (x->longValue == 0) ... |
#include <java/lang/Integer.h> jdouble java::lang:Integer::doubleValue() { return (jdouble) value; } |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
String
オブジェクトを取り扱うためのユーティリティ関数をいくつか提供しています。
その名前やインタフェースは、
JNIにおけるものに似ています。
String
オブジェクトを返します。
String
オブジェクトを返します。
String
の長さがstrlen(bytes)
である点を除き、
前の関数と同じです。
String
を返します。
String
オブジェクトstrの文字列から構成される配列へのポインタを返します。
String
オブジェクトstrの内容をUTF-8エンコードするために必要とされるバイト数を返します。
String
オブジェクトstrのある部分をUTF-8エンコードしたものをバッファbuf
に入れます。
取り出すべき部分は先頭位置startと終端位置lenで示されます。
bufはバッファであり、
Cの文字列ではないことに注意してください。
その終端はNULLではありません。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
class ::MyClass : public java::lang::Object { char* variable; // char*はJavaにおいては正当な型ではない } uint ::SomeClass::someMethod (char *arg) { . . . } // |
jint ::SomeClass::otherMethod (jstring str) { char *arg = ... . . . } |
gnu.gcj.RawData
クラスが含まれています。
RawData
クラスは、
検査されないリファレンス型です。
言い換えると、
RawData
型として宣言された変数には任意のデータを持たせることができ、
コンパイラによるチェックはいかなるものであれ行なわれないということです。
このことは、
適切なキャストを使いさえすれば、
CNIクラスの中にC/C++の
(クラスも含む)
データ構造を持つことができるということを意味しています。
以下にいくつか例を示します。
class ::MyClass : public java::lang::Object { gnu.gcj.RawData string; MyClass (); gnu.gcj.RawData getText (); void printText (); } ::MyClass::MyClass () { char* text = ... string = text; } gnu.gcj.RawData ::MyClass::getText () { return string; } void ::MyClass::printText () { printf("%s\n", (char*) string); } |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
throw
を使ってC++からJavaの例外を投げることができます。
そして、
この例外をJavaのコードの中で捕捉することができます。
また同様に、
Javaから投げられた例外をC++のcatch
を使って捕捉することもできます。
以下に例を示します。
if (i >= count) throw new java::lang::IndexOutOfBoundsException(); |
struct S { ~S(); }; extern void bar(); // Javaで実装されていて、例外を投げる。 void foo() { S s; bar(); } |
__gxx_personality_v0
という名前のルーチンが存在しないというメッセージが出力されます。
ファイルの先頭に#pragma GCC java_exceptions
と書くことによって、
コンパイラの推測の如何にかかわらず、
翻訳単位の中でJavaの例外が使われることをコンパイラに知らせることができます。
この#pragma
は、
例外を投げる関数、
例外を捕捉する関数、
または、
例外が投げられたときにデストラクタを実行する関数のどれよりも前になければなりません。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
monitorenter
命令を使ってモニタのロックを取得し、
monitorexit
命令を使ってモニタのロックを解放します。
これらに対応するCNIマクロはJvMonitorEnter
とJvMonitorExit
です
(JNIにも類似のメソッドMonitorEnter
とMonitorExit
があります)。
Javaソース言語は、
これらのプリミティブに直接アクセスする手段を提供していません。
その代わりになるものとしてsynchronized
文があります。
これは、
ブロックに入る前に暗黙的にmonitorenter
を呼び出し、
ブロックから出るときにmonitorexit
を呼び出します。
そのブロックの実行が例外によって異常中断させられる場合でもロックは解放されなければならないということに注意してください。
このことは、
同期化ロックを囲む暗黙的なtry finally
が存在することを意味しています。
C++においては、
ロックを解放するのにデストラクタを使うのが合理的です。
CNIは、
以下に示すユーティリティクラスを定義しています。
class JvSynchronize() { jobject obj; JvSynchronize(jobject o) { obj = o; JvMonitorEnter(o); } ~JvSynchronize() { JvMonitorExit(obj); } }; |
synchronized (OBJ) { CODE } |
{ JvSynchronize dummy (OBJ); CODE; } |
synchronized
属性を持つメソッドもあります。
これは、
メソッド本体全体をsynchronized
文で囲むことと同じです。
(あるいは、
呼び出し側で同期化を行なわせる必要がある実装というのもあり得るでしょう。
しかし、
これはコンパイラにとっては現実的ではありません。
すべての仮想メソッドの呼び出しにおいて、
同期化が必要かどうかを実行時にテストしなければならないからです。)
gcj
においては、
synchronized
属性はメソッドの実装側で処理されるので、
synchronized
属性を持つネィティブメソッドの場合は、
(そのメソッドのC++実装において)
同期化を処理するかどうかは、
そのメソッドのプログラマ次第となります。
言葉を換えれば、
native synchornized
メソッドにおいてはJvSynchronize
の呼び出しを手作業で追加する必要があるということです。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
main()
関数から一度呼び出されなければなりません.
推奨はされませんが、
JvCreateJavaVM()
を複数回呼び出すことは、
その呼び出しが単一のスレッドから行なわれるいう条件のもとでは安全です。
vm_argsパラメータは、
Javaランタイムの初期化パラメータを指定するのに使うことができます。
NULL
であっても構いません。
この関数は、
成功したときには0
を返します。
また、
ランタイムが既に初期化済みであるときは、
-1
を返します。
注: GCJ 3.1では、
vm_args
パラメータは無視されます。
将来のリリースでは使われる可能性があります。
JvCreateJavaVM
の呼び出しのあとでなければなりません。
nameには、
スレッドの名前を指定します。
NULL
であっても構いません。
その場合は、
名前は生成されることになります。
groupは、
このスレッドがメンバとなるThreadGroupです。
NULL
が指定されると、
スレッドはメインスレッドグループのメンバとなります。
戻り値は、
このスレッドを表わすJavaのThread
オブジェクトです。
同一のスレッドからJvAttachCurrentThread()
を複数回呼び出しても安全です。
そのスレッドに既にアタッチ済みであれば、
この関数の呼び出しは無視され、
カレントスレッドオブジェクトが返されます。
JvAttachCurrentThread()
を使ってアタッチされたスレッドによって、
Javaコードの呼び出しが終了した後に、
呼び出されるべきものです。
この呼び出しによって、
そのスレッドに関連付けされていたすべてのリソースが、
ガーベジコレクションの適格候補となります。
この関数は、
成功したときには0
を返します。
カレントスレッドがアタッチされていない場合は、
-1
を返します。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
out
フィールドにアクセスするために、
java.lang.System
クラスが初期化されて、
Javaの文字列が出力されています。
最後に、
Javaの呼び出しが終了した後に、
スレッドがランタイムからディタッチされています。
捕捉されなかったすべての例外に対するデフォルトハンドラを提供するべく、
すべてのコードがtry/catchブロックによって囲まれています。
この例は、
c++ test.cc -lgcj
によってコンパイルできます。
// test.cc #include <gcj/cni.h> #include <java/lang/System.h> #include <java/io/PrintStream.h> #include <java/lang/Throwable.h> int main(int argc, char *argv) { using namespace java::lang; try { JvCreateJavaVM(NULL); JvAttachCurrentThread(NULL, NULL); String *message = JvNewStringLatin1("Hello from C++"); JvInitClass(&System.class$); System::out->println(message); JvDetachCurrentThread(); } catch (Throwable *t) { System::err->println(JvNewStringLatin1("Unhandled Java exception:")); t->printStackTrace(); } } |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
jfieldID
型とjmethodID
型は、
JNIのものと同様です。
以下の関数
JvFromReflectedField
,
JvFromReflectedMethod
,
JvToReflectedField
JvToFromReflectedMethod
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |