1. 程式人生 > >extern "C" 與 __stdcall

extern "C" 與 __stdcall

轉自http://blog.csdn.net/huapeng_guo/article/details/7672337

C語言中extern c

c與c++程式連線問題 
它們之間的連線問題主要是因為c c++編繹器對函式名譯碼的方式不能所引起的,考慮下面兩個函式 
/* c*/ 
int strlen(char* string) 
{ 
... 
} 

//c++ 
int strlen(char* string) 
{ 
... 
} 

兩個函式完全一樣。在c在函式是通過函式名來識別的,而在C++中,由於存在函式的過載問題,函式的識別方式通函式名,函式的返回型別,函式引數列表三者組合來完成的。因此上面兩個相同的函式,經過C,C++編繹後會產生完全不同的名字。所以,如果把一個用c編繹器編繹的目的碼和一個用C++編繹器編繹的目的碼進行連線,就會出現連線失敗的錯誤。 

解決的方法是使用extern C,避免C++編繹器按照C++的方式去編繹C函式 
在標頭檔案中定義: 
extern "C" int strlen(char* string) 
或 
extern "C" 
{ 
int strlen(char* string) 
} 
當C編繹器遇到extern "C"的時候就用傳統的C函式編譯方法對該函式進行編譯。由於C編繹器不認識extern "C"這個編繹指令,而程式設計師又希望C,C++程式能共用這個標頭檔案,因此通常在標頭檔案中使用_cplusplus巨集進行區分: 
#if define _cplusplus 
extern "C"{ 
#endif 
int strlen(char* string) 
#ifdefine _cplusplus 
} 
#endif

#define CALLBACK     __stdcall
#define WINAPI       __stdcall

那麼,除了__stdcall,還有別的呼叫型別嗎?究竟什麼是呼叫型別呢?我的理解是:呼叫型別就是如何使用函式引數的一種規則。有三種呼叫型別:__fastcall、__cdecl、__stdcall。
1、__cdecl呼叫型別:
    這是C的呼叫規則。對於所有非C++成員函式或未標有__stdcall或__fastcall的函式來說,這是預設呼叫規則。


2、__fastcall呼叫型別:
    從字面意思可知,這是一種快速呼叫。因為CPU的暫存器會被使用來存放函式引數列表中的頭幾個引數。而剩下引數將被從右至左地推倒堆疊上。被呼叫函式將從 暫存器和堆疊獲得函式引數。在x86中,ECX和EDX一般被用來存放開始的引數。在.NET中,為了效能上的快速,就是使用ecx和edx來實現 __fastcall的。
3、 __stdcall呼叫型別:
    該呼叫只是通過堆疊來push和pop引數。push引數時,順序是從右到左。

現在,你應該明白了吧。最後,我帶一句。三種呼叫型別在VC編譯器中對應/Gd、/Gr、/Gz三個編譯選項

呼叫C/C++製做的DLL檔案中匯出函式的幾點說明

用某種語言呼叫C/C++製做的DLL檔案中匯出函式時,有幾點注意:

一、“_stdcall”的作用 
在C/C++中函式預設Calling Conventions(呼叫約定)是:
引數由右向左壓入棧,由呼叫者清空棧。,
在FORTRAN、PASCAL、VisualBASIC等語言中,函式的Calling Conventions是:
引數由右向左壓入棧,由被調函式清空棧。

那麼在C/C++中使用_stdcall宣告匯出函式,就可以指定C/C++按照FORTRAN等的呼叫約定申明該函式,這時FORTRAN等其它語言中再呼叫這些函式就不會出現報錯:run-time '49':Bad DLL call conventions.

二、加“extern "C"”的作用    
    extern "C" int _stdcall Extest();
    如上的原型,函式名符號會被VC編譯器處理為 
    int _stdcall Extest();
    如上的原型,函式名符號會被VC編譯器處理為 
    C語言的編譯連結環境不能處理第二種方式的修飾名,所以如果你的DLL要在C和C++的環境下都適用,那麼應該使用第一種命名方式。

例如:在MFC的類中呼叫自己寫的C函式,有時出現錯誤說無法找到函式的定義,原因是由於C 和C++的編譯器對函式的修飾名不同,C++的修飾名中包括了各引數型別,因此通常情況下,C++程式無法找到C庫中的函式,需要在宣告C函式時加上 extern "C"的說明:

extern"C"void foo();
C++編譯器就會用C的修飾名方式來進行連線呼叫。同樣,當C需要呼叫C++函式時,該C++函式也必須宣告為extern "C"。通常可以在C的標頭檔案裡這樣定義:

#ifdef __cplusplus
extern"C" {
#endif

 ...

#ifdef __cplusplus
 }
#endif

就可相容C和C++程式。