Cとのインターフェイス
D は、システムのCコンパイラと相性良く動作するように設計されています。 独自のVMを持ったりはせず、既存のCのランタイムライブラリを使うのです。 せっかく沢山のC APIが既に存在するというのに、それを D に移植したり D 用のラッパを書いたり…という作業はばかげています。 直接Cの関数を呼び出せるというのは、なんと楽なことでしょうか。
データ型やそのメモリ配置、関数の呼び出し規約をCコンパイラと合わせることで、 Cとの互換性が実現されています。
C の関数を呼び出す
Cの関数はDから直接呼び出せます。 ラッパ関数や引数のすりあわせとか、 Cの関数を別のDLLに分けたりする必要は全くありません。
Cの関数は呼び出し規約の指定付きで宣言します。大抵は "C" 呼び出し規約で問題ないでしょう。例えば:
extern (C) int strcmp(char* string1, char* string2);
すると、Dのコードからは当たり前のように呼び出すことができます:
import std.string; int myDfunction(char[] s) { return strcmp(std.string.toStringz(s), "foo"); }
何が起きているのかをもっと具体的に説明しますと:
- Dコンパイラは、C の関数名がどのようにmangleされているのか、 またCの関数の正しい呼び出し/復帰手続きについて、把握しています。
- Cの関数を別の同名の C関数でオーバーロードすることはできません。
- __cdecl や __far, __stdcall, __declspec, などのCの型修飾子は D にはありませんが、これは extern (C) のように属性として処理されています。
- Dにはconstやvolatile修飾子もありません。 これらを使ったCの関数を宣言するには、 単にその修飾子を省いて宣言します。
- Dの文字列は0終端になっていません。 詳しくは "データ型の互換性" をご覧下さい。 ただし、文字列リテラルは0終端になっています。
extern (C) 属性などで Cコンパイラと互換性があるようにコンパイルされた D の関数なら、逆にCのコードからDの関数を呼び出すことも可能です:
// myfunc() はCのどの関数からも呼び出せる extern (C) { void myfunc(int a, int b) { ... } }
メモリ割り当て
C では、malloc() や free() によって明示的にメモリを管理します。 一方 D ではガベージコレクタでメモリを割り当てるので、 明示的な free は不要です。
mallocで確保されたバッファを要求するようなCの関数と連携するために、 D では c.stdlib.malloc() や c.stdlib.free() の呼び出しによって 明示的にメモリ管理を行うこともできます。
Dのガベージコレクタで確保したメモリへのポインタを渡すには、 Cの関数がそのメモリを使い終わる前にガベージコレクタが領域を解放してしまう、 といった事故が起きないことを確かめなくてはなりません。 これは幾つかの方法で実現できます:
- c.stdlib.malloc() で確保した領域にデータをコピーし、 そちらを代わりに渡す。
- その領域へのポインタをスタック上(引数か、自動変数)に残しておく。 ガベージコレクタはスタック上のオブジェクトは生きていると判定します。
- その領域へのポインタを静的データ領域に残しておく。 ガベージコレクタは静的データ領域のオブジェクトは生きていると判定します。
- そのポインタを、gc.addRoot() か gc.addRange() によってガベージコレクタへ登録する。
オブジェクトがまだ使われていることを GC に知らせるには、 割り当てられたメモリ領域の内部へのポインタがあれば十分です。 領域の先頭へのポインタを保持しておく必要はありません。
Dによって作られた以外のスレッドのスタックや、 他のDLLに属するデータセグメントについては、 ガベージコレクタによって探索されません。
データ型の互換性
D の型 | C の型 | |
---|---|---|
32 bit | 64 bit | |
void | void | |
byte | signed char | |
ubyte | unsigned char | |
char | char (Dではcharはunsignedです) | |
wchar | wchar_t (sizeof(wchar_t) が 2 のとき) | |
dchar | wchar_t (sizeof(wchar_t) が 4 のとき) | |
short | short | |
ushort | unsigned short | |
int | int | |
uint | unsigned | |
long | long | long long |
ulong | unsigned long | unsigned long long |
float | float | |
double | double | |
real | long double | |
ifloat | float _Imaginary | |
idouble | double _Imaginary | |
ireal | long double _Imaginary | |
cfloat | float _Complex | |
cdouble | double _Complex | |
creal | long double _Complex | |
struct | struct | |
union | union | |
enum | enum | |
class | なし | |
type* | type * | |
なし | type & | |
type[dim] | type[dim] | |
type[dim]* | type(*)[dim] | |
type[] | なし | |
type[type] | なし | |
type function(parameters) | type(*)(parameters) | |
type delegate(parameters) | なし | |
size_t | size_t | |
ptrdiff_t | ptrdiff_t |
この対応関係はほとんどの32bit C コンパイラで成り立ちます。ただし Cの標準では型のサイズは一意に定められていないので、注意が必要です。
printf() の呼び出し
これは主に、printf のフォーマット指定がDのデータ型と合うかどうか、 が問題になります。printf はNUL終端の文字列を扱うようになっていますが、 Dでのcharの動的配列はそうなっていません。 しかし配列データの前に 4バイトで配列のサイズが置かれていますので、%.*s を使うと完璧に動作します:
void foo(char[] string) { printf("my string is: %.*s\n", string); }
例の printf のフォーマット文字列リテラルは \0 で終わっていません。これは、 より大きな構造体の初期化子の一部でない限り、 文字列リテラルの後ろには \0 が格納されるようになっているためです。
書式化出力を得るためのDでのベターな関数として、 std.stdio.writef() があります。
構造体と共用体
Dの構造体と共用体は、Cのそれとほぼ同じです。
Cのコードでは、実装特有の#pragmaやコンパイラのコマンドスイッチによって、 構造体の整列を制御します。これに対応するものとして、D には 明示的なアラインメント属性が用意されています。 C側でのアラインメントを調べて、 D側の構造体宣言に明示的にその値を設定して下さい。
Dはビットフィールドをサポートしません。必要ならば、 シフトとビットマスク演算によってエミュレートできます。 htod は、 ビットフィールドを右シフトとマスクを使ったinline関数に変換します。
C++とのインターフェイス
DはC++とのインターフェイスは提供しません。しかし、D は C とのインターフェイスを備えているので、C++ のコードが C のリンケージでコンパイルされていれば、直接リンクすることが出来ます。
DのクラスオブジェクトはC++のクラスオブジェクトとは互換性がありません。