DLL之__declspec(dllexport)與__declspec(dllimport)用法


  動態鏈接庫的使用可分為:

  顯式調用:使用LoadLibrary載入動態鏈接庫-GetProcAddress獲取某函數地址。

  隱式調用:使用#pragma comment(lib, “XX.lib”)的方式,也可以直接將XX.lib加入到工程中。  

一、定義及基本用法

  按C++標准,class 與className 中間不可以存在任何實質性的東西的。但dllimport / dllexport只是修飾符,Windows平台下為了dll的兼容性的特有關鍵字,他們都是DLL內的關鍵字,即導出與導入。他們是將DLL內部的類與函數以及數據導出與導入時使用的,看它的具體定義是什么。一般類的修飾符有導入或導出即:

__declspec(dllexport) extern __declspec(dllimport) 

       dllexport是在這些類、函數以及數據聲明的時候使用。用他表明這些東西可以被外部函數使用,即(dllexport)是把 DLL中的相關代碼(類,函數,數據)暴露出來為其他應用程序使用。使用了(dllexport)關鍵字,相當於聲明了緊接在(dllexport)關鍵字后面的相關內容是可以為其他程序使用的。

       dllimport是在外部程序需要使用DLL內相關內容時使用的關鍵字。當一個外部程序要使用DLL 內部代碼(類,函數,全局變量)時,只需要在程序內部使用(dllimport)關鍵字聲明需要使用的代碼就可以了,即(dllimport)關鍵字是在外部程序需要使用DLL內部相關內容的時候才使用。(dllimport)作用是把DLL中的相關代碼插入到應用程序中。

       _declspec(dllexport)與_declspec(dllimport)是相互呼應,只有在DLL內部用dllexport作了聲明,才能在外部函數中用dllimport導入相關代碼。但MSDN文檔里面,對於 __declspec(dllimport)的說明讓人感覺有點奇怪,先來看看MSDN里面是怎么說的:

         不使用 __declspec(dllimport)也能正確編譯代碼,但使用 __declspec(dllimport) 使編譯器可以生成更好的代碼。編譯器之所以能夠生成更好的代碼,是因為它可以確定函數是否存在於 DLL 中,這使得編譯器可以生成跳過間接尋址級別的代碼,而這些代碼通常會出現在跨DLL 邊界的函數調用中。但是,必須使用 __declspec(dllimport) 才能導入 DLL 中使用的變量。

      使用__declspec(dllimport)可以生成更好的代碼,這點好理解,但必須使用它才能導出dll中的變量,對於動態庫本身必須使用關鍵字__declspec(dllexport),對於應用程序,如果不使用動態庫導出的變量,不使用關鍵字__declspec(dllimport)也可以保證動態庫的正常使用,但實際使用中,還是建議應用程序使用關鍵字__declspec(dllimport),具體原因,還是上面MSDN的那段話。

>>> 注意:動態庫與靜態庫並存

   另外,有時我們的程序需要同時提供動態庫和靜態庫庫,且都使用一個頭文件,為了解決關鍵字的使用沖突,建議使用如下的宏定義:

1 #ifdefined DLL_EXPORTS
2     #ifdefined INSIDE_DLL
3          #define SIMPLE_CLASS_EXPORT__declspec(dllexport)
4     #else
5         #define SIMPLE_CLASS_EXPORT__declspec(dllimport)
6     #endif
7 #else
8       #define SIMPLE_CLASS_EXPORT
9 #endif 

    對於動態庫本身,需要定義宏DLL_EXPORTS和INSIDE_DLL 使用動態庫的應用程序定義宏DLL_EXPORTS,對於靜態庫,不需要定義DLL_EXPORTS,當然靜態庫的應用程序也不需要定義。如此定義,就可以讓動態庫和靜態庫的導出都使用同一份頭文件。

 二、實現及相關問題(導出類的簡單方式)

  加載一個dll時,其實你的程序是運行在兩個獨立空間的(dll的空間和你自己的程序空間),dll的對象模型其實相當嚴格,要訪問dll空間的變量和函數,必須導出他們,否則這些對象是不可見的。這可以通過加入一個def文件,或者在聲明中使用__declspec(dllimport)前綴,告訴編譯器以下這些變量和函數是從dll導出的。同時定義這些變量的dll源文件必須加上__declspec(dllexport)前綴,告訴編譯器這些函數需要被導出。 

  對類對象來說,靜態成員和函數必須加上這個前綴,因為這些對象都是在dll空間內的。在類的前面加上這些前綴就對整個類的成員進行了聲明。這樣在你的dll工程中定義__DLLEXPORT_IMP,__DLLEXPORT就會根據不同的工程轉換成相應的前綴聲明了。如果不加入這些前綴,鏈接會出現找不到符號的錯誤,因為這些符號在lib文件中被隱藏了。

//一般這樣寫一個宏: 
#if defined __DLLEXPORT_IMP 
#define __DLLEXPORT __declspec(dllexport) 
#else 
#define __DLLEXPORT __descspec(dllimport) 
#endif 

  分析如下代碼:

class VTK_PARALLEL_EXPORT vtkCompositer: public vtkObject {   //...
};

  關鍵字class和類名之間包含其他內容,這里的VTK_PARALLEL_EXPORT應該就是之前定義的可修飾class導入/到處的宏了。這樣主要還是為了使用方便,在編寫庫時,只要定義了VTK_PARALLEL_EXPORT 宏,所有動態庫中的類都會自動導出。如果內部使用的話將該宏定義將被展開為空串,在多文件或多個dll的情況下使用非常方便。

  這種方式是比較簡單的,同時也是不建議采用的不合適方式。只需要在導出類加上__declspec(dllexport),就可以實現導出類。對象空間還是在使用者的模塊里,dll只提供類中的函數代碼。不足的地方是:使用者需要知道整個類的實現,包括基類、類中成員對象,也就是說所有跟導出類相關的東西,使用者都要知道。通過Dependency Walker可以看到,這時候的dll導出的是跟類相關的函數:如構造函數、賦值操作符、析構函數、其它函數,這些都是使用者可能會用到的函數。

  這種導出類的方式,除了導出的東西太多、使用者對類的實現依賴太多之外,還有其它問題:必須保證使用同一種編譯器。導出類的本質是導出類里的函數,因為語法上直接導出了類,沒有對函數的調用方式、重命名進行設置,導致了產生的dll並不通用。 

三、使用虛函數導出(不使用_declspec(dllexport) / _declspec(dllimport))

  跟com類似,導出類是一個派生類,派生自一個抽象類——都是純虛函數。使用者需要知道這個抽象類的結構。DLL最少只需要提供一個用於獲取類對象指針的接口。使用者跟DLL提供者共用一個抽象類的頭文件,使用者依賴於DLL的東西很少,只需要知道抽象類的接口,以及獲取對象指針的導出函數,對象內存空間的申請是在DLL模塊中做的,釋放也在DLL模塊中完成,最后記得要調用釋放對象的函數。

        這種方式通用,產生的DLL沒有特定環境限制。借助了C++類的虛函數。一般都是采用這種方式。除了對DLL導出類有好處外,采用接口跟實現分離,可以使得工程的結構更清晰,使用者只需要知道接口,而不需要知道實現。

參考目錄:

  1.https://blog.csdn.net/huangyimo/article/details/81748939

  2.https://blog.csdn.net/inu1255/article/details/10810343

  3.https://blog.csdn.net/LinearF/article/details/81981031

       4.C++ DLL導出類 知識大全

       5.編寫DLL所學所思(1)——導出函數

  6.由extern "C"引申出C++、C動態庫調用的一些注意事項


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM