為什麼windows的dll要區分導入導出?

而Unix的so完全不需要這套玩意。

今天慘遭蹂躪啊,關鍵是一部分不太正常的導入導出wrapper是外部庫的。。。

人類為什麼要這樣相互傷害。。。

===== 純潔的分割線 =====

補充:是指dllexport和dllimport。

我用的一個庫,它針對MSVC定義了這麼一套宏:

# ifdef FOO_DLL_BUILD
# define FOO_API __declspec (dllexport)
# pragma warning (disable: 4251)
# elif defined (FOO_DLL)
# define FOO_API __declspec (dllimport)
# pragma warning (disable: 4251)
# endif

然後,這個庫所有的class前面都帶個FOO_API。

它要求你從它的源代碼生成dll的時候,必須帶上全局宏定義FOO_DLL_BUILD;而在使用庫的時候,必須帶上全局宏定義FOO_DLL。如果你不設,就以一堆詭異的鏈接時錯誤死給你看。


so不需要?不,那是因為so默認把所有的都導出,所有的,所有的!

在現在的gcc裡面,你仍然可以用__attribute__((__visibility__(xxx)))來控制是否對外可見。這就相當於控制了導出。這麼做之後,binary大小可以小非常多非常多非常多。


VC和GCC編譯器都讓頭文件作者可以選擇性導出函數,導出符號沒搞對不論在Windows上還是Linux上都會有詭異的錯誤。

最小化導出的函數介面使得程序員們可以隱藏細節,是代碼模塊化的一部分。減少導出的符號數目也會減少連接器的工作量,減少編譯所需時間。

so不需要這玩意?你的so不會是靜態鏈接的吧。

參考https://gcc.gnu.org/wiki/Visibility


dll只能導出啊,你編譯的時候導入的是編譯dll的時候順便弄出來的一個lib文件,並沒有什麼導入dll的這種動作。

而一個程序鏈接lib這種事情是再正常不過的了。


我理解的題主說的意思應該是想表達這樣的現象:

windows下需要使用導出符號表(.def)文件(or 在.h裡面使用__declspec(dllexport)自動生成.def)來鏈接dll才能讓dll的符號表裡有對應的符號,而在編譯使用了dll的程序的時候也需要用那個.def文件(或者__declspec(dllimport))來鏈接到dll文件上。不像linux下不需要指定這些你就可以正常的編譯鏈接。

原因是windows下你使用的是ms的鏈接器,ms的鏈接器是需要使用.def來尋找符號的,假如編譯的時候沒有.def文件那麼默認就一個符號都不導出,並且沒有.def文件也不能從dll中導入符號。而linux下你使用的gcc的鏈接器的行為就是正好反過來的,編譯動態鏈接庫的時候是默認把符號全部導出了,然後鏈接階段也會自動從動態鏈接庫中導入符號(因為導出的name裡面是修改過的保存了函數的調用信息的)。

但是如果你想在gcc編譯出來的動態鏈接庫文件中隱藏某些符號怎麼辦?這個據我所知你還是得使用所謂的version-script才行,性質其實和.def差不多了。

補充:

看了下樓上的回答中的評論,題主說的是C++的類里的靜態成員的問題,此時dllimport關鍵字和動態庫生成的lib文件就是必須的了,這個原因是.def文件裡面只能包括extern "C"的函數,即C-Style的函數


題主的情況,直接看最後的補充:

導出表是給DLL工程用的,是告訴DLL工程,哪些符號是對外公開的,哪些符號是私有的。

導入表是給使用DLL的工程用的,是告訴使用者,要使用哪些符號。

如果不用__declspec (dllexport),那麼也可以用DEF文件來實現(我最喜歡這種方式)。

如果不用__declspec (dllimport),那麼也可以用LIB文件來實現(我最喜歡這種方式)。

DEF文件需要手寫。

LIB文件一般都隨DLL一起發布,如果是自建工程,在工程的目標文件夾里找,能找到的。

拿到LIB文件以後,把LIB文件放到DLL的使用者工程里,添加到工程的依賴庫里,如果不需要調試的話,連DLL文件都不需要拷,自己寫函數聲明然後直接使用就可以了,編譯的時候會正確鏈接(話說VC6里winsock開發就是這樣的)。

所以DEF文件是導出表,LIB文件是導入表,VS里所有WindowsAPI實際上也都包含在一組LIB文件里(比如CreateFile),在工程屬性里可以看到(常見的API基本上位於kernel32.lib user32.lib gdi32.lib)

如果DLL使用C語言開發,那麼這麼做足夠了。

如果是C++的話就麻煩了點,因為C++不能用DEF文件,然後題主說不用導入表會編譯失敗,我試了一下,果然C++是不行的。

---------------------題主遇到LIB文件失效的的是這種情況---------------------

然後我查了一下MSDN,dllimport的一大作用是導入類的結構,最主要的是類的靜態成員:

dllimport Classes

When you declare a class dllimport, all its member functions and static data members are imported. Unlike the behavior of dllimport and dllexport on nonclass types, static data members cannot specify a definition in the same program in which a dllimport class is defined.

所以題主說編不過,很有可能這個DLL里用了類的靜態成員,沒準還給初始化了,要是這種情況,LIB文件就不管用了,唯一的方法是用dllimport,可能是為了告訴編譯器這地方要多做點事情吧。

另外,我不清楚so庫里是否允許導出一個C++的類,並且連帶靜態成員也一起導出去?

參見:http://blog.csdn.net/oneal1987/article/details/5798834


而Unix的so完全不需要這套玩意

原因是so默認全導出。

導出意味著編譯器必須為函數按照ABI規範生成調用代碼,並寫入符號表。不導出時編譯器可以直接inline此函數。我還是覺得默認不導出,顯示的指定導出函數/變數比較清真一些。

另外,如果你真的懶到連導出函數都不想指定,或者模塊間的耦合度的確很高,那麼建議你在windows上用靜態庫。你可以認為靜態庫就是項目所有obj文件的合集。限制是不能跨編譯器。


dll的導出聲明是很有用的功能,可以隱藏內部細節,可以屏蔽符號衝突

Linux的so默認是導出全部符號,其實不是一種良好的方式,大概是因為開源工作者的自己沒什麼好隱藏的思維方式形成的。當然還是有方法可以隱藏的。除了給每個函數加上屬性,也可在鏈接的時候一次性生成例如:

$(LD) -shared $(LDFLAGS) -Wl,--retain-symbols-file=libxxx.symtab -Wl,--version-script=libxxx.dynsym $(OBJS) $(STATIC_LIBS) -o $@ $(SHARED_LIBS)


#ifdef _API_EXPORT_

#define __apispec __declspec(dllexport)

#else

#define __apispec __declspec(dllimport)

#endif

class __apispec BlaBlaBla{

public:

void xxxx();

};

======================================

你改成下面這樣,使用的時候就不用定義全局宏 FOO_DLL啦。

# ifdef FOO_DLL_BUILD
# define FOO_API __declspec (dllexport)
# pragma warning (disable: 4215)
# else
# define FOO_API __declspec (dllimport)
# pragma warning (disable: 4251)
# endif


DLL的導出聲明是必須要的,導入聲明不使用也是可以的,使用導入聲明是為了加快調用導入聲明的函數。


推薦閱讀:

關於APP token驗證的疑問?
代碼耦合是怎麼回事呢?
能否比較一下常見C++開發框架的API風格?
Web API介面如何防止本網站/APP以外的調用?
如何才能寫出簡潔好看的API文檔,有沒有開源框架可以用?

TAG:微軟Microsoft | API | 動態鏈接庫 |