很難受的是,這部分的檔案太少了,根本沒有 教學向 的文章。所以我寫了此文以做分享。
本文原址連結(防止機器搬運):https://www.cnblogs.com/jeefy/p/17499441.html
閱讀提示:請保證如下條件:
會基本C語言使用,以及其編譯命令。
會基本D語言使用,以及其編譯命令。
會使用 Makefile
之類的東西(不會也無所謂),不會 dub
// chello.c
#include <stdio.h>
void hello(void) {
puts("Hello World!");
}
// hello.d
extern (C) void hello();
void main() {
hello();
}
其實上面的兩個程式的意義非常明顯,就是最基本的
Hello World!
輸出罷了。
我們有了上面兩個檔案後可以通過如下命令編譯:
gcc -c chello.c
dmd hello.d chello.o
最終會得到一個 main
可執行檔案(也可能是 main.exe
,看系統)。執行它,你就得到了 Hello World!
接下來對於部分做出解釋。
函數的宣告:在 hello.d
中有一句 extern (C) void hello();
,這就是函數的宣告部分,由於 dlang 與 C++ 一樣在處理常式名的時候會做一些變化,然而 C 中的函數名卻是不變的,所以需要顯式宣告其函數名的處理方式:extern (C)
,也就是按照 C 的處理方式處理,這樣才能呼叫到 chello.o
中的 hello
方法。
編譯的命令:唯一需要注意的是需要把 .o
檔案顯示的放入編譯命令中。
標頭檔案:這是困擾我最久的一個點,dlang 如何使用 C 的標頭檔案?後來才發現了一個誤區,dlang 不存在標頭檔案的說法,也就是說 dlang 無法 直接 使用 .h
檔案。但是我們又需要宣告函數怎麼辦?在官方檔案中有這樣一個命令:
gcc -E -P program.h > program.lst
這個命令的作用在於列出所有宣告的東西(包括函數與結構體的宣告)。在經過一定的修改後,就可以變為 .d
檔案(做類似於標頭檔案的作用)。
接下來我們嘗試一點更加高階的東西。
我使用的例子是我自己在寫的一個小東西。參見:Jeefy / jimg · GitLab
假如我需要使用一個簡單的 SDL
程式以顯示一個色塊。我可以很輕易的寫出如下程式碼:
// cshow.c
#include <stdio.h>
#include <SDL2/SDL.h>
// 省略了部分宏定義....避免冗長
int showColor(int R, int G, int B) {
SDL_PREWORK(50, 50);
while (!done) {
SDL_MYEQUIT(60);
if (SDL_SetRenderDrawColor(ren, R, G, B, 255) < 0) {
fprintf(stderr, "Error Set render draw color: %s\n", SDL_GetError());
return -1;
}
SDL_RenderFillRect(ren, NULL);
SDL_RenderPresent(ren);
}
SDL_CLEANUP;
return 0;
}
對於 dlang 中的呼叫也很明瞭:
// color.d
// show the color by SDL (written in C)
extern (C) int showColor(int, int, int);
只是問題出在編譯的部分。
如果我們按照一下步驟編譯:
gcc -c cshow.c `pkg-config sdl2 --cflags --libs`
dmd color.d cshow.o
我們最終會發現出現 undefined reference to ...
的錯誤。
在官方檔案中並沒有提及使用系統連結庫的問題。但是 隱晦 的給出瞭解決方法。
我們直接找到 libSDL2.so
所在的位置。在我的系統中是 /usr/lib/x86_64-linux-gnu/libSDL2.so
於是編譯命令新增一個部分,變為:
gcc -c cshow.c `pkg-config sdl2 --cflags --libs`
dmd color.d cshow.o /usr/lib/x86_64-linux-gnu/libSDL2.so
也就是直接把連結庫也帶上……屬實給我整無語了。
啊,於是,我們不能真的吧所有庫的位置全部找到,然後在編譯的時候一個一個複製上去吧。所以就需要類似與 Make
或者 CMake, meson
之類的構建工具輔助我們。這部分不過多展開。
有些時候,我們想要在 C 中使用 dlang 分配的記憶體。如:
// exmaple.c
void fillMem(int *dst, int size, int val) {
for (int i = 0; i < size; ++i)
dst[i] = val;
}
自然的,我們可以想到:
// example.d
// 錯誤範例!!!!!!!!!!!!!!!!!!!!
import std.stdio;
extern (C) void fillMem(int[] dst, int size, int val);
void main() {
int[] arr = new int[](3);
fillMem(arr, 3, 1);
writeln(arr[2]);
}
可以編譯通過,然後執行……輸出 0
。
講道理應該是輸出 1
才對。
那麼很明顯,型別出了問題,我們不應該如此操作。
於是參考官方檔案。正確的姿勢如下:
import std.stdio;
extern (C) void fillMem(int* dst, int size, int val);
void main() {
int[] arr = new int[](3);
fillMem(arr.ptr, 3, 1);
writeln(arr[0]);
writeln(arr[1]);
writeln(arr[2]);
writeln(arr.length);
}
為什麼如此?在 dlang 中,可變長陣列其實有兩個變數,可以理解為:
struct {
int length;
int * ptr;
}
當然,型別肯定不是這樣的……
也就是說 arr.ptr
中才存著資料。所以如此。
那麼考慮不可變長陣列。好像還是隻能使用上面那種宣告和呼叫方法……
這就是在傳遞引數的時候最需要注意的一個點。其他的引數型別轉化可以參考: