extern ”C"的使用

原文地址: http://blog.csdn.net/u013074465

本文分析extern “C”的使用方法;介绍C++和C代码相互调用的方式。

链接指示extern "C"

          extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。被extern "C"限定的函数或变量是extern类型的。

          externC/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,语句:

[cpp]  view plain  copy
  1. extern int a;  //声明,未分配空间  

仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。

但是注意,如果将上述语句赋值的话,那么它就是定义了:

[cpp]  view plain  copy
  1. extern int a = 2;    //定义。只有定义时才可以初始化;若声明有初始化,则它就是定义了(即使有extern)  

         通常,在模块的头文件对本模块供给其他模块使用的函数和全局变量以关键字extern声明。例如,模块B欲引用模块A中的全局变量和函数时,只需要包含A的头文件即可。这样,模块B中调用A模块的函数时,在编译阶段,B找不到该函数定义但是不会报错,会在连接阶段中从模块A编译生成的目标代码中找到该函数。

        例如,在下面的“C调用C++函数”一节中,在cpptest.h头文件中声明了一个extern int test变量,test变量的定义下载cpptest.cpp文件中,那么在c_use_c++.c中就可以通过包含cpptest.h头文件来引用test变量。虽然.cpp文件中将test赋值为2但是在main函数中将test再次赋值为1,因此,最后在main函数中test的两次输出均为1。

        与extern关键字对应的是static,它修饰的函数和变量只能在本模块中使用。因此,如果函数或变量只允许在本模块中使用,那么不能用extern “C"修饰。


注意几点

(1)链接指示extern "C"中的“C"代表的并不是C语言,而是用extern ”C“来声明非C++函数;实际上Fortran和汇编语言也常常使用,因为它们也正好符合C实现的约定。支持什么语言由编译器而定,C++保证支持的唯一语言是C

(2)extern ”C“不能出现在类定义或函数定义的内部,必须出现在函数的第一次声明上。

(3)链接指示的两种形式:单个的和复合的。如:

[cpp]  view plain  copy
  1. //单个的形式  
  2. extern "C" void test(int a);  
  3.   
  4. //复合形式  
  5. extern "C" {  
  6. void test(int a);  
  7. int fun(int a, double b);  
  8. }  

 
(4)链接指示和头文件 

可以将多重声明形式应用于整个头文件,如C++的cstring头文件可以像这样:

[cpp]  view plain  copy
  1. extern "C" {  
  2.       #include <string.h>  
  3. }  
如果写成上面的形式,那么string.h头文件中的所有普通函数都假定为用链接指示的语言编写的。 连接指示可以嵌套 ,因此,如果头文件中包含了带链接指示的函数,该好函数的链接不受影响。

(5)通过连接指示可以让C++使用C的函数,也可以让C使用C++的函数。这点之后的”C和C++的相互调用“会介绍。

(6)当C++调用其他语言的函数时,C++必须声明用其他语言编写的函数的名字。编译器按照处理普通C++函数的方式检查对外部语言函数的调用,但是,编译器一般必须产生不同的代码来调用其他语言编写的函数。C++使用链接指示指出任意非C++函数所使用的语言。

(7)为什么C++中调用被C编译器编译后的函数,要加extern ”C"?

         C不支持重载函数,因此C++中的重载函数中仅能只有一个函数声明为extern "C",否则会出错。extern "C"解决名字匹配问题。例如对于void fun(int a)这样的函数,为了支持重载机制,C++将函数的名字编译为类似_fun_int或_xxx_funDxxx这样的形式,而C语言则一般将函数编译为类似_fun这样的形式。那么,对于用C编译器编译成的库,用C++直接链接势必会出现不能识别符号的问题。extern "C" 的作用就是让编译器知道要以C语言的方式编译和连接封装函数。


C和C++的相互调用

C++调用C函数

首先定义一个C的文件及相应的头文件,将其编译生成C的动态库,然后让C++来调用该C的库。

[cpp]  view plain  copy
  1. //ctest.c  
  2. #include "ctest.h"  
  3.   
  4. void test(int a, double b) {  
  5.   printf("a: %d\n", a);  
  6.   printf("b: %lf\n", b);  
  7. }  
[cpp]  view plain  copy
  1. //ctest.h  
  2. #ifndef CTEST_H  
  3. #define CTEST_H  
  4.   
  5. #include <stdio.h>  
  6.   
  7. void test(intdouble);  
  8.   
  9. #endif  
编译生成C动态库:gcc --shared -o libtest_c++_use_c.so ctest.c

[cpp]  view plain  copy
  1. //c++_use_c.cpp  
  2. extern "C" void test(intdouble);  
  3. //extern void test(int, double);  
  4.   
  5. int main() {  
  6.   test(2, 1.4);  
  7.   return 0;  
  8. }  
使用上边生成的动态库libtest_c++_use_c.so来编译该C++文件:g++ -o c++_use_c c++_use_c.cpp -L /home/yang -ltest_c++_use_c

执行:./c++_use_c

执行的结果如下图所示。


分析上述程序:

         在上面的程序中,c++_use_c.cpp是一个C++程序,它调用了C函数test。按照之前的分析,在c++_use_c.cpp中要使用extern “C”来声明非C++函数,调用C语言编写的函数。c++_use_c.cpp如果没有extern "C"链接指示,而仅仅仅换成注释中所示的普通的函数声明,编译该文件会出错(即使注释处有extern关键字也会出错)如下图所示。因为g++编译器按照C++的方式来在main函数中调用test函数,这样将没办法找到C++方式的test函数,因为test函数时gcc编译成的C函数的方式。



