作者:magictong
時間:2010-09-08
注:例子演示裏面都是以debug模式下的彙編來講,在release下因爲經過一些優化,過程會有一些區別,但是最終的結論是一樣的。
__declspec本身就是microsoft對c++的擴展,因此後面的討論都是指在VS2005下編譯的結果,與__declspec(dllimport)相對的一個組合用法是__declspec(dllexport),__declspec(dllexport)是作用於PE文件導出函數、類、變量等等(如果你不用def文件導出函數,就必須使用__declspec(dllexport)來進行導出)。__declspec(dllimport)的具體作用在msdn上面講得比較模糊,如下Importing into an Application Using __declspec(dllimport):
英文:
Using __declspec(dllimport) is optional on function declarations, but the compiler produces more efficient code if you use this keyword. However, you must use __declspec(dllimport) for the importing executable to access the DLL's public data symbols and objects. Note that the users of your DLL still need to link with an import library.
中文:
在函數聲明上使用 __declspec(dllimport) 是可選操作,但如果使用此關鍵字,編譯器將生成更有效的代碼。但是,爲使導入的可執行文件能夠訪問 DLL 的公共數據符號和對象,必須使用 __declspec(dllimport)。請注意,DLL 的用戶仍然需要與導入庫鏈接。
所謂可選操作,就是可要可不要,但是如果使用了,編譯器會生成更高效的代碼。嗯,編譯器就是這麼一個意思。後來我測試了四種情況,來看看到底是什麼情況。
情況一:EXE內部調用自己的swap函數,swap函數沒有使用__declspec(dllimport)修飾
- int swap(int& a, int& b);
- int swap(int& a, int& b)
- {
- a = a ^ b;
- b = a ^ b;
- a = a ^ b;
- return 0;
- }
int swap(int& a, int& b);
int swap(int& a, int& b)
{
a = a ^ b;
b = a ^ b;
a = a ^ b;
return 0;
}
對swap的調用生成的彙編代碼爲:
swap(m, n);
00411423 lea eax,[n]
00411426 push eax
00411427 lea ecx,[m]
0041142A push ecx
0041142B call swap (411113h)
00411430 add esp,8
看一下411113h 這個地址是一條jmp指令,跳轉到swap的真正地址:
0x00411113 e9 98 04 00 00
00411113 jmp swap (4115B0h)
int swap(int& a, int& b)
{
004115B0 push ebp
004115B1 mov ebp,esp
004115B3 sub esp,0C0h
因此,這種情況僅僅是正常的的函數調用。
情況二:EXE內部調用自己的swap函數,swap函數使用__declspec(dllimport)修飾
這種情況跟情況一生成的代碼基本一樣,唯一有點點區別是在編譯的時候在swap的實現處會出現下面的警告(大概意思就是聲明有點不一致,與編譯器的預期有點不一樣,因爲編譯器預期會是一個dll的導出函數,嗯,大概是這麼個情況,不用深究):
warning C4273: 'swap' : inconsistent dll linkage
情況三:EXE內部調用dll的add函數,add函數不使用__declspec(dllimport)修飾
- #if IMPORTDLLDLL_EXPORTS
- #define API_DECLSPEC __declspec(dllexport)
- #else
- #define API_DECLSPEC
- #endif
- // -------------------------------------------------------------------------
- API_DECLSPEC int __stdcall add(int a, int b);
- int __stdcall add(int a, int b)
- {
- return a + b;
- }
#if IMPORTDLLDLL_EXPORTS
#define API_DECLSPEC __declspec(dllexport)
#else
#define API_DECLSPEC
#endif
// -------------------------------------------------------------------------
API_DECLSPEC int __stdcall add(int a, int b);
int __stdcall add(int a, int b)
{
return a + b;
}
對add的調用生成的彙編代碼爲:
add(m, n);
004113FC mov eax,dword ptr [n]
004113FF push eax
00411400 mov ecx,dword ptr [m]
00411403 push ecx
00411404 call add (411127h)
看下411127h這個地址也是一條jmp指令,跳轉到411620h
0x00411127 e9 f4 04 00 00
00411127 jmp add (411620h)
跳轉到411620h之後發現居然還不是add的地址,而是
00411620 jmp dword ptr [__imp_add (4181E0h)]
依然是一個jmp指令,跳轉到4181E0h指向的一個位置,看下內存,應該是跳轉到0x100110f0去執行: 0x004181E0 f0 10 01 10
跟過去,居然還是一條跳轉指令,這次是跳轉到0x10011340:
100110F0 jmp add (10011340h)
再跟過去,這次終於對了:
int __stdcall add(int a, int b)
{
10011340 push ebp
10011341 mov ebp,esp
10011343 sub esp,0C0h
10011349 push ebx
這次很糾結,中間多了幾個jmp指令,這個我們先不講,我們先看看情況四之後再討論。
情況四:EXE內部調用dll的add函數,add函數使用__declspec(dllimport)修飾
- #if IMPORTDLLDLL_EXPORTS
- #define API_DECLSPEC __declspec(dllexport)
- #else
- #define API_DECLSPEC __declspec(dllimport)
- #endif
- // -------------------------------------------------------------------------
- API_DECLSPEC int __stdcall add(int a, int b);
- int __stdcall add(int a, int b)
- {
- return a + b;
- }
#if IMPORTDLLDLL_EXPORTS
#define API_DECLSPEC __declspec(dllexport)
#else
#define API_DECLSPEC __declspec(dllimport)
#endif
// -------------------------------------------------------------------------
API_DECLSPEC int __stdcall add(int a, int b);
int __stdcall add(int a, int b)
{
return a + b;
}
對add的調用生成的彙編代碼爲:
add(m, n);
004113FC mov esi,esp
004113FE mov eax,dword ptr [n]
00411401 push eax
00411402 mov ecx,dword ptr [m]
00411405 push ecx
00411406 call dword ptr [__imp_add (4181E0h)]
這次情況好像有點不一樣,直接call了4181E0h指向的一個位置,看下內存:
0x004181E0 f0 10 01 10
也就是說跳轉到0x100110f0去執行,跟過去:
100110F0 jmp add (10011340h)
直接一個jmp指令到10011340h,再跟過去就是add的真正地址了:
int __stdcall add(int a, int b)
{
10011340 push ebp
10011341 mov ebp,esp
10011343 sub esp,0C0h
這種情況下,對比情況三,情況四少了2條jmp指令……
結論:
看來msdn說的沒錯o(∩_∩)o ,確實使用了__declspec(dllimport)之後,生成的代碼更加高效。在對release代碼的查看中發現,最終會少一條jmp指令。
其實整個來講還是比較好理解的,對於編譯器來講,你沒有__declspec(dllimport)這個來修飾函數聲明,它開始就會把函數當成一個本地函數來生成調用代碼(如:call add (411127h)),但是最後鏈接的時候發現這個函數是個動態鏈接庫裏面的函數,真正地址會存在輸入表裏面,因此中間就多了至少一條jmp指令來中轉。如果使用了__declspec(dllimport)來修飾,那麼編譯器知道,哦,是個外部的函數,函數的地址在輸入表裏面,雖然現在不知道函數真正地址,但是裝載之後該函數地址在輸入表中的位置是固定的,因此編譯器就直接生成調用輸入表中某個地方的函數地址的代碼了(如:call dword ptr [__imp_add (4181E0h)])。
【END】