C 调用 C++ 代码

210 阅读3分钟

C++ 因为兼容 C (ANSI),所以基本上直接调用头文件到时候一起编译就完了,但是 C 调用 C++ 的情况要不是从公司代码看到,我还真不知道有(话说为什么不把 main 改到 C++)。

总之,C 调用 C++ 的代码(主要是函数),通过的是 ABI(Appliction Binary Interface),也就是从二进制文件层级提供接口出来。不光是 C++,其他语言,比如 Rust,Zig 都可以通过二进制的接口出来给其他语言调用。通常能编译到二进制可执行文件的编程语言都有这个功能,例如 C 自身也可以通过 ABI 的方式给 Node.js 或者 Python 调用。

示例一:利用 vector 排序的简单示例

以下是一个简单的示例,让 C 能够”享用到” C++ 的 STL 库:

    // mycode.cpp
    #include <vector>
    #include <algorithm>

    extern "C" {
        void performSort(int* array, int size) {
            // 通过构造函数将数组转换到 vector
            std::vector<int> vec(array, array + size);
            // 使用 vector 的排序接口
            std::sort(vec.begin(), vec.end());
            // 将排序后的数据复制回原始数组
            for (int i = 0; i < size; i++) {
                array[i] = vec[i];
            }
        }
    }
  • 上面是一个 C++ 代码,通过 extern "C" 对外导出 C 可以调用的函数,如果不这样做的话,C++ 编译器一般会把函数名称优化修改,导致 C 编译器无法找到函数
  • 当然了,我知道 C 语言有 qsort 函数,这里只是举个排序的例子展示用法
    // main.c
    #include <stdio.h>

    extern void performSort(int* array, int size); // from mycode.cpp

    int main() {
        int arr[] = {3, 1, 4, 1, 5, 9, 2, 6};
        int size = sizeof(arr) / sizeof(arr[0]);

        performSort(arr, size);

        for (int i = 0; i < size; i++) {
            printf("%d ", arr[i]);
        }

        return 0;
    }
  • 上面是一个 C 代码,通过 extern 的方式声明一个函数。但是这个函数既不是其他头文件引入的,也不是本地实现的,所以通过一些来工具来追代码,大概率就追到这里为止了,能够在注释中写明函数的来源最好不过了。
g++ -c mycode.cpp -o mycode.o
gcc -c main.c -o main.o
g++ mycode.o main.o -o myprogram
  • 通过编译器链接起来就行了

image.png

需要注意的是,虽然能够使用到 STL 的模板类,但是本身数据不是兼容的,因为 C 不支持对象,传进去的时候需要转换一次,传出来的时候需要再转换一次,效率肯定不高。想在 C 里使用 C++ 的对象也无可厚非,这个请看示例二。

示例二:如何处理对象的简单示例

在 C 里面保存一个 C++ 对象是不可能的,但是可以通过结构体包装和结构体指针的方式,并同时使用 create 和 destroy 来包装对象的构造和析构。如下是使用 C++ 的 queue 模板库的方法:

// queue.cpp
#include <queue>

extern "C" {
    // 定义队列结构体
    typedef struct Queue Queue;

    // 创建队列
    Queue* createQueue();

    // 销毁队列
    void destroyQueue(Queue* queue);

    // 入队
    void enqueue(Queue* queue, int element);

    // 出队
    int dequeue(Queue* queue);
}

struct Queue {               // 💖
    std::queue<int> q;
};

Queue* createQueue() {
    return new Queue;
}

void destroyQueue(Queue* queue) {
    delete queue;
}

void enqueue(Queue* queue, int element) {
    queue->q.push(element);
}

int dequeue(Queue* queue) {
    if (queue->q.empty()) {
        return -1; // 队列为空时返回-1,当然这里不是很严谨
    }
    int element = queue->q.front();
    queue->q.pop();
    return element;
}
  • 比较重要的点是,我们用 Queue 这个结构体包裹了 queue<int> 的数据,然后将这个结构体通过 ABI 导出,在所有操作相关的函数中传入这个结构体,然后通过结构体内的成员,也就是一个 queue<int> 类型的对象去操作数据
// main.c
#include <stdio.h>

typedef struct Queue Queue;

extern Queue* createQueue();
extern void destroyQueue(Queue* queue);
extern void enqueue(Queue* queue, int element);
extern int dequeue(Queue* queue);

int main() {
    Queue* queue = createQueue();

    // 入队
    enqueue(queue, 5);
    enqueue(queue, 3);
    enqueue(queue, 8);

    // 出队并打印元素
    int element;
    while ((element = dequeue(queue)) != -1) {
        printf("%d\n", element);
    }

    // 销毁队列
    destroyQueue(queue);

    return 0;
}
  • 单从 C 代码上来看和调用 C 的函数差不多

image.png

不过,如果有不同数据类型的队列,都得包装一下,不可能像 C++ 模板那样灵活。

示例三:使用头文件的例子

上面的例子中没有使用头文件,所以必须用 extern 来声明外部函数。因为 C 和 C++ 有不兼容的地方,所以如果头文件中 #include 了 C++ 的头文件或者使用了 C++ 的特性就会导致 C 代码无法编译。

不过,C++ 中有一个宏定义 __cplusplus,在 C++ 中,因为这个宏变量被定义,所以能够区分是 C 环境还是 C++ 环境,可以通过它进行条件编译。

通过这个办法,修改例子二中的代码,将函数定义放入头文件共享。

// queue.h
#ifdef __cplusplus
#include <queue>
struct Queue {
    std::queue<int> q;
};
extern "C" {
#endif
        // 定义队列结构体
        typedef struct Queue Queue;

        // 创建队列
        Queue* createQueue();

        // 销毁队列
        void destroyQueue(Queue* queue);

        // 入队
        void enqueue(Queue* queue, int element);

        // 出队
        int dequeue(Queue* queue);
#ifdef __cplusplus
}
#endif

此时 C++ 的代码为:

// queue.cpp
#include "queue.h"

Queue* createQueue() {
    return new Queue;
}

void destroyQueue(Queue* queue) {
    delete queue;
}

void enqueue(Queue* queue, int element) {
    queue->q.push(element);
}

int dequeue(Queue* queue) {
    if (queue->q.empty()) {
        return -1; // 队列为空时返回-1
    }
    int element = queue->q.front();
    queue->q.pop();
    return element;
}

此时 C 的代码为:

#include "queue.h"
#include "stdio.h"

int main() {
    Queue* queue = createQueue();

    // 入队
    enqueue(queue, 5);
    enqueue(queue, 3);
    enqueue(queue, 8);

    // 出队并打印元素
    int element;
    while ((element = dequeue(queue)) != -1) {
        printf("%d\n", element);
    }

    // 销毁队列
    destroyQueue(queue);

    return 0;
}

至于编译过程还是一样的,就是省去了 extern 的语句。