是時候總結一下__declspec(dllimport)的作用了。可能有人會問:__declspec(dllimport)和__declspec(dllexport)是一對的,在動態鏈接庫中__declspec(dllexport)管導出,__declspec(dllimport)管導出,就像一個國家一樣,有出口也有進口,有什么難理解的呢?這是一種很自然的思路,開始我也是這樣理解。
但是在兩年前的一個項目中,我發現不用__declspec(dllimport)似乎也可以。比如現在我新建一個使用共享MFC DLL的規則DLL工程:DllDlg。然后我新建兩個文件:DllApi.h和DllApi.cpp。DllApi.h作為接口文 件,DllApi.cpp作為實現文件。
接着在DllApi.h聲明一個函數:
- __declspec(dllexport) void HelloWorld();
- __declspec(dllexport) void HelloWorld();
在DllApi.cpp寫這個函數的實現:
- void HelloWorld()
- {
- AfxMessageBox(_T("HelloWorld"));
- }
- void HelloWorld()
- {
- AfxMessageBox(_T("HelloWorld"));
- }
這樣外部的應用程序或dll就能調用HelloWorld函數。這里要特別提醒的是:有些網友說要把DllApi.h中的__declspec(dllexport) void HelloWorld();改為__declspec(dllimport) void HelloWorld();才能提供給外部調用,實際上這並不需要,這個我已經測試過。從那時我就產生一個疑問:照這樣,像類似下面的:
- #ifdef _EXPORTING
- #define API_DECLSPEC __declspec(dllexport)
- #else
- #define API_DECLSPEC __declspec(dllimport)
- #endif
- #ifdef _EXPORTING
- #define API_DECLSPEC __declspec(dllexport)
- #else
- #define API_DECLSPEC __declspec(dllimport)
- #endif
是不是就只剩下一種作用:讓外部調用者看得更自然些,知道哪些接口是自己工程需要導入的?__declspec(dllimport)是不是一點實際作用都沒有呢?這個疑問一直盤旋在我的腦海。直到最近,我在CSDN論壇上發了一個帖子:
__declspec(dllimport) 的作用到底在哪里呢?
總結了各位大蝦的發言,特得出如下結論:
1. 在導入動態鏈接庫中的全局變量方面起作用:
使用類似
- #ifdef _EXPORTING
- #define API_DECLSPEC __declspec(dllexport)
- #else
- #define API_DECLSPEC __declspec(dllimport)
- #endif
- #ifdef _EXPORTING
- #define API_DECLSPEC __declspec(dllexport)
- #else
- #define API_DECLSPEC __declspec(dllimport)
- #endif
可以更好地導出dll中的全局變量,比如按照的宏,可以在dll中這樣導出全局變量:
- API_DECLSPEC CBtt g_Btt;
- API_DECLSPEC CBtt g_Btt;
然后在調用程序這樣導入:
- API_DECLSPEC CBtt g_Btt;
- API_DECLSPEC CBtt g_Btt;
當然也可以使用extern關鍵字,比如在dll中這樣導出全局變量:
- CBtt g_Btt;
- CBtt g_Btt;
然后在調用程序這樣導入:
- extern CBtt g_Btt;
- extern CBtt g_Btt;
但據說使用__declspec(dllimport)更有效。
2. __declspec(dllimport)的作用主要體現在導出類的靜態成員方面,
比如在動態鏈接庫中定義這樣一個導出類:
- class __declspec(dllexport) CBtt
- {
- public:
- CBtt(void);
- ~CBtt(void);
- public:
- CString m_str;
- static int GetValue()
- {
- return m_nValue;
- }
- private:
- static int m_nValue;
- };
- class __declspec(dllexport) CBtt
- {
- public:
- CBtt(void);
- ~CBtt(void);
- public:
- CString m_str;
- static int GetValue()
- {
- return m_nValue;
- }
- private:
- static int m_nValue;
- };
照上面這樣聲明,外部雖然可以使用CBtt類,但不能使用CBtt類的GetValue函數,一使用就會出現無法解析的外部符號 "public: static int CBtt::m_nValue" (?m_nValue@CBtt@@2HA)。只有如下聲明才能使用CBtt類的GetValue函數:
- #ifdef _EXPORTING
- #define API_DECLSPEC __declspec(dllexport)
- #else
- #define API_DECLSPEC __declspec(dllimport)
- #endif
- class API_DECLSPEC CBtt
- {
- public:
- CBtt(void);
- ~CBtt(void);
- public:
- CString m_str;
- static int GetValue()
- {
- return m_nValue;
- }
- private:
- static int m_nValue;
- };
- #ifdef _EXPORTING
- #define API_DECLSPEC __declspec(dllexport)
- #else
- #define API_DECLSPEC __declspec(dllimport)
- #endif
- class API_DECLSPEC CBtt
- {
- public:
- CBtt(void);
- ~CBtt(void);
- public:
- CString m_str;
- static int GetValue()
- {
- return m_nValue;
- }
- private:
- static int m_nValue;
- };
3. 使用隱式使用dll時,不加__declspec(dllimport)完全可以,使用上沒什么區別,只是在生成的二進制代碼上稍微有點效率損失。
a、 不加__declspec(dllimport)時,在使用dll中的函數時,編譯器並不能區別這是個普通函數,還是從其它dll里導入的函數,所以其生 成的代碼如下:
call 地址1
地址1:
jmp 實際函數地址
b、有 __declspec(dllimport)時,編譯器知道這是要從外部dll導入的函數,從而在生成的exe的輸入表里留有該項,以便在運行 exe,PE載入器加載exe時對輸入地址表IAT進行填寫,這樣生成的代碼如下:
call dword ptr[輸入表里哪項對應的內存地址] (注意:現在就不需要jmp stub了)。這里
有興趣的朋友可以參看《編譯原理》和 PE文件格式。
4.使用__declspec(dllimport)體現了語言的一種對稱美,比如雖然!true就是表示false,但是我們還是需要false這個關鍵字,這里體現了一種對稱美。
在此特別感謝CSDN的眾位大俠:superdiablo、wltg2001、ccpaishi、jszj、WizardK、hurryboylqs、jingzhongrong、jameshooo、glacier3d、winnuke、starnight1981、laiyiling、yang79tao、ForestDB、zhouzhipen、lxlsymbome、Beyond_cn。
參考文獻:
1. __declspec(dllimport) 到底有什么用?
from: http://blog.csdn.net/clever101/article/details/5421782