[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10. CNI

ここではCNIについて記載します。 CNIは、 Cygnus Native Interfaceの略であり、 C++を使ってJavaネィティブメソッドを書くための便利な方法です。 標準的なJNI (Javaネィティブインタフェース) に対する、 より効率的かつより便利な、 しかし移植性は弱い代替策です。
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] [ ? ]

10.1 基本的概念

言語の機能という観点からすると、 JavaはほぼC++のサブセットになっています。 Javaにはいくつか重要な拡張機能があり、 強力な標準クラスライブラリも付いていますが、 全体としては、 これらも基本的な類似性を変えるものではありません。 Javaはハイブリッドなオブジェクト指向言語であり、 クラス型に加えていくつかのネィティブ型を持っています。 Javaにおいては、 クラスがベースになっていて、 クラスには、 オブジェクトのインスタンスごとに存在するフィールドに加えて静的フィールドがあります。 また、 インスタンスメソッドに加えて静的メソッドがあります。 静的でないメソッドは仮想メソッドにすることができ、 オーバロードすることができます。 オーバローディングは、 実際の引数の型をパラメータの型に対応させることによって実行時に解決されます。 仮想メソッドは、 ディスパッチテーブル (仮想関数テーブル) を経由する間接呼び出しを使って実装されます。 オブジェクトはヒープ上に割り当てられ、 コンストラクタメソッドを使って初期化されます。 クラスはパッケージ階層に組織化されます。 これまで列挙したような属性はすべてC++においても妥当します。 ただし、 C++にはさらに追加的な特徴があります (例えば、 C++オブジェクトは、 ヒープ上に割り当てることもできますが、 静的に、 あるいは、 ローカルスタックフレーム上に割り当てることもできます)。 gcjはG++と同一のコンパイラ技術 (GNU C++コンパイラ) を使っていますので、 両方の言語の共通部分において同一のABI (オブジェクト表現、および、呼び出し規約) を使うようにすることが可能です。 CNIにおける主たるアイデアは、 JavaオブジェクトはC++オブジェクトであり、 すべてのJavaクラスはC++クラスである (しかしその逆ではない) というところにあります。 そこで、 JavaとC++を統合するにあたって最重要なタスクは、 余計な非互換性を除去することにあります。 CNIコードは通常のC++ソースファイルとして書きます。 (Java/CNIを認識するC++コンパイラ、 具体的には最近のバージョンのG++を使わなければなりません。) CNI C++ソースファイルには以下の記述がなければなりません。
 
#include <gcj/cni.h>
さらに、 個々のJavaクラスごとに1つのヘッダファイルをインクルードしなければなりません。 以下に例を示します。
 
#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] [ ? ]

10.1.1 制限

JavaクラスがC++クラスであるということは、 Javaの拘束から解放されるということを意味しません。 CNI C++クラスは、 Java言語の規則に従わなければなりません。 例えば、 CNIクラスの中にCの文字列 (char*) を引数として取るメソッドを宣言することはできません。 また、 Javaのデータ型以外のデータ型のメンバ変数を宣言することもできません。
[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.2 パッケージ

Javaにおける唯一のグローバル名はクラス名とパッケージ名です。 package (パッケージ) は、 ゼロ個以上のクラスを持つことができ、 ゼロ個以上のサブパッケージを持つこともできます。 すべてのクラスは無名パッケージ、 あるいは、 階層的かつグローバルに一意な名前を持つあるパッケージのいずれかに属します。 JavaのパッケージはC++の namespace (ネームスペース) にマップされます。 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] [ ? ]

10.2.1 パッケージ名の省略

常に完全修飾されたJavaクラス名を使うのはうんざりするほど冗長になることがあります。 また完全修飾名を使うと、 コードが単一のパッケージに結び付けられてしまい、 クラスのパッケージが変更されるとコードの変更が必要になってしまいます。 Javaの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;
Javaでは必要に応じてインポートを行なわせることもできます。 以下のようにします。
 
