- 浏览: 257515 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
ab0809:
写的很好,谢谢
c++中的 extern "C" -
eieihihi:
说得太好了,我就喜欢这种很透彻的说法
c++中的 extern "C" -
infollllll:
package com;import java.io.*;im ...
websocket通讯协议(10版本)简介 -
rocksent:
websocket draft10握手成功了,可是传数据一直失 ...
websocket通讯协议(10版本)简介 -
guanbeilang:
通过你的代码,学会了怎么从客户端接收消息,并改造成了nodej ...
websocket通讯协议(10版本)简介
比如说你用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" {} 这种语法形式就是用来解决这个问题的。本文将以示例对这个问题进行说明。
首先假设有下面这样三个文件:
/* 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 文件中:
/* test_extern_c.c */ #include "test_extern_c.h" int ThisIsTest(int a, int b) { return (a + b); }
可以看到,ThisIsTest() 函数的实现非常简单,就是将两个参数的相加结果返回而已。现在,假设要从CPP 中调用ThisIsTest() 函数:
/* 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); }
在这个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() 的引用。
为了更清楚地说明问题的原因,我们采用下面的方式先把目标文件编译出来,然后看目标文件中到底都有些什么符号:
[cyc@cyc src]$ gcc -c test_extern_c.c
[cyc@cyc src]$ objdump -t test_extern_c.o
test_extern_c.o: file format elf32-i386
SYMBOL TABLE:
00000000 l df *ABS* 00000000 test_extern_c.c
00000000 l d .text 00000000
00000000 l d .data 00000000
00000000 l d .bss 00000000
00000000 l d .comment 00000000
00000000 g F .text 0000000b ThisIsTest
[cyc@cyc src]$ g++ -c main.cpp
[cyc@cyc src]$ objdump -t main.o
main.o: file format elf32-i386
MYMBOL TABLE:
00000000 l df *ABS* 00000000 main.cpp
00000000 l d .text 00000000
00000000 l d .data 00000000
00000000 l d .bss 00000000
00000000 l d .rodata 00000000
00000000 l d .gnu.linkonce.t._ZN3FOO3barEii 00000000
00000000 l d .eh_frame 00000000
00000000 l d .comment 00000000
00000000 g F .text 00000081 main
00000000 *UND* 00000000 atoi
00000000 *UND* 00000000 _Znwj
00000000 *UND* 00000000 _ZdlPv
00000000 w F .gnu.linkonce.t._ZN3FOO3barEii 00000027 _ZN3FOO3barEii
00000000 *UND* 00000000 _Z10ThisIsTestii
00000000 *UND* 00000000 printf
00000000 *UND* 00000000 __gxx_personality_v0
可以看到,采用gcc 编译了test_extern_c.c 之后,在其目标文件test_extern_c.o 中的有一个ThisIsTest 符号,这个符号就是源文件中定义的ThisIsTest() 函数了。而在采用g++ 编译了main.cpp 之后,在其目标文件main.o 中有一个_Z10ThisIsTestii 符号,这个就是经过g++ 编译器“粉碎”过后的函数名。其最后的两个字符i 就表示第一参数和第二参数都是整型。而为什么要加一个前缀_Z10 我并不清楚,但这里并不影响我们的讨论,因此不去管它。显然,这就是原因的所在,其原理在本文开头已作了说明。
那么,为什么采用了extern "C" {} 形式就不会有这个问题呢,我们就来看一下当test_extern_c.h 采用extern "C" {} 的形式时编译出来的目标文件中又有哪些符号:
[cyc@cyc src]$ gcc -c test_extern_c.c
[cyc@cyc src]$ objdump -t test_extern_c.o
test_extern_c.o: file format elf32-i386
SYMBOL TABLE:
00000000 l df *ABS* 00000000 test_extern_c.c
00000000 l d .text 00000000
00000000 l d .data 00000000
00000000 l d .bss 00000000
00000000 l d .comment 00000000
00000000 g F .text 0000000b ThisIsTest
[cyc@cyc src]$ g++ -c main.cpp
[cyc@cyc src]$ objdump -t main.o
main.o: file format elf32-i386
SYMBOL TABLE:
00000000 l df *ABS* 00000000 main.cpp
00000000 l d .text 00000000
00000000 l d .data 00000000
00000000 l d .bss 00000000
00000000 l d .rodata 00000000
00000000 l d .gnu.linkonce.t._ZN3FOO3barEii 00000000
00000000 l d .eh_frame 00000000
00000000 l d .comment 00000000
00000000 g F .text 00000081 main
00000000 *UND* 00000000 atoi
00000000 *UND* 00000000 _Znwj
00000000 *UND* 00000000 _ZdlPv
00000000 w F .gnu.linkonce.t._ZN3FOO3barEii 00000027 _ZN3FOO3barEii
00000000 *UND* 00000000 ThisIsTest
00000000 *UND* 00000000 printf
00000000 *UND* 00000000 __gxx_personality_v0
注意到这里和前面有什么不同没有,可以看到,在两个目标文件中,都有一个符号ThisIsTest ,这个符号引用的就是ThisIsTest() 函数了。显然,此时在两个目标文件中都存在同样的ThisIsTest 符号,因此认为它们引用的实际上同一个函数,于是就将两个目标文件连接在一起,凡是出现程序代码段中有ThisIsTest 符号的地方都用ThisIsTest() 函数的实际地址代替。另外,还可以看到,仅仅被extern "C" {} 包围起来的函数采用这样的目标符号形式,对于main.cpp 中的FOO 类的成员函数,在两种编译方式后的符号名都是经过“粉碎”了的。
因此,综合上面的分析,我们可以得出如下结论:采用extern "C" {} 这种形式的声明,可以使得CPP 与C 之间的接口具有互通性,不会由于语言内部的机制导致连接目标文件的时候出现错误。需要说明的是,上面只是根据我的试验结果而得出的结论。由于对于CPP 用得不是很多,了解得也很少,因此对其内部处理机制并不是很清楚,如果需要深入了解这个问题的细节请参考相关资料。
注意:
用g++编译cpp程序时,编译器会定义宏 __cplusplus ,可根据__cplusplus是否定义决定是否需要extern "C"。
总结:
上面讲的都是理论,和一些程序,那么实际使用时有以下集中情况:
1. 现在要写一个c语言的模块,供以后使用(以后的项目可能是c的也可能是c++的),源文件事先编译好,编译成.so或.o都无所谓。头文件中声明函数时要用条件编译包含起来,如下:
#ifdef __cpluscplus extern "C" { #endif //some code #ifdef __cplusplus } #endif
也就是把所有函数声明放在some code的位置。
2. 如果这个模块已经存在了,可能是公司里的前辈写的,反正就是已经存在了,模块的.h文件中没有extern "C"关键字,这个模块又不希望被改动的情况下,可以这样,在你的c++文件中,包含该模块的头文件时加上extern "C", 如下:
extern "C" { #include "test_extern_c.h" }
3.上面例子中,如果仅仅使用模块中的1个函数,而不需要include整个模块时,可以不include头文件,而单独声明该函数,像这样:
extern "C" { int ThisIsTest(int, int); }
然后就可一使用模块中的这个ThisIsTest函数了。
发表评论
-
c++ vector list map在遍历中删除元素
2013-05-07 14:47 4997c++ STL 中的vector, list, map ... -
c++ 构造函数和析构函数调用顺序
2013-04-25 16:54 1139#include <iostream> ... -
epoll高效的原因
2013-03-18 11:10 991epoll高效的原因: 使用内核中断实现,中断后调用 ... -
UNIX domain socket传递文件描述符
2012-06-19 18:41 5361c版本(UNIX高级编程中的例子): // sendm ... -
unix domain socket传递描述符
2012-04-25 15:18 930用unix domain socket在进程间传递描述符。 ... -
C++中的虚函数和虚函数表
2012-04-02 18:17 923请看这一篇文章: http://hi.baidu.com/w ... -
c++中函数参数返回值用string好还是const char *好
2012-02-01 07:38 9762有这样一个函数test需要两个字符串作为参数,那么test的原 ... -
google的gtest测试框架
2011-12-21 18:17 820http://www.cnblogs.com/coderzh/ ... -
windows下TCP通讯
2011-12-20 18:17 1009客户端 #include <iostream> ... -
静态库与共享库
2011-11-01 19:00 1682在用c c++编程时经常用到库,库有静态的,和共享 ... -
c++线程池
2011-09-26 09:17 774 -
websocket通讯协议(10版本)简介
2011-09-22 18:27 14993前言: 工作中用到 ... -
epoll服务器
2011-09-21 22:33 765epoll服务器。 ... -
c,c++ little knowledge
2011-08-31 15:00 6331. 把一个string型字符串全部转成小写。 ... -
c++中的拷贝构造函数
2011-07-14 16:44 1067int a = 2; int b = a; ... -
简单的tcp通讯
2011-07-12 14:55 849简单的tcp通讯例子。 server端 /* ... -
c语言操作mysql数据库(Ubuntu11.04)
2011-06-11 17:43 2883系统:Ubuntu11.04 1.刚安装 ... -
全局宏定义
2010-12-22 22:59 5439首先我们要理解定义和声明的区别,举个常见的例子,比 ...
相关推荐
C++中extern “C”含义深层探索
看看就知道:学习总结:C++中extern “C”含义深层探索.doc
C++语言extern C浅析
主要介绍了C/C++中extern "C" 的作用,是在进行C/C++程序设计中非常常见的用法,需要的朋友可以参考下
主要介绍了C++中的extern “C”用法详解,简单来说,extern “C”是C++声明或定义C语言符号的方法,是为了与C兼容,需要的朋友可以参考下
C/C++ 中extern关键字详解 在C/C++编程过程中,经常会进行变量和函数的声明和定义,各个模块间共用同一个全局变量时,此时extern就派上用场了。 定义 extern可以置于变量或者函数前,以标示变量或者函数的定义在别的...
本文组要讲述了extern在C++中的作用,与在C中的作用相区分,帮助大家更好的理解。
方法一、全局函数和变量在devVar.c文件中实现,在extern.cpp文件中使用extern关键字声明在devVar.c文件中定义的函数和变量。 devVar.c文件的代码如下所示: #include int i = 1; void func() { ...
我们经常会在C/C++程序中见到extern “C”,这是一个很重要的概念。本文就来以实例形式讲述C/C++中extern “C”的作用。分享给大家供大家参考之用。具体分析如下: 作用:实现C和C++混合编程。 原理:C和C++编译器...
主要介绍了C/C++中extern关键字详解 的相关资料,需要的朋友可以参考下
学习过C++的人都知道,extern关键字可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。这里起到的是声明作用范围的用处。另外,extern还可以与...
主要介绍了C++中extern "C"的用法,是深入理解C++所应该掌握的概念,需要的朋友可以参考下
c++/cextern用法,不熟的朋友可以看一下
本文主要介绍了C+中extern的用法,希望对你的学习有所帮助。
本篇文章是对C/C++中的static与extern关键字的使用进行了详细的分析介绍,需要的朋友参考下
C++语言的创建初衷是“a better C”,但是这并不意味着C++中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种欲与C兼容的语言, C++保留了一部分过程式语言的特点(被世人称为“不彻底地...
使用extern "C"改善显式调用dll的例子
在C++中extern ”C“ 的含义是使用C语言的编译和链接规则编译和链接下面的代码。 所以C++中的函数,声明前如果加上extern ”C“, 那么编译器就不会对它做命名修饰,编译出来的代码就可以在C程序中直接调用。 当然...