標籤:dllimport dllexport
Microsoft Specific
You can declare C++ classes with the dllimport or dllexport attribute. These forms imply that the entire class is imported or exported. Classes exported this way are called exportable classes.
The following example defines an exportable class. All its member functions and static data are exported:
譯:可以在聲明C++類時使用dllimport和dllexport屬性。這兩個形式將隱含匯入或匯出整個類。通過這種方法匯出的類稱為可匯出類。
下列範例定義了一個可匯出類,其所有的成員函數和靜態將被匯出:
#define DllExport __declspec( dllexport ) class DllExport C { int i; virtual int func( void ) { return 1; }};
Note that explicit use of the dllimport and dllexport attributes on members of an exportable class is prohibited.
譯:注意,禁止在一個可匯出類的成員上顯式的使用dllimport和dllexport屬性。
balon註:如你不能像下例這樣寫:
#define DllExport __declspec( dllexport ) class DllExport C { DllExport int i; // 不可以在成員上使用dllexport DllExport int func( void ) { return 1; } // 不可以在成員上使用dllexport};
dllexport Classes
通過dllexport匯出類
When you declare a class dllexport, all its member functions and static data members are exported. You must provide the definitions of all such members in the same program. Otherwise, a linker error is generated. The one exception to this rule applies to pure virtual functions, for which you need not provide explicit definitions. However, because a destructor for an abstract class is always called by the destructor for the base class, pure virtual destructors must always provide a definition. Note that these rules are the same for nonexportable classes.
If you export data of class type or functions that return classes, be sure to export the class.
譯:當你聲明一個類為dllexport,其所有的成員函數和待用資料成員將被匯出。你必須在同一個程式中定義所有此類成員,否則會產生一個連結錯誤。
balon註:如在下面的匯出類中,func必須在這個DLL的工程中或是在使用DLL的程式中被定義,否則在使用時會出現無法解析的外部符號的連結錯誤。注意,定義可以放在DLL中,也可放在DLL的使用者工程中,只要在使用前被定義就可以。
方法一:在Dll工程中定義,在使用者工程中直接使用。
// dll.h
#define DllExport __declspec( dllexport )class DllExport C { int func( void );}; // dll.cppint C::func(void){ return 1;}
方法二:在DLL工程中只有一個聲明,沒有定義。可以在使用者使用前給出該函數的定義。另外,如果你在使用者程式中根本就沒有用到func,可以不提供其定義,不會出連結錯誤。
// dll.h
#define DllExport __declspec( dllexport )class DllExport C { int func( void );}; // client.cppint C::func(void){ return 1;} int main(){ C c; c.func(); // ok return 1;}
這個規則的唯一例外是對純虛函數你可以不用提供顯示的定義。
balon註:如下面例子中的純虛函數func,可以不必提供定義。因為這個匯出的類可能本身就是一個抽象類別,func就是一個沒有實現的函數,當然可以不提供定義。
但是,由於抽象類別的解構函式總是會被基類的解構函式調用的,因此純虛解構函式必須提供一個定義。這條規則對於不可匯出的類同樣適用。
balon註:根據我的實驗結果,將各種情況下的編譯結果列了一個表:
DLL工程中沒有對應函數定義的編譯結果 客戶程式使用方式
成員函數 正常連結。 可以定義此類的執行個體,不調用此成員函數就正常連結;調用此成員函數連結出錯,提示此函數為未決的外部符號。
虛成員函數 連結出錯,提示此函數為未決的外部符號。 -
純虛成員函數 正常連結。 不能定義此類執行個體,編譯出錯,提示無法執行個體化一個抽象類別。
解構函式 正常連結。 連結出錯,提示解構函式為未決的外部符號
虛解構函式 連結出錯,提示解構函式為未決的外部符號。 -
純虛解構函式 連結出錯,提示解構函式為未決的外部符號。 -
可見文檔中說的規則屬實,純虛解構函式被當作虛解構函式對待。但另一個問題是,為什麼虛函數(包括解構函式)就一定要在其聲明所在的工程中被定義,而普通函數就不必呢?我分析原因估計如下:
我們知道,C++類如果有虛函數的話(包括其基類有虛函數的情況),C++編譯器會在編譯時間為其產生一個虛表,並在建構函式中,將對應的虛表首地址填到類執行個體的虛表指標成員中。如果一個虛函數在派生體系中被多次實現,虛表中填入的是最底層(most-derived)實現。這個填表過程是,首先基類對象被構造,其建構函式先填入基類的虛表實現入口(這就是為什麼,在建構函式中調用虛函數,只會調用到當前類層次的實現,無法調用到衍生類別的實現的原因),接著衍生類別被構造,其建構函式將衍生類別的虛表入口填入,覆蓋掉剛才基類記錄的入口地址。這個過程一直進行,直到整個物件建構完成。
說了這麼多,其實其中最關鍵的一點就是:建構函式必須知道當前類的虛表入口地址,而這就需要知道虛表裡填寫的所有虛函數的入口地址!否則,編譯器將無法產生建構函式的填虛表的功能。於是,編譯器只好開始抱怨了。
因此,所有的虛函數必須在其聲明所在的工程中有定義,在連結時能正確找到其入口地址以便編譯器可以產生建構函式的填虛表功能。
如果你匯出一個類類型的資料成員,或是一個返回類的函數,請確保匯出那個類。
balon註:這個是一個指導原則,不是一個強制的編譯連結要求。只要你不使用返回的類的成員函數,並且返回類沒有虛表,可以不必匯出類。但多數情況下是要匯出的,所以作為一個好的原則,按MSDN的建議做就好了。在本文的下一篇中將對此有詳細介紹。
dllimport Classes
通過dllimport匯入類
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.
譯:當你將一個類聲明為dllimport,其所有的成員函數和待用資料成員將被匯入。與在非類類型上使用dllimport和dllexport不同的是,不能在有dllimport類的定義的同一個程式中指給出待用資料成員的定義。
balon註:這句話譯的有些拗口,原文也好不到哪裡。其實看了通過dllexport匯出類一節的註解,就好理解這裡想要說的意思了。意思就是:待用資料成員不能像其它成員函數那樣,可以在使用者工程中定義,而不在DLL本身工程中定義。
方法一、按要求在DLL中定義待用資料成員:
// dll.h
#define DllExport __declspec( dllexport )class DllExport C { static int x;};
// dll.cpp
int C::x = 0;
// client.cpp
int main(){ C c; c.x = 10; // ok return 1;}
方法二、試圖“在有dllimport類的定義的同一個程式中指給出待用資料成員的定義”,則在客戶程式編譯時間 出現編譯錯誤:
// dll.h
#define DllExport __declspec( dllexport )class DllExport C { static int x;}; // client.cppint C::x = 0; // C4273
int main(){ C c; c.x = 10; return 1;}
Inheritance and Exportable Classes
繼承與可匯出類
All base classes of an exportable class must be exportable. If not, a compiler warning is generated. Moreover, all accessible members that are also classes must be exportable. This rule permits a dllexport class to inherit from a dllimport class, and a dllimport class to inherit from a dllexport class (though the latter is not recommended). As a rule, everything that is accessible to the DLL‘s client (according to C++ access rules) should be part of the exportable interface. This includes private data members referenced in inline functions.
譯:一個可匯出類的所有基類都必須可匯出,否則會產生一個編譯錯誤。
balon註:事實上,這條規則也不是一定的,在滿足一定的條件情況下,基類不可匯出程式也是正常的。當然,多數情況下還是要匯出的,建議還是按MSDN的要求做好了。在本文的下一篇中將對此有詳細介紹。
如下例中,A是一個可匯出類,則A的基類Base也應當是一個可匯出類。
#define DllExport __declspec( dllexport )class DllExport A : public Base{ // ...};
此外,所有可訪問的類類型成員也必須是可匯出的。這條規則允許了一個dllexport類派生自一個dllimport類,或一個dllimport類派生自一個dllexport類(儘管後者是不被推薦的)。
balon註:如下例中,Base是一個dllimport類,
// BaseDll的標頭檔basedll.h
#define DllImport __declspec( dllimport )class DllImport Base{};
// DerivedDll的標頭檔Deriveddll.h
#include “basedll.h” // 將一個dllimport類聲明包含進來#define DllExport __declspec( dllexport )class DllExport A : public Base // A派生自dllimport類Base{ // ...};
結果就是,這個DLL的客戶可訪問的所有東西(依照C++的訪問規則)都應當是匯出介面的一部分。包括被inline函數引用的私人成員。
balon註:這句話其實是全文中最重要的一句話,其實這篇文檔如果把這句話展開說清楚了,也不用我在這裡寫這篇文章了。在本文的下一篇中將有對於這句話的深入討論
Selective Member Import/Export
選擇性成員匯入匯出
譯:Because member functions and static data within a class implicitly have external linkage, you can declare them with the dllimport or dllexport attribute, unless the entire class is exported. If the entire class is imported or exported, the explicit declaration of member functions and data as dllimport or dllexport is prohibited. If you declare a static data member within a class definition as dllexport, a definition must occur somewhere within the same program (as with nonclass external linkage).
譯:因為類中的成員函數和待用資料隱含進行外部連結,你可以在沒有將整個類匯出的情況下,在他們聲明中加上dllimport或是dllexport屬性。如果整個類被匯入或是匯出,將不允許顯式的以dllimport和dllexport對成員函數和資料進行聲明。如果你將類中的一個待用資料成員聲明為dllexport,在同一個程式中的某個地方應當有它的定義(如同非類外部連結那樣)。
balon註:前面幾句很好理解。最後一句實際上是在說,你可以把匯出一個待用資料成員,當作與一個從DLL中匯出一個非類成員的普通變數那樣對待,要在DLL所在工程中有定義。匯出一個普通變數方法就是在DLL中的某一個CPP檔案中定義此變數,並加上dllexport聲明:
// dll.cpp
__declspec( dllexport ) int x = 0;
那麼,對比一下將一個類的待用資料成員匯出的方法:
// dll.h
#define DllExport __declspec( dllexport )class A // 注意,這裡沒有匯出類A{public: DllExport static int x; // 所以這裡才可以匯出個別成員}; // dll.cppint A::x = 0;
Similarly, you can declare member functions with the dllimport or dllexport attributes. In this case, you must provide a dllexport definition somewhere within the same program.
譯:類似的,你也可以為一個成員函式宣告加上dllimport或dllexport屬性。這種情況下,你必須在同一個程式中的某處提供dllexport定義。
It is worthwhile to note several important points regarding selective member import and export:
· Selective member import/export is best used for providing a version of the exported class interface that is more restrictive; that is, one for which you can design a DLL that exposes fewer public and private features than the language would otherwise allow. It is also useful for fine-tuning the exportable interface: when you know that the client, by definition, is unable to access some private data, you need not export the entire class.
· If you export one virtual function in a class, you must export all of them, or at least provide versions that the client can use directly.
· If you have a class in which you are using selective member import/export with virtual functions, the functions must be in the exportable interface or defined inline (visible to the client).
· If you define a member as dllexport but do not include it in the class definition, a compiler error is generated. You must define the member in the class header.
· Although the definition of class members as dllimport or dllexport is permitted, you cannot override the interface specified in the class definition.
· If you define a member function in a place other than the body of the class definition in which you declared it, a warning is generated if the function is defined as dllexport or dllimport (if this definition differs from that specified in the class declaration).
譯:關於選擇性成員匯入匯出的一些重點值得我們關註:
· 選擇性成員匯入匯出最好用在為一個匯出的類介面提供一個更具限制的版本;即是說允許你設計一個DLL匯出比正常情況下語言允許的更少的公用或私人特性。這對於微調可匯出的介面也很有用:如果根據定義,客戶無法訪問一些私人資料,你沒必要匯出整個類。
· 如果你匯出一個類中的某一個虛函數,那你就必須把所有虛函數一併匯出,或至少提供使用者可以直接存取的版本。
· 如果你在一個類的虛函數上使用了選擇性成員匯入匯出,那麼這些函數必須是在可匯出介面中,或是內聯定義(對客戶可見)。
· 如果你將一個成員定義為dllexport,但沒有將定義包含在類的定義中,將產生一個編譯器錯誤。你必須在類的標頭檔中定義這個成員。
· 儘管允許將類成員定義為dllimport和dllexport,但你無法覆寫這個類的定義。
· 如果你沒有在聲明成員函數的類體定義處定義一個成員函數,並且此成員函數被定義為dllexport或dllimport,將產生一個警告(如果定義與在類中指定的聲明不同時)。
END Microsoft Specific
本文出自 “GDT解說” 部落格,轉載請與作者聯絡!
從普通DLL中匯出C++類 <一>