import package-name.*;
こうすることによって、 プログラムテキストの中でpackage-nameパッケージの任意のクラスをそのクラス名だけで参照することができるようになります。 C++において同じことを達成するには、 以下のようにしなければなりません。
 
using namespace package-name;

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.3 プリミティブ型

Javaは、 整数、 浮動小数点数、 文字、 論理値を表わす8種類のプリミティブ型 (および、 void型) を提供しています。 C++も非常に良く似た具体的な型を独自に持っています。 しかし、 C++におけるこれらの型の実装は必ずしも常に同一であるとは限りません (例えばintは、 16ビット、 32ビット、 あるいは、 64ビットとして実装される可能性があります)。 したがって、 CNIは、 Javaの各プリミティブ型に対応した特殊なC++型を提供しています。
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 値なし
Javaの型に言及する際には、 期待外れな誤解を避けるためにも、 これらのC++型名 (例えばjint) を使うべきです。
[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.3.1 プリミティブ型に対応するリファレンス型

Javaの各プリミティブ型には対応するリファレンス型があります。 例えば、 booleanに対応するのはjava.lang.Booleanクラスです。 こうしたクラスの扱いを簡単にするために、 GCJはJvPrimClassマクロを提供しています。
マクロJvPrimClass(型):
指定された型に対応するClassオブジェクトへのポインタを返します。
 
JvPrimClass(void) => java.lang.Void.TYPE

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.4 インタフェース

Javaクラスは、 単一の基底クラスからの継承に加えて、 ゼロ個以上のインタフェース実装することができます。 CNIでは、 CNIのコードにインタフェースのメソッドを実装させることができます。 また、 いくつか制限はありますが、 インタフェースに対するリファレンスを通じてメソッドを呼び出すこともできます。 CNIは、 インタフェースの継承をまったく認識しません。 したがって、 あるインタフェースメソッドを呼び出すことができるのは、 呼び出しの対象となるフィールドの宣言上の型が、 そのメソッドを宣言しているインタフェースと一致している場合だけです。 そのインタフェースリファレンスを正しい上位インタフェースにキャストすることによって、 この制限を回避することができます。 例えば、
 
interface A 
{ 
  void a(); 
} 
 
interface B extends A 
{ 
  void b(); 
} 
において、 C++のコードで型がBの変数を宣言している場合、 その変数をまずAにキャストしない限りa()を呼び出すことはできません。
[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.5 オブジェクトとクラス


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.5.1 クラス

すべてのJavaクラスはjava.lang.Objectを継承しています。 C++には唯一のルートクラスというものは存在しませんが、 私たちは、 Javaのjava.lang.Objectクラスに対応するC++クラスjava::lang::Objectを使っています。 その他のすべてのJavaクラスは、 java::lang::Objectを継承する、 対応するC++クラスにマップされます。 インタフェース継承 (implementsキーワード) は、 現在のところC++へのマッピングには反映されません。
[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.5.2 オブジェクトフィールド

個々のオブジェクトにはオブジェクトヘッダがあり、 その後ろに、 クラスのインスタンスフィールドがあります。 オブジェクトヘッダは、 ディスパッチテーブル、 あるいは、 仮想関数テーブルへの単一のポインタです。 (オブジェクトのに追加的なフィールドが存在する可能性はあります。 例えば、 メモリ管理用のフィールドです。 しかし、 これはアプリケーションからは不可視であり、 オブジェクトに対するリファレンスはディスパッチテーブルへのポインタを指しています。) フィールドは、 C++の場合と同一の順序、 境界整列、 サイズによって展開されます。 具体的には、 8ビットおよび16ビットのネィティブ型 (byteshortcharboolean) は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] [ ? ]

10.5.3 アクセス指定子

CNIは、 Javaのアクセス指定子を厳密に守ってはいません。 Javaにおけるアクセス許可はC++におけるアクセス許可に直接マップできないからです。 Javaのprivateフィールドおよびprivateメソッドは、 C++のprivateフィールドおよびprivateメソッドにマップされます。 しかし、 その他のフィールドおよびメソッドは、 publicフィールドおよびpublicメソッドにマップされます。
[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.6 クラスの初期化

Javaにおいては、 個々のクラスが最初に実際に使われるときに自動的に初期化されることが必要です。 クラスの初期化には、 静的フィールドの初期化、 クラスの初期化メソッドの実行、 基底クラスの初期化などが伴います。 ほかにも、 例えばコード中の文字列リテラルに対応する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] [ ? ]

10.7 オブジェクト割り当て

新規に作成されたJavaオブジェクトはクラスインスタンス作成式を使って割り当てられます。 例えば、 以下のような式です。
 
new Type ( ... )
同一の構文がC++においても使えます。 主な相違点は、 C++オブジェクトは明示的に削除されなければならないというところにあります。 Javaでは、 オブジェクトはガーベジコレクタによって自動的に削除されます。 CNIを使えば、 新規のJavaオブジェクトを標準的なC++の構文を使って割り当てることができます。 この場合、 C++コンパイラはガーベジコレクタからメモリを割り当てます。 オーバロードされたコンストラクタがあれば、 コンパイラは、 標準的なC++のオーバロード解決ルールを使って正しいコンストラクタを選択します。 以下に例を示します。
 
java::util::Hashtable *ht = new java::util::Hashtable(120);
Function: void* _Jv_AllocBytes (jsize size)
sizeバイトをヒープから割り当てます。 このメモリはガーベジコレクタによって検査されることはありませんが、 そのメモリへのリファレンスが見つからなければ解放されます。

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.8 配列

Javaは多くの点においてC/C++に似ていますが、 配列の取り扱いは非常に異なります。 Cの配列は、 ポインタに対する算術演算というアイデアに基づいていますが、 これはJavaのセキュリティ要件とは両立しないでしょう。 Javaの配列は本物のオブジェクトです (配列型は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]; }
};
JNItypedefに対応する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;
Method: template<class T> T* elements (JArray<T> array)
このテンプレート関数を使って、 arrayで指定される配列の要素に対するポインタを取得することができます。 例えば、 int[]を構成する整数型要素に対するポインタは、 以下のようにして取得することができます。
 
