您的位置:首页 > 编程语言 > C语言/C++

C++ 中extern"C"

2016-04-25 15:34 239 查看
比如说你用C 开发了一个DLL 库,为了能够让C ++语言也能够调用你的DLL 输出(Export) 的函数,你需要用extern "C" 来强制编译器不要修改你的函数名。

通常,在C 语言的头文件中经常可以看到类似下面这种形式的代码:

#ifdef __cplusplus
extern "C" {
#endif

/**** some declaration or so *****/

#ifdef __cplusplus
}
#endif


那么,这种写法什么用呢?实际上,这是为了让CPP 能够与C 接口而采用的一种语法形式。之所以采用这种方式,是因为两种语言之间的一些差异所导致的。由于CPP 支持多态性,也就是具有相同函数名的函数可以完成不同的功能,CPP 通常是通过参数区分具体调用的是哪一个函数。在编译的时候,CPP 编译器会将参数类型和函数名连接在一起,于是在程序编译成为目标文件以后,CPP 编译器可以直接根据目标文件中的符号名将多个目标文件连接成一个目标文件或者可执行文件。但是在C 语言中,由于完全没有多态性的概念,C 编译器在编译时除了会在函数名前面添加一个下划线之外,什么也不会做(至少很多编译器都是这样干的)。由于这种的原因,当采用CPP 与C 混合编程的时候,就可能会出问题。假设在某一个头文件中定义了这样一个函数:

int foo(int a, int b);

而这个函数的实现位于一个.c 文件中,同时,在.cpp 文件中调用了这个函数。那么,当CPP 编译器编译这个函数的时候,就有可能会把这个函数名改成_fooii ,这里的ii 表示函数的第一参数和第二参数都是整型。而C 编译器却有可能将这个函数名编译成_foo 。也就是说,在CPP 编译器得到的目标文件中,foo() 函数是由_fooii 符号来引用的,而在C 编译器生成的目标文件中,foo() 函数是由_foo 指代的。但连接器工作的时候,它可不管上层采用的是什么语言,它只认目标文件中的符号。于是,连接器将会发现在.cpp 中调用了foo() 函数,但是在其它的目标文件中却找不到_fooii 这个符号,于是提示连接过程出错。extern
"C" {} 这种语法形式就是用来解决这个问题的。

本文将以示例对这个问题进行说明。
首先假设有下面这样三个文件:

C代码:

/* file: test_extern_c.h */

#ifndef __TEST_EXTERN_C_H__
#define __TEST_EXTERN_C_H__

#ifdef __cplusplus
extern "C" {
#endif

/*
* this is a test function, which calculate
* the multiply of a and b.
*/

extern int ThisIsTest(int a, int b);

#ifdef __cplusplus
}
#endif

#endif


在这个头文件中只定义了一个函数,ThisIsTest() 。这个函数被定义为一个外部函数,可以被包括到其它程序文件中。假设ThisIsTest() 函数的实现位于test_extern_c.c 文件中:

C代码:

/* test_extern_c.c */

#include "test_extern_c.h"

int ThisIsTest(int a, int b)
{
return (a + b);
}


可以看到,ThisIsTest() 函数的实现非常简单,就是将两个参数的相加结果返回而已。现在,假设要从CPP 中调用ThisIsTest() 函数:

Cpp代码:

/* main.cpp */

#include "test_extern_c.h"

#include <stdio.h>
#include <stdlib.h>

class FOO {

public:

int bar(int a, int b)

{

printf("result=%i\n", ThisIsTest(a, b));

}

};

int main(int argc, char **argv)
{

int a = atoi(argv[1]);

int b = atoi(argv[2]);

FOO *foo = new FOO();

foo->bar(a, b);

return(0);
}
在这个头文件中只定义了一个函数,ThisIsTest() 。这个函数被定义为一个外部函数,可以被包括到其它程序文件中。假设ThisIsTest() 函数的实现位于test_extern_c.c 文件中:

在这个CPP 源文件中,定义了一个简单的类FOO ,在其成员函数bar() 中调用了ThisIsTest() 函数。下面看一下如果采用gcc 编译test_extern_c.c ,而采用g++ 编译main.cpp 并与test_extern_c.o 连接会发生什么情况:

[cyc@cyc src]$ gcc -c test_extern_c.c

[cyc@cyc src]$ g++ main.cpp test_extern_c.o

[cyc@cyc src]$ ./a.out 4 5

result=9

可以看到,程序没有任何异常,完全按照预期的方式工作。那么,如果将test_extern_c.h 中的extern "C" {}所在的那几行注释掉会怎样呢?注释后的test_extern_c.h 文件内容如下:
/* test_extern_c.h */

#ifndef __TEST_EXTERN_C_H__
#define __TEST_EXTERN_C_H__

//#ifdef   __cplusplus
//extern "C" {
//#endif

/*
/* this is a test function, which calculate
* the multiply of a and b.
*/

extern int ThisIsTest(int a, int b);

//#ifdef   __cplusplus
// }
//#endif

#endif


之外,其它文件不做任何的改变,仍然采用同样的方式编译test_extern_c.c 和main.cpp 文件:

[cyc@cyc src]$ gcc -c test_extern_c.c

[cyc@cyc src]$ g++ main.cpp test_extern_c.o

/tmp/cca4EtJJ.o(.gnu.linkonce.t._ZN3FOO3barEii+0x10): In function `FOO::bar(int, int)':

: undefined reference
to `ThisIsTest(int, int)'

collect2: ld returned 1 exit status

在编译main.cpp 的时候就会出错,连接器ld 提示找不到对函数ThisIsTest() 的引用。


注意:

用g++编译cpp程序时,编译器会定义宏 __cplusplus ,可根据__cplusplus是否定义决定是否需要extern "C"


总结:

上面讲的都是理论,和一些程序,那么实际使用时有以下集中情况:

1. 现在要写一个c语言的模块,供以后使用(以后的项目可能是c的也可能是c++的),源文件事先编译好,编译成.so或.o都无所谓。头文件中声明函数时要用条件编译包含起来,如下:
C代码:

#ifdef __cpluscplus
extern "C" {
#endif

//some code

#ifdef __cplusplus
}
#endif


也就是把所有函数声明放在some code的位置。

2. 如果这个模块已经存在了,可能是公司里的前辈写的,反正就是已经存在了,模块的.h文件中没有extern "C"关键字,这个模块又不希望被改动的情况下,可以这样,在你的c++文件中,包含该模块的头文件时加上extern "C", 如下:

C++代码:
extern "C" {
#include "test_extern_c.h"
}


3.上面例子中,如果仅仅使用模块中的1个函数,而不需要include整个模块时,可以不include头文件,而单独声明该函数,像这样:

C++代码:

extern "C" {
int ThisIsTest(int, int);
}
然后就可使用模块中的这一ThisIsTest函数了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: