您好,登錄后才能下訂單哦!
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.cpp int C::func(void) { return 1; }
方法二:在DLL工程中只有一個聲明,沒有定義。可以在使用者使用前給出該函數的定義。另外,如果你在使用者程序中根本就沒有用到func,可以不提供其定義,不會出鏈接錯誤。
// dll.h
#define DllExport __declspec( dllexport ) class DllExport C { int func( void ); }; // client.cpp int 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.cpp int 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.cpp int 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
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。