文章轉自開源電子論壇:http://www.openedv.com/forum.php?mod=viewthread&tid=7986php
看一些程序的時候總是有
「#ifdef __cplusplus
extern "C" {
#endif」的定義,搞搞清楚是怎麼回事:
Microsoft-Specific redefined Macros
__cplusplus Defined for C++ programs only.
意思是說,若是是C++程序,就使用
extern "C"{
而這個東東,是指在下面的函數不使用的C++的名字修飾,而是用C的
The following code shows a header file which can be used by C and C++
client applications:
// MyCFuncs.h
#ifdef __cplusplus
extern "C" { //only need to export C interface if
// used by C++ source code
#endif
__declspec( dllimport ) void MyCFunc();
__declspec( dllimport ) void AnotherCFunc();
#ifdef __cplusplus
}
#endif
當咱們想從C++中調用C的庫時,(注,驅動是用C寫的,連new、delete也不能用,鬱悶)不能僅僅說明 一個外部函數,由於調用C函數的編譯代碼和調用C++函數的編譯代碼是不一樣的。若是你僅說明一個外部函數, C++編譯器假定它是
C++的函數編譯成功了,但當你鏈接時會發現很可愛的錯誤。
解決的方法就是指定它爲C函數:
extern "c" 函數描述
指定一羣函數的話:
extern "C"{
n個函數描述
}
若是想C和C++混用的話:
#ifdef _cplusplus
extern "C"{
#endif
n個函數描述
#ifdef _cplusplus
}
#endif
extern "C"表示編譯生成的內部符號名使用C約定。
C++支持函數重載,而C不支持,二者的編譯規則也不同。函數被C++編譯後在符號庫中的名字與C語言的不一樣。例如,假設某個函數的原型爲: void foo( int x, int y ); 該函數被C編譯器編譯後在符號庫中的名字可能爲_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不一樣的編譯器可能生成的名字不一樣,可是都採用了相同的機制,生成的新名字稱爲「mangled name」)。_foo_int_int這樣的名字包含了函數名、函數參數數量及類型信息,C++就是靠這種機制來實現函數重載的。下面以例子說明,如何在C++中使用C的函數,
或者在C中使用C++的函數。
//C++引用C函數的例子
//test.c
#include <stdio.h>
void mytest()
{
printf("mytest in .c file ok\n");
}
//main.cpp
extern "C"
{
void mytest();
}
int main()
{
mytest();
return 0;
}
//在C中引用C++函數
在C中引用C++語言中的函數和變量時,C++的函數或變量要聲明在extern "C"{}裏,可是在C語言中不能使用extern "C",不然編譯出錯。
//test.cpp
#include <stdio.h>
extern "C"
{
void mytest()
{
printf("mytest in .cpp file ok\n");
}
}
//main.c
void mytest();
int main()
{
mytest();
return 0;
}
//綜合使用
通常咱們都將函數聲明放在頭文件,當咱們的函數有可能被C或C++使用時,咱們沒法肯定是否要將函數聲明在extern "C"裏,因此,咱們應該添加
#ifdef __cplusplus
extern "C"
{
#endif
//函數聲明
#ifdef __cplusplus
}
#endif
若是咱們注意到,不少頭文件都有這樣的用法,好比string.h,等等。
//test.h
#ifdef __cplusplus
#include <iostream>
using namespace std;
extern "C"
{
#endif
void mytest();
#ifdef __cplusplus
}
#endif
這樣,能夠將mytest()的實現放在.c或者.cpp文件中,能夠在.c或者.cpp文件中include "test.h"後使用頭文件裏面的函數,而不會出現編譯錯誤。
//test.c
#include "test.h"
void mytest()
{
#ifdef __cplusplus
cout << "cout mytest extern ok " << endl;
#else
printf("printf mytest extern ok n");
#endif
}
//main.cpp
#include "test.h"
int main()
{
mytest();
return 0;
}
extern "C" 的用意
前些天,編程序是用到了好久之前寫的C程序,想把裏面的函數利用起來,鏈接發現出現了找不到具體函數的錯誤:
如下是假設舊的C程序庫
C的頭文件
/*-----------c.h--------------*/#ifndef _C_H_#define _C_H_extern int
add(int x, int y);#endif
C的源文件
/*-----------c.c--------------*/int add(int x, int y){ return x+y;}
C++的調用
/*-----------cpp.cpp--------------*/#include "c.h"void main(){ add
(1, 0);}
這樣編譯會產生錯誤cpp.obj: error LNK2001: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z),緣由是找不到add的目標模塊這才令我想起C++重載的函數命名方式和C函數的命名方式,讓咱們回顧一下:C中函數編譯後命名會在函數名前加以"_",好比add函數編譯成obj文件時的實際命名爲_add,而c++命名則不一樣,爲了實現函數重載一樣的函數名add因參數的不一樣會被編譯成不一樣的名字
例如
int add(int , int)==>add@@YAHHH@Z,
float add(float , float )==>add@@YAMMM@Z,
以上是VC6的命名方式,不一樣的編譯器會不一樣,總之不一樣的參數一樣的函數名將編譯成不一樣目標名,以便於函數重載是調用具體的函數。
編譯cpp.cpp中編譯器在cpp文件中發現add(1, 0);的調用而函數聲明爲extern int add(int x, int y);編譯器就決定去找add@@YAHHH@Z,惋惜他找不到,由於C的源文件把extern int add(int x, int y);編譯成_add了;爲了解決這個問題C++採用了extern "C",這就是咱們的主題,想要利用之前的C程序庫,那麼你就要學會它,咱們能夠看如下標準頭文件你會發現,不少頭文件都有如下的結構
#ifndef __H#define __H#ifdef __cplusplusextern "C" {#endifextern int f1(int, int);extern int f2(int, int);extern int f3(int, int);
#ifdef __cplusplus}#endif#endif /*__H*/若是咱們仿製該頭文件能夠獲得
#ifndef _C_H_#define _C_H_#ifdef __cplusplusextern "C" {#endifextern
int add(int, int);#ifdef __cplusplus}#endif#endif /* _C_H_ */ 這樣編譯
/*-----------c.c--------------*/
int add(int x, int y){
return x+y;
}
這時源文件爲*.c,__cplusplus沒有被定義,extern "C" {}這時沒有生效對於C他看到只是extern int add(int, int);add函數編譯成_add(int, int);
而編譯c++源文件
/*-----------cpp.cpp--------------*/
#include "c.h"
void main()
{
add(1, 0);
}
這時源文件爲*.cpp,__cplusplus被定義,對於C++他看到的是extern "C" {extern int add(int, int);}編譯器就會知道 add(1, 0);調用的C風格的函數,就會知道去c.obj中找_add(int, int)而不是add@@YAHHH@Z;這也就爲何DLL中常看見extern "C" {},windows是採用C語言編制他首先要考慮到C能夠正確調用這些DLL,而用戶可能會使用C++而extern "C" {}就會發生做用ios