extern "c"的作用是宣告以c語言的格式編譯當前**:
上**,兩個函式,分別以c和c++格式編譯,看看效果是什麼:
// extern "c" 與 預設c++ 方式的區別
extern "c" __declspec(dllexport) void func1_c();
__declspec(dllexport) void func1_cpp();
void func1_c(){}
void func1_cpp(){}
很簡單的兩個函式,沒有輸入也沒有輸出。編譯,檢視生產的dll匯出的函式名稱:
可以看到宣告extern "c"編譯的函式名和我們定義的函式名是一致的,而以c++方式編譯的函式名加了字首和字尾。
再分析一下編譯後的「func1_c」,既沒有字首又沒有字尾,說明呼叫約定是__cdecl(預設呼叫約定)。
呼叫約定,約定的什麼?
在表面看來,約定了編譯後的函式名稱;實際約定的是由呼叫者還是被呼叫者清理堆疊上的引數。
有兩派:
__cdecl :呼叫者清理
__stdcall :被呼叫者清理
__fastcall比較特殊,沒有被標準化,各個編譯器實現不一樣。
但是,但是!!!在windows x64環境下編譯**時,只有一種呼叫約定,也就是說,32位下的各種約定在64位下統一成一種了。(x86呼叫約定 - 維基百科)
再深入就涉及到彙編了,到此為止;下面講一下編譯後的函式名稱的區別。
還是上圖上**:
這裡只寫出宣告**,實際是沒有定義的函式是不會編譯的。
#ifdef dllapi
#define dllapi __declspec(dllexport)
#else
#define dllapi __declspec(dllimport)
#endif
#ifndef dllapi_extern_c
#define dllapi_extern_c extern "c" dllapi
#endif
// 以c++方式編譯
dllapi_extern_c void __stdcall fun_extern_c_stdcall();
dllapi_extern_c int __stdcall fun_extern_c_stdcall0();
dllapi_extern_c int __stdcall fun_extern_c_stdcall1(int param);
dllapi_extern_c int __stdcall fun_extern_c_stdcall2(int param, char param2);
dllapi_extern_c int __stdcall fun_extern_c_stdcall3(int param, char param2,long param3);
dllapi_extern_c int __cdecl fun_extern_c_cdecl(int param);
dllapi_extern_c int __fastcall fun_extern_c_fastcall(int param);
// 以c方式編譯
dllapi void __stdcall fun_cpp_stdcall();
dllapi int __stdcall fun_cpp_stdcall0();
dllapi int __stdcall fun_cpp_stdcall1(int param);
dllapi int __stdcall fun_cpp_stdcall2(int param, char param2);
dllapi int __stdcall fun_cpp_stdcall3(int param, char param2, long param3);
dllapi int __cdecl fun_cpp_cdecl(int param);
dllapi int __fastcall fun_cpp_fastcall(int param);
上圖:
__stdcall呼叫約定:
1)、以"?「標識函式名的開始,後跟函式名;
2)、函式名後面以」@@yg"標識參數列的開始,後跟參數列;
3)、參數列以代號表示:
x–void ,
d–char,
e–unsigned char,
f–short,
h–int,
i–unsigned int,
j–long,
k–unsigned long,
m–float,
n–double,
_n–bool,
pa(32位)/pea(64位)–表示指標,後面的代號表明指標型別,如果相同型別的指標連續出現,以"0"代替,乙個"0"代表一次重複;
4)、參數列的第一項為該函式的返回值型別,其後依次為引數的資料型別,指標標識在其所指資料型別前;
5)、參數列後以"@z"標識整個名字的結束,如果該函式無引數,則以"z"標識結束。
其格式為"?functionname@@yg***@z"或"?functionname@@yg*xz",例如:
int __stdcall func_cpp_stacall3(int param,char param2,long param3)
編譯後函式名:「?func_cpp_stacall3@@yghhdj@z」
「?」 以c++方式編譯
「@@」 後面為引數定義
「yg」 呼叫約定為__stdcall
「h」 返回值為 int 型
「h」 第乙個引數為 int 型
「d」 第二個引數為 char 型
「j」 第三個引數為 long 型
「@z」 有參函式名字尾
void __stdcall func_cpp_stdcall()
編譯後函式名:「?func_cpp_stdcall@@ygxxz」
「?」 以c++方式編譯
「@@」 後接引數定義
「x」 返回值為 void
「xz」 無參函式字尾
__cdecl呼叫約定:規則同上面的_stdcall呼叫約定,只是參數列的開始標識由上面的"@@yg"變為"@@ya"。
__fastcall呼叫約定:
規則同上面的_stdcall呼叫約定,只是參數列的開始標識由上面的"@@yg"變為"@@yi"。
**__stdcall呼叫約定:**
以「_」開頭,以「@數字」結尾,數字為引數占用的記憶體位元組數。
**__cdecl呼叫約定:**
不改變函式名稱。
**__fastcall呼叫約定:**
以「@」開頭,以「@數字」結尾,數字為引數占用的記憶體位元組數。
總結:假設函式為:int func(int arg1,char arg2,long arg3);
以下列方式編譯後的函式名為:
cdecl
stdcall
fastcall
以c方式編譯
func
_func@12
@func@12
以c++方式編譯
?func@@yahhdj@z
?func@@yghhdj@z
?func@@yihhdj@z
為什麼是@12?int佔4byte,char佔1byte,long佔4byte,4+1+4=9?只想說一句:記憶體4位對齊!。注意:指標在32位程式中是4位元組,64位程式是8位元組
從上一節可以看出,只有以c方式編譯且呼叫約定為「__cdecl」(extern 「c」 __declspec(dllimport) int function)時函式名不變,其他時候函式名都會變,在大部分時候都沒有影響(c/c++提供.lib檔案即可、c#需要宣告呼叫約定的特性),但是在delphi呼叫c/c++ dll時不能直接使用定義的函式名,只能使用編譯後的函式名。為了統一編譯後的函式名稱,只能使用模組定義檔案(def檔案)定義函式名稱,使用模組定義檔案可以不用__declspec(dllimport)宣告函式,最好都統一在.def檔案中宣告,方便管理。
看一下.def檔案格式:
根據函式名稱匯出函式
根據函式名稱匯出函式 相關的rva 各種rva資料寬度 根據名稱匯出函式的步驟 獲取模組首位址,首位址也是dos頭首位址 根據dos頭中的e lfanew rva,因為pe頭對映到記憶體中後和exe檔案中是一樣的,所以這個位址也是file address,即lfa這個字首 定位到nt頭 根據nt頭中...
DLL匯出函式名稱改編的解決方法
分類 c c 程式設計學習 vc ms 2011 05 26 15 04 1437人閱讀收藏 舉報dll delphi api編譯器 pascal winapi 1.dll編譯後匯出函式名稱改編 在編寫乙個dll後,為了能被別的程式呼叫,需要將被使用的函式匯出 但是一般的編譯器都會將到處函式名稱改編...
DLL匯出函式名稱改編的解決方法
1.dll編譯後匯出函式名稱改編 在編寫乙個dll後,為了能被別的程式呼叫,需要將被使用的函式匯出 但是一般的編譯器都會將到處函式名稱改編 例如 在vc中新建乙個空的win32 dll工程,然後新增下面的檔案 cpp view plain copy ifdef dll api declspec dl...