| [ < ] | [ > ] | [ << ] | [ 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] | [ ? ] |