extern jintArray foo;
jint *intp = elements (foo);
この関数の名前は将来変更される可能性があります。
Function: jobjectArray JvNewObjectArray (jsize length, jclass klass, jobject init)
ここでのklassは配列要素の型、 initは配列の各スロットにセットされる初期値です。

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.8.1 配列の作成

各プリミティブ型に対応して、 その型の配列を新規に作成するのに使うことのできる関数が提供されています。 関数の名前は以下のような形式となります。
 
JvNewTypeArray
例えば、
 
JvNewBooleanArray
は、 Javaプリミティブ型であるbooleanの配列を作成するのに使うことができます。 以下の関数定義は、 これらの関数すべてに対するテンプレートです。
Function: jbooleanArray JvNewBooleanArray (jint length)
インデックス長がlengthである配列を作成します。
Function: jsize JvGetArrayLength (jarray array)
arrayで指定される配列の長さを返します。

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.9 メソッド

Javaのメソッドは直接C++のメソッドにマップされます。 gcjhによって生成されるヘッダファイルには、 適切なメソッド定義が含まれています。 基本的には、 生成されたメソッドは、 Javaメソッドと同一の名前と 対応する型を持っていて、 自然な方法で呼び出されます。
[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.9.1 オーバロード

JavaおよびC++はいずれもメソッドのオーバロードを提供しています。 これは、 1つのクラスが同じ名前のメソッドを複数持っていて、 正しいメソッドが (コンパイル時に) 引数の型をもとに選択されるというものです。 正しいメソッドを選択するためのルールは (予想どおり) JavaにおいてよりもC++においてのほうが複雑です。 しかし、 C++コンパイラは、 gcjhによって生成された複数のオーバロードメソッドをもとに、 期待どおりのメソッドを選択するでしょう。 一般的なアセンブラやリンカは、 C++オーバロードのことを知っていません。 したがって、 標準的な実装方法は、 メソッドの引数の型をアセンブリレベルの名前の一部にエンコードすることです。 このエンコードのことをマングル(mangling)と呼びます。 また、 エンコードされた名前のことをマングルドネーム(mangled name)と呼びます。 これと同じ機構がJavaにおけるオーバロードの実装にも使われます。 C++とJavaの相互運用性のためには、 JavaコンパイラとC++コンパイラが同一のエンコードスキーマを使うことが重要です。
[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.9.2 静的メソッド

CNIでは、 Javaの静的メソッドは 標準的なC++構文を使って呼び出されます。 すなわち、 .オペレータではなく::オペレータが使われます。 以下に例を示します。
 
jint i = java::lang::Math::round((jfloat) 2.3);
静的ネィティブメソッドの定義にはC++のメソッド定義構文が使われます。 以下に例を示します。
 
#include <java/lang/Integer>
java::lang::Integer*
java::lang::Integer::getInteger(jstring str)
{
  ...
}

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.9.3 オブジェクトコンストラクタ

コンストラクタは、 newオペレータを使ったオブジェクト割り当ての一環として暗黙のうちに呼び出されます。 以下に例を示します。
 
java::lang::Integer *x = new java::lang::Integer(234);
Javaでは、 コンストラクタがネィティブメソッドであることは許されません。 しかし、 コンストラクタがネィティブメソッドを呼び出すことは可能なので、 この制限を回避するコードを書くことは可能です。
[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.9.4 インスタンスメソッド

C++ CNIメソッドからJavaのインスタンスメソッドを呼び出すのには、 標準的なC++構文が使われます。 以下に例を示します。
 
// 最初にJavaオブジェクトを作成する
java::lang::Integer *x = new java::lang::Integer(234);
// 次にメソッドを呼び出す
jint prim_value = x->intValue();
if (x->longValue == 0) 
  ...
Javaネィティブメソッドの定義もまた自然な形で行なわれます。
 
#include <java/lang/Integer.h>

jdouble
java::lang:Integer::doubleValue()
{
  return (jdouble) value;
}

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.9.5 インタフェースメソッド

Javaにおいては、 メソッドをインタフェースリファレンスを使って呼び出すことができます。 この方法は不完全な形でサポートされています。 10.4 インタフェースを参照してください。
[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.10 文字列

CNIは、 JavaのStringオブジェクトを取り扱うためのユーティリティ関数をいくつか提供しています。 その名前やインタフェースは、 JNIにおけるものに似ています。
Function: jstring JvNewString (const char* chars, jsize len)
Cの文字列charsの配列中のインデックスlenまでの文字列から構成されるJavaのStringオブジェクトを返します。
Function: jstring JvNewStringLatin1 (const char* bytes, jsize len)
bytesの長さlenのバイト列から構成されるJavaのStringオブジェクトを返します。
Function: jstring JvNewStringLatin1 (const char* bytes)
Stringの長さがstrlen(bytes)である点を除き、 前の関数と同じです。
Function: jstring JvNewStringUTF (const char* bytes)
Cの文字列bytesの中にあるUTFエンコードされた文字列から構成されるStringを返します。
Function: jchar* JvGetStringChars (jstring str)
Stringオブジェクトstrの文字列から構成される配列へのポインタを返します。
Function: int JvGetStringUTFLength (jstring str)
Stringオブジェクトstrの内容をUTF-8エンコードするために必要とされるバイト数を返します。
Function: jsize JvGetStringUTFRegion (jstring str, jsize start, jsize len, char* buf)
Stringオブジェクトstrのある部分をUTF-8エンコードしたものをバッファbufに入れます。 取り出すべき部分は先頭位置startと終端位置lenで示されます。 bufはバッファであり、 Cの文字列ではないことに注意してください。 その終端はNULLではありません

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.11 C/C++との相互運用

CNIは、 Javaのクラスやメソッドを表現するために設計されていますので、 そのまま直ちにC/C++の型を混在させることはできません。 重要な制限の中に、 Javaのクラスが、 Javaに無い型のインスタンス変数、 静的変数を持つことができないこと、 また、 Javaに無い型を引数または戻り値の型として取るメソッドを持つことができないということがあります。 CNIにおいては以下のどれも可能ではありません。
 
class ::MyClass : public java::lang::Object
{
   char* variable;  // char*はJavaにおいては正当な型ではない
}


uint
::SomeClass::someMethod (char *arg)
{
  .
  .
  .
}   // uintchar*もJavaにおいては正当な型ではない
もちろん、 メソッドの有効範囲内においてC/C++型を使うことには問題はありません。
 
jint
::SomeClass::otherMethod (jstring str)
{
   char *arg = ...
   .
   .
   .
}
しかし、 この制限は問題を引き起こします。 そのため、 CNIには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] [ ? ]

10.12 例外処理

C++とJavaは一般的な例外処理フレームワークを共有していますが、 両者は完全に統合されているわけではありません。 主要な問題点は、 これら2つの言語が持つ、 実行時の型情報に関する機能が、 統合されていないことです。 それにもかかわらず、 実際にはかなりうまく機能します。 通常のthrowを使ってC++からJavaの例外を投げることができます。 そして、 この例外をJavaのコードの中で捕捉することができます。 また同様に、 Javaから投げられた例外をC++のcatchを使って捕捉することもできます。 以下に例を示します。
 
if (i >= count)
   throw new java::lang::IndexOutOfBoundsException();
通常、 Javaの例外を使うC++コードを書くと、 G++はそのことを自動的に検出して、 それらの例外を適切に処理します。 しかし、 Javaの例外がC++側に投げられたときに、 C++コードがする必要のあることが単にデストラクタを呼び出すことだけである場合、 GCCはそのことを正しく推測してくれないでしょう。 この問題を持つコードのサンプルを以下に示します。
 
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] [ ? ]

10.13 同期化

個々のJavaオブジェクトはどれも暗黙的なモニタを持っています。 Java VMは、 monitorenter命令を使ってモニタのロックを取得し、 monitorexit命令を使ってモニタのロックを解放します。 これらに対応するCNIマクロはJvMonitorEnterJvMonitorExitです (JNIにも類似のメソッドMonitorEnterMonitorExitがあります)。 Javaソース言語は、 これらのプリミティブに直接アクセスする手段を提供していません。 その代わりになるものとしてsynchronized文があります。 これは、 ブロックに入る前に暗黙的にmonitorenterを呼び出し、 ブロックから出るときにmonitorexitを呼び出します。 そのブロックの実行が例外によって異常中断させられる場合でもロックは解放されなければならないということに注意してください。 このことは、 同期化ロックを囲む暗黙的なtry finallyが存在することを意味しています。 C++においては、 ロックを解放するのにデストラクタを使うのが合理的です。 CNIは、 以下に示すユーティリティクラスを定義しています。
 
class JvSynchronize() {
  jobject obj;
  JvSynchronize(jobject o) { obj = o; JvMonitorEnter(o); }
  ~JvSynchronize() { JvMonitorExit(obj); }
};
したがって、 以下のJavaコード
 
synchronized (OBJ)
{
   CODE
}
は、 例えば以下のようなC++コードになるでしょう。
 
{
   JvSynchronize dummy (OBJ);
   CODE;
}
Javaにはsynchronized属性を持つメソッドもあります。 これは、 メソッド本体全体をsynchronized文で囲むことと同じです。 (あるいは、 呼び出し側で同期化を行なわせる必要がある実装というのもあり得るでしょう。 しかし、 これはコンパイラにとっては現実的ではありません。 すべての仮想メソッドの呼び出しにおいて、 同期化が必要かどうかを実行時にテストしなければならないからです。) gcjにおいては、 synchronized属性はメソッドの実装側で処理されるので、 synchronized属性を持つネィティブメソッドの場合は、 (そのメソッドのC++実装において) 同期化を処理するかどうかは、 そのメソッドのプログラマ次第となります。 言葉を換えれば、 native synchornizedメソッドにおいてはJvSynchronizeの呼び出しを手作業で追加する必要があるということです。
[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.14 呼び出し

CNIは、 JavaコードからC++を呼び出すことができるようにするだけでなく、 C++アプリケーションがJavaクラスを呼び出すこともできるようにします。 呼び出しAPIとして知られているいくつかの関数が、 そのために提供されています。
Function: jint JvCreateJavaVM (void* vm_args)
Javaランタイムを初期化します。 この関数は、 スレッドインタフェース、 ガーベジコレクタ、 例外処理、 および、 ランタイムのその他の主側面の基本的な初期化を実行します。 これは、 アプリケーションによって、 他のJava呼び出し、 CNI呼び出しのすべての先立って、 非Javaのmain()関数から一度呼び出されなければなりません. 推奨はされませんが、 JvCreateJavaVM()を複数回呼び出すことは、 その呼び出しが単一のスレッドから行なわれるいう条件のもとでは安全です。 vm_argsパラメータは、 Javaランタイムの初期化パラメータを指定するのに使うことができます。 NULLであっても構いません。 この関数は、 成功したときには0を返します。 また、 ランタイムが既に初期化済みであるときは、 -1を返します。 注: GCJ 3.1では、 vm_argsパラメータは無視されます。 将来のリリースでは使われる可能性があります。
Function: java::lang::Thread* JvAttachCurrentThread (jstring name, java::lang::ThreadGroup* group)
既存スレッドをJavaランタイムに登録します。 これは、 その他のJava呼び出し、 CNI呼び出しを行なう個々のスレッドによって、 それらの呼び出しすべてに先立って一度呼び出されなければなりません。 また、 その呼び出しはJvCreateJavaVMの呼び出しのあとでなければなりません。 nameには、 スレッドの名前を指定します。 NULLであっても構いません。 その場合は、 名前は生成されることになります。 groupは、 このスレッドがメンバとなるThreadGroupです。 NULLが指定されると、 スレッドはメインスレッドグループのメンバとなります。 戻り値は、 このスレッドを表わすJavaのThreadオブジェクトです。 同一のスレッドからJvAttachCurrentThread()を複数回呼び出しても安全です。 そのスレッドに既にアタッチ済みであれば、 この関数の呼び出しは無視され、 カレントスレッドオブジェクトが返されます。
Function: jint JvDetachCurrentThread ()
Javaランタイムからスレッドの登録を取り消します。 これは、 JvAttachCurrentThread()を使ってアタッチされたスレッドによって、 Javaコードの呼び出しが終了した後に、 呼び出されるべきものです。 この呼び出しによって、 そのスレッドに関連付けされていたすべてのリソースが、 ガーベジコレクションの適格候補となります。 この関数は、 成功したときには0を返します。 カレントスレッドがアタッチされていない場合は、 -1を返します。

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.14.1 捕捉されなかった例外の処理

呼び出しAPIを使って呼び出されたJavaコードから例外が投げられて、 その例外のハンドラが見つからなかった場合、 ランタイムはそのアプリケーションを異常停止します。 アプリケーションをもっと堅牢なものにするためにも、 すべてのJava例外を捕捉するトップレベルのtry/catchブロックによって、 呼び出しAPIを使うコードを囲むことが推奨されます。
[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

10.14.2 実例

以下のコードは、 呼び出しAPIの使い方を示すものです。 この例でのC++アプリケーションは、 Javaランタイムを初期化して自分自身をアタッチします。 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] [ ? ]

10.15 リフレクション

CNIコードにおいてリフレクションを使うことも可能です。 JNIにおけるリフレクションと同様に機能します。 jfieldID型とjmethodID型は、 JNIのものと同様です。 以下の関数 が、 まもなく追加されるでしょう。 JNIの関数に対応する他の関数も同様です。
[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by TurboLinux User on February, 6 2003 using texi2html