在C程序中调用C++代码
现在在做Open64的编译器项目,里面有一堆80年代SGI写的C代码,现在要加入一些新的代码。我用C++写新的代码,然后试图在C程序中调用,结果出错,在网上搜索半天,终于找到解决办法,这里总结一下。
C与C++的区别主要是C不支持类(Class),不支持函数重载(function overloading),当然模板(template)也是不支持的。所以要在C程序中调用C++代码,最重要的原则就是给C程序提供一个C程序可用的接口。
规则一:接口中不能涉及类,以及引用
class A{....};
A foo(); // 这个函数是不能被C程序调用的,因为C程序不能识别A
void foo(A); // 这个也不行,道理同上
void foo(const int & n); // 同样不行,C不能识别引用
void foo(int n); // 这个就可以
规则二:接口中不能有重名函数
因为C中没有函数重载这个概念,同名的函数会被当做同一个函数,即使他们的参数不一样,所以在C程序调用的接口中,不能有重名的函数。如果你有两个函数 foo(int)和foo(double),那么就必须将这两个函数改名,以便使C程序能够区别他们,比如将两个函数分别命名为 foo_int(int)和foo_double(double).
规则三:接口需要用 extern "C"来声明
这个规则需要解释一下。C++允许函数重载,也就是说你可以同时声明 foo(int) 和foo(double),这两个函数是同名的,但实际在编译时编译器会给这两个函数生成不同的名字,以供区别,比如foo(int)可能对应成foo12345(12345是随机生成的),而foo(double)对应成foo34532。在不存在函数重载的情况下,编译器仍然会给这些函数重命名。而在C语言中,因为没有函数重载,所以也不存在函数重命名的情况。
比如说,我们写了一个C++程序,里面包含函数foo(int),我们将这个程序编译成 a.o。同时我们还写了一个C程序,里面调用了这个函数,我们将它编译为 b.o。在链接a.o和b.o的过程中,编译器会提示你找不到foo(int)这个函数的定义。因为在a.o里,foo很可能已经被重命名为fooxxxx,而b.o里的函数名却依旧是foo,编译器会试图在a.o里查找foo,当然就会失败。
extern "C"正是用来解决这种情况的。只要将 foo(int)的声明写成:
extern "C" foo(int)
编译器就不会对这个函数重命名,从而避免了上述错误的发生。
规则四:用 #ifdef __cplusplus 来避免编译失败
有时候我们会在.h文件中加入类定义。但是对C程序来说,类是不可识别的。因此如果我们在一个C文件里包含这样的.h文件,就会碰到编译不通过的问题。
比如一个A.h文件如下:
// A.h
class A{
public:
void foo();
//...
};
extern "C" invokeA();
B.c文件如下:
#include "A.h"
int main(){
invokeA();
//...
}
这样就会出现B.c文件无法编译的情况。要解决这种情况,只需要将A.h改成:
#ifdef __cplusplus
// A.h
class A{
public:
void foo();
//...
};
#endif
extern "C" invokeA();
这样当B.c包含A.h时,编译器处理到#ifdef __cplusplus时发现判断结果为假,就自动忽略了下面的内容,B.c就能编译通过了。
同时我们看到,这里有个invokeA()函数,它的作用就是调用A中的foo函数。也就是说,invokeA只是A::foo的一个包装。实际上,在C函数中要调用类的成员函数,也只能通过这样的包装。