DLang 與 C 語言互動

2023-06-24 21:00:49

DLang 與 C 語言互動

很難受的是,這部分的檔案太少了,根本沒有 教學向 的文章。所以我寫了此文以做分享。

本文原址連結(防止機器搬運):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!


接下來對於部分做出解釋。

  1. 函數的宣告:在 hello.d 中有一句 extern (C) void hello();,這就是函數的宣告部分,由於 dlang 與 C++ 一樣在處理常式名的時候會做一些變化,然而 C 中的函數名卻是不變的,所以需要顯式宣告其函數名的處理方式:extern (C),也就是按照 C 的處理方式處理,這樣才能呼叫到 chello.o 中的 hello 方法。

  2. 編譯的命令:唯一需要注意的是需要把 .o 檔案顯示的放入編譯命令中。

  3. 標頭檔案:這是困擾我最久的一個點,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 中才存著資料。所以如此。

那麼考慮不可變長陣列。好像還是隻能使用上面那種宣告和呼叫方法……

這就是在傳遞引數的時候最需要注意的一個點。其他的引數型別轉化可以參考: