[[表紙|/]] [[編集|EDIT]] [[短縮|TURL]] 一応基本的な事を。 このページは[[http://www.digitalmars.com/d/cpp_interface.html|http://www.digitalmars.com/d/cpp_interface.html]]の翻訳(の作業場)デス。 迷った翻訳は後回し 訳間違いはガンガン直してください。D言語リファレンス 日本語版に流用していただければ。 * C++とのインターフェース DはCとの完全なインターフェースを持ちますが、C++とのインターフェースは大きく制限されています。C++とリンクするには3つの方法があります。 1. C++の機能にあるCインターフェースを利用し、DからCとのインターフェースを使ってリンクする。 2. C++の機能にあるCOMインターフェースを利用し、DからCOMとのインターフェースを使ってリンクする。 3. 以下に記述する方法でC functions and classesと限定的ながら直接結合する。 (訳註:C++ {functions and classes}の誤植か、{C関数}とクラスなのかは後の翻訳で……誤植な感じ) ** 基本的な考え方 C++と100%互換性を持たせることは、事実上完全なC++コンパイラをDフロントエンド上に加えるに等しいことです。過去の事例から言えば、それを実現するには最低でも10人年(訳註:労働用語)が必要で、基本的にそのような互換性を持つDコンパイラは実装不可能です。C++との結合を作ろうとしている他の言語でも似たような問題を抱え、以下のような解決策がとられてきました: 1. COMインターフェースのサポート(ただしWindowsのみ) 2. C++コードにCラッパを無理やりかぶせる 3. SWIGのようにCラッパを自動的に作るツールを使う 4. C++コードを別の言語で再実装する 5. あきらめる Dは現実的なアプローチとして、以下の2~3のささやかな用立てで多くの問題を解決します ・C++の名前マングリング規約と合わせる ・C++の関数呼び出し規約と合わせる ・C++の仮想関数テーブルと合わせる(単一クラス継承の場合) (訳註:matchの訳は「対応させる」がいいかも) ** C++のグローバル関数をDから呼び出す C++関数が以下のソースコードで与えられている場合、 #include <iostream> using namespace std; int foo(int i, int j, int k) { cout << "i = " << i << endl; cout << "j = " << j << endl; cout << "k = " << k << endl; return 7; } 対応する以下のDコードでは、fooはC++のリンケージと関数呼び出し規約を持つものとして宣言されます。 extern (C++) int foo(int i, int j, int k); これでDから関数を呼ぶことができます。 extern (C++) int foo(int i, int j, int k); void main() { foo(1,2,3); } 一つ目のコードをC++コンパイラで、二つ目のコードをDコンパイラでコンパイルし、双方をリンクして走らせると、以下のような出力されます。 i = 1 j = 2 k = 3 何が起きているのかをもっと具体的に説明しますと: ・Dコンパイラは、C++の関数名がどのようにmangleされているのか、 またC++の関数の正しい呼び出し/復帰手続きについて、把握しています。 ・(訳註:Dの)モジュールはC++の一部ではないため、C++リンケージの関数は(訳註:モジュールを無視して)グローバルスコープで唯一(訳註:の名前?)でなければなりません。 ・__cdecl, __far, __stdcall, __declspec などの非標準のC++拡張はDにはありません。 ・Dにはvolatile修飾子はありません。 ・Dの文字列は0終端になっていません。 詳しくは "データ型の互換性" をご覧下さい。 ただし、文字列リテラルは0終端になっています。 ** Dのグローバル関数をC++から呼び出す D関数をC++からアクセス可能にするには、C++リンケージを与えます: import std.stdio; extern (C++) int foo(int i, int j, int k) { writefln("i = %s", i); writefln("j = %s", j); writefln("k = %s", k); return 1; } extern (C++) void bar(); void main() { bar(); } C++側はこのようになります: int foo(int i, int j, int k); void bar() { foo(6, 7, 8); } コンパイルしてリンクして実行させると以下の出力が得られます: i = 6 j = 7 k = 8 ** クラス Dのクラスは唯一のルートクラスObjectから派生しており、C++のクラスレイアウトとは互換性がありません。ですが、DのインターフェースはC++の単一継承ヒエラルキーとかなり似ています。ですから、extern (C++) 属性をつけたDインターフェースの仮想関数ポインタテーブル(vtbl[])はC++のそれと厳密に対応させることができます。通常ののD++インターフェースのvtbl[]の最初のエントリはDのRTTI infoを指すポインタですが、一方でC++(訳註:リンケージ?)のそれは最初の仮想関数を指しています。 *** C++仮想関数のDからの呼び出し C++で以下のようにクラスを定義したコードを与えられた場合: #include <iostream> using namespace std; class D { public: virtual int bar(int i, int j, int k) { cout << "i = " << i << endl; cout << "j = " << j << endl; cout << "k = " << k << endl; return 8; } }; D *getD() { D *d = new D(); return d; } Dコードからは以下のように使えます: extern (C++) { interface D { int bar(int i, int j, int k); } D getD(); } void main() { D d = getD(); d.bar(9,10,11); } *** D仮想関数のC++からの呼び出し 以下のDコードが与えられた場合: extern (C++) int callE(E); extern (C++) interface E { int bar(int i, int j, int k); } class F : E { extern (C++) int bar(int i, int j, int k) { writefln("i = ", i); writefln("j = ", j); writefln("k = ", k); return 8; } } void main() { F f = new F(); callE(f); } それにアクセスするC++コードは以下の通り: class E { public: virtual int bar(int i, int j, int k); }; int callE(E *e) { return e->bar(11,12,13); } 注意: ・非仮想関数とstaticメンバ関数にはアクセスできません。 ・クラスのフィールドにはgetter / setter となる仮想関数を使ってしかアクセスできません。 ** 関数のオーバーロード C++とDの関数オーバーロード規則は異なります。Dソースコード中では、extern (C++)付き関数を呼んだとしても、Dのオーバーロード規則に従います。 ** メモリ割り当て C++コードでは::operator new()と::operator delete()を呼んで明示的にメモリを管理します。一方Dではガベージコレクタでメモリを割り当てるので、明示的なdeleteは不要です。DのnewとdeleteはC++の::operator new()と::operator delete()とは互換性がありません。C++の::operator newでメモリを割り当ててDのdeleteで消す、あるいはその逆をしようとすれば、悲惨な結果を招くでしょう。 mallocで確保されたバッファを要求するようなC++の関数と連携するために、 Dでは c.stdlib.malloc() や c.stdlib.free() の呼び出しによって明示的にメモリ管理を行うこともできます。 Dのガベージコレクタで確保したメモリへのポインタを渡すには、C++の関数がそのメモリを使い終わる前にガベージコレクタが領域を開放してしまう、といった事故が起きないことを確かめなくてはなりません。これは幾つかの方法で実現できます: (訳註:garbage collectはfreeやdeleteとは限らないので、「開放」に限らず「処理」「片付け」という訳語のほうが適しているかもしれません。その場合Cとのインターフェースもあわせていただけると……120氏ご指摘感謝) ・c.stdlib.malloc() で確保した領域にデータをコピーし、 そちらを代わりに渡す。 ・その領域へのポインタをスタック上(引数か、自動変数)に残しておく。 ガベージコレクタはスタック上のオブジェクトは生きていると判定します。 ・その領域へのポインタを静的データ領域に残しておく。 ガベージコレクタは静的データ領域のオブジェクトは生きていると判定します。 ・そのポインタを、gc.addRoot() か gc.addRange() によってガベージコレクタへ登録する。 オブジェクトがまだ使われていることをGCに知らせるには、割り当てられたメモリ領域の内部へのポインタがあれば十分です。領域の先頭へのポインタを保持しておく必要はありません。 Dによって作られた以外のスレッドのスタックや、他のDLLに属するデータセグメントについては、ガベージコレクタによって探索されません。 ** データ型の互換性 (省略) ** 構造体と共用体 Dの構造体と共用体は、Cのそれとほぼ同じです。(訳註:C++の、ではなく) Cのコードでは、実装特有の#pragmaやコンパイラのコマンドスイッチによって、構造体の整列を制御します。これに対応するものとして、Dには明示的なアラインメント属性が用意されています。C側でのアラインメントを調べて、D側の構造体宣言に明示的にその値を設定して下さい。 Dはビットフィールドをサポートしません。必要ならば、シフトとビットマスク演算によってエミュレートできます。 htodはビットフィールドを右シフトとマスクを使ったインライン関数に変換します。(訳註:日本語訳のCとのインターフェースにはこの1文が抜けてます) ** オブジェクトのコンストラクトとデストラクト ストレージメモリへの割り当てと開放と同じように、DのコードでコンストラクトされたオブジェクトはDで、C++のコードでコンストラクトされたオブジェクトはC++でデストラクトされなければなりません。 ** 特殊なメンバ関数 DはC++の特殊なメンバ関数は呼べませんし、逆もしかりです。特殊なメンバ関数には、コンストラクタ、デストラクタ、変換(訳註:cast?)オペレータ、演算子オーバーロード、割り当て関数(newとdelete)が含まれます。 ** 実行時型識別 Dの実行時型識別はC++のそれとはまったく異なった技術を使っています。両者は互換性がありません。 ** C++クラスの値型オブジェクト DはPOD (Plain Old Data) 型C++の構造体にアクセスできますし、C++の仮想関数には参照を使ってアクセスできます。DはC++の値型オブジェクトにはアクセスできません(=Dはクラスオブジェクトにはすべて参照経由でアクセスします) (訳註:C++構造体の関数にはアクセスできる?メンバは?継承していたら?D側でnewできる?構造体扱いなら仮想関数にはアクセスできないがメンバと非仮想関数にアクセスできる?) ** C++テンプレート DのテンプレートはC++のテンプレートとわずかな共通性しか持ちません。C++テンプレートをリンク互換性のある方法でDコードで表現する適切な方法などは見つかりそうにありません。 このことは、C++ STLとC++ BoostはDからは利用できないことを意味します。 ** 例外処理 DとC++の例外処理はまったく異なっています。DとC++のコードをまたいで例外が飛んでいくような場合は正しく機能しません。 ** 将来の開発 将来的にC++0xが標準になった場合のこの機能への影響はまだ分かりません。 時間がたてば、C++ ABIのより多くの側面がDから直接アクセスできるようになるでしょう。