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にはvolatile修飾子もありません。 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() で確保した領域にデータをコピーし、 そちらを代わりに渡す。
- その領域へのポインタをスタック上(引数か、自動変数)に残しておく。 ガベージコレクタはスタック上のオブジェクトは生きていると判定します。
- その領域へのポインタを静的データ領域に残しておく。 ガベージコレクタは静的データ領域のオブジェクトは生きていると判定します。
- そのポインタを、 std.gc.addRoot() か std.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 | |
c_long (core.stdc.config で定義) | long | long |
c_ulong (core.stdc.config で定義) | unsigned long | unsigned long |
long | long long | long (または long long) |
ulong | unsigned long long | unsigned long (または unsigned long long) |
float | float | |
double | double | |
real | long double | |
struct | struct | |
union | union | |
enum | enum | |
class | なし | |
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 |
の対応関係はほとんどの C コンパイラで成り立ちます。ただし Cの標準では型のサイズは一意に定められていないので、注意が必要です。
Dの配列をCの関数に渡す
Cでは、例え関数プロトタイプでは配列であるかのように宣言されていたとしても、 配列はポインタとして渡されます。Dでは、静的配列は参照渡しではなく、値渡しされます。 従って、 関数プロトタイプはCの期待するものになっているように調整する必要があります。
D の型 | C の型 |
---|---|
T* | T[] |
ref T[dim] | T[dim] |
例えば:
void foo(int a[3]) { ... } // C のコード
extern (C)
{
void foo(ref int[3] a); // D でのプロトタイプ宣言
}
printf() の呼び出し
これは主に、 printf のフォーマット指定 がDのデータ型と合うかどうか、 が問題になります。printf はNUL終端の文字列を扱うようになっていますが、 Dでのcharの動的配列はそうなっていません。 しかし配列データの前に 4バイトで配列のサイズが置かれていますので、%.*s を使うと動作します:
void foo(char[] string) {
printf("my string is: %.*s\n", string.length, string.ptr);
}
例の printf のフォーマット文字列リテラルは '\0' で終わっていません。これは、 より大きな構造体の初期化子の一部でない限り、 文字列リテラルの後ろには '\0' が格納されるようになっているためです。
書式化出力を得るためのDでのベターな関数として、 std.stdio.writef() があります。
構造体と共用体
Dの構造体と共用体は、Cのそれとほぼ同じです。
>Cのコードでは、実装特有の#pragmaやコンパイラのコマンドスイッチによって、 構造体の整列を制御します。これに対応するものとして、D には 明示的なアラインメント属性が用意されています。 C側でのアラインメントを調べて、 D側の構造体宣言に明示的にその値を設定して下さい。
Dはビットフィールドをサポートしません。必要ならば、 シフトとビットマスク演算によってエミュレートできます。 std.bitmanip.bitfields というライブラリ型もあります。 htod を使うと、 ビットフィールドはシフトとマスクを使ったインライン関数に変換されます。
既存の C ライブラリを使う
D からは C のコードを直接呼び出せるので、どんな C のライブラリ関数でも、 D とリンクすれば呼び出すことができます。 これをするには、しかし、D インターフェイス (.di) ファイルを、 そのライブラリの C の .h ヘッダファイルから変換して作る必要があります。
著名な C のライブラリに関しては、まずは対応する D のインターフェイスファイルがないか、 Deimos プロジェクト をチェックしましょう。 それがまだ存在せず、あなたが書き上げたならば、ぜひ、 Deimos プロジェクトに貢献してください。.
C のグローバル変数へのアクセス
C のグローバル変数は D から直接アクセスできます。C のグローバル変数は C の命名規約に従うので、D からは extern (C) ブロックで宣言される必要があります。 extern 記憶域クラスを指定することで、そのグローバル変数が D のコードではなく C のコードの側で割り当てられていることを処理系に伝えます。 C のグローバル変数は、デフォルトではスレッドローカルではなく、グローバルな記憶域に配置されます。 グローバル記憶域を D から参照するには、 __gshared 記憶域クラスを使います。
extern (C) extern __gshared int x;