C调用C++函数

         简单思考下:C语言先于C++出现,C++是在C的基础上发展来的。因此,可以想象,C并没有调用C++的机制,因此要实现C调用C++函数通过对要调用的C++函数进行处理

[cpp]  view plain  copy
  1. //cpptest.cpp  
  2. #include "cpptest.h"  
  3. #include <stdio.h>  
  4. #include <iostream>  
  5. using namespace std;  
  6. /* 
  7. #ifdef __cplusplus 
  8. extern "C" { 
  9. #endif 
  10. */  
  11. int test = 2;  
  12. void fun(int a) {  
  13.     cout << "test in fun: "  << test << endl;  
  14.     cout << "a: " << a << endl;  
  15.         printf("test in fun: %d\n", test);  
  16.         printf("a: %d\n", a);  
  17. }  
  18.   
  19. /* 
  20. #ifdef __cplusplus 
  21. } 
  22. #endif 
  23. */  
[cpp]  view plain  copy
  1. //cpptest.h  
  2. #ifndef CPP_TEST_H  
  3. #define CPP_TEST_H  
  4.   
  5. #ifdef __cplusplus  
  6. extern "C" {  
  7. #endif  
  8.   
  9. void fun(int);  
  10. //写成这样也会出错:extern "C" void fun(int);  
  11. extern int test;  
  12.   
  13.   
  14. #ifdef __cplusplus  
  15. }  
  16. #endif  
  17.   
  18. #endif  
编译生成C++动态库:g++ --shared -o libtest_c_use_c++.so cpptest.cpp

[cpp]  view plain  copy
  1. //c_use_c++.c  
  2. #include <stdio.h>  
  3. #include "cpptest.h"  
  4. extern void fun(int);  
  5.   
  6. extern int test;  
  7. int main(int argc, char *argv[]) {  
  8.     test = 1;  
  9.     printf("test in main: %d\n", test);  
  10.     fun(4);  
  11.     return 0;  
  12. }  
使用上边编译生成的动态库libtest_c_use_c++.so来编译该C文件:gcc -o c_use_c++ c_use_c++.c -L /home/yang -ltest_c_use_c++

执行:./c_use_c++

执行结果如下图所示。


         在上面的过程中,C编写的文件c_use_c++.c使用gcc编译器来以C语言的方式调用C++动态库libtest_c_use_c++.so。可以看到这里的C代码和正常C程序的代码是一样的,在C调用C++过程中仅仅是对C++函数做了相应的处理,这也符合上面说的”要实现C调用C++函数通过对要调用的C++函数进行处理“。


        注意如果在cpptest.h中没有下面的语句程序将无法正常执行,会出现下面第一个图所示的错误,提示找不到fun函数的定义。这是因为g++编译的C++函数fun在C程序调用中并不能被识别。

         那么,我们同样不要下面的代码,将cpptest.h文件中的fun函数声明”void fun(int)“改为其下面注释的那行的形式,即直接使用extern ”C"来声明fun函数呢?同样会出错,如下面第二个图,因为是gcc编译C代码,这样C代码调用的extern “C"将没法识别,因为C中没有定义该链接指示。所以关键的代码就是下面的代码。这段代码的含义是如果使用的是C++代码时才使用extern "C",因为这种技术也可能会用在由C头文件产生出的C++文件中,这样使用是为了建立起公共的C和C++文件,也就是保证当这个文件被用做C文件编译时,可以去掉C++结构,也就是说,extern "C"语法在C编译环境下是不允许的

[cpp]  view plain  copy
  1. #ifdef __cplusplus  
  2. extern "C" {  
  3. #endif  
  4.   
  5. //do something  
  6.   
  7. #ifdef __cplusplus  
  8. }  
  9. #endif  

更新:

      注意,如上面的方式中,是将C++文件cpptest.cpp文件编译成动态库,然后C程序调用C++的动态库。这样的方式是可行的。如果用这样的方式是不行的:先用g++将cpptest.cpp编译为cpptest.o文件,然后通过gcc  -o  c_use_c++  c_use_c++.o   cpptest.o, 这样的方式是没法运行程序的。因为cpptest.o是采用C++方式编译的,然后再用gcc方式生成c_use_c++时就是用C方式,所以无法运行,程序出错。


C调用C++的类成员函数

       (未编程验证) 需要提供一个简单 的包装。例如,

[cpp]  view plain  copy
  1. //C++ code  
  2. class Test {  
  3.   public:  
  4.     virtual int f(int);  
  5. }  
  6. extern "C" int call_class_f(Test* p, int i) {  
  7.   return p->f(i);  
  8. }  
这样就可以调用Test::f():

[cpp]  view plain  copy
  1. //C code  
  2. int call_class_f(struct Test *p, int i);  
  3. void test_c(struct Test *p, int i) {  
  4.    int t = call_class_f(p, i);  
  5. }  

C调用C++的重载函数

       (未编程验证)必须为重载函数提供不同的名字的包装,才能调用重载函数。

[cpp]  view plain  copy
  1. // C++ code:    
  2. void f(int);    
  3. void f(double);    
  4. extern "C" void f_i(int i) { f(i); }    
  5. extern "C" void f_d(double d) { f(d); }    


[cpp]  view plain  copy
  1. /* C code: */    
  2. void f_i(int);    
  3. void f_d(double);    
  4. void cccc(int i,double d)    
  5. {    
  6.  f_i(i);    
  7.  f_d(d);    
  8. /* ... */    
  9. }    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值