溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

從普通DLL中導(dǎo)出C++類 <一>

發(fā)布時間:2020-06-19 20:15:35 來源:網(wǎng)絡(luò) 閱讀:2481 作者:844133395 欄目:編程語言

    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屬性。這兩個形式將隱含導(dǎo)入或?qū)С稣麄€類。通過這種方法導(dǎo)出的類稱為可導(dǎo)出類。
    下列范例定義了一個可導(dǎo)出類,其所有的成員函數(shù)和靜態(tài)將被導(dǎo)出:

#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.
    譯:注意,禁止在一個可導(dǎo)出類的成員上顯式的使用dllimport和dllexport屬性。
    balon注:如你不能像下例這樣寫:

#define DllExport   __declspec( dllexport )
 
class DllExport C {
   DllExport int i;    // 不可以在成員上使用dllexport
   DllExport int func( void ) { return 1; } // 不可以在成員上使用dllexport
};


dllexport Classes
通過dllexport導(dǎo)出類

    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.
    譯:當(dāng)你聲明一個類為dllexport,其所有的成員函數(shù)和靜態(tài)數(shù)據(jù)成員將被導(dǎo)出。你必須在同一個程序中定義所有此類成員,否則會產(chǎn)生一個鏈接錯誤。
    balon注:如在下面的導(dǎo)出類中,func必須在這個DLL的工程中或是在使用DLL的程序中被定義,否則在使用時會出現(xiàn)無法解析的外部符號的鏈接錯誤。注意,定義可以放在DLL中,也可放在DLL的使用者工程中,只要在使用前被定義就可以。
方法一:在Dll工程中定義,在使用者工程中直接使用。
// dll.h

#define DllExport   __declspec( dllexport )
class DllExport C {
    int func( void );
}; 
// dll.cpp
int C::func(void)
{
    return 1;
}

 
方法二:在DLL工程中只有一個聲明,沒有定義??梢栽谑褂谜呤褂们敖o出該函數(shù)的定義。另外,如果你在使用者程序中根本就沒有用到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;
}


    這個規(guī)則的唯一例外是對純虛函數(shù)你可以不用提供顯示的定義。
    balon注:如下面例子中的純虛函數(shù)func,可以不必提供定義。因為這個導(dǎo)出的類可能本身就是一個抽象類,func就是一個沒有實現(xiàn)的函數(shù),當(dāng)然可以不提供定義。 

    但是,由于抽象類的析構(gòu)函數(shù)總是會被基類的析構(gòu)函數(shù)調(diào)用的,因此純虛析構(gòu)函數(shù)必須提供一個定義。這條規(guī)則對于不可導(dǎo)出的類同樣適用。
    balon注:根據(jù)我的試驗結(jié)果,將各種情況下的編譯結(jié)果列了一個表:
    DLL工程中沒有對應(yīng)函數(shù)定義的編譯結(jié)果 客戶程序使用情況 
成員函數(shù) 正常鏈接。 可以定義此類的實例,不調(diào)用此成員函數(shù)就正常鏈接;調(diào)用此成員函數(shù)鏈接出錯,提示此函數(shù)為未決的外部符號。 
    虛成員函數(shù) 鏈接出錯,提示此函數(shù)為未決的外部符號。 - 
    純虛成員函數(shù) 正常鏈接。 不能定義此類實例,編譯出錯,提示無法實例化一個抽象類。 
    析構(gòu)函數(shù) 正常鏈接。 鏈接出錯,提示析構(gòu)函數(shù)為未決的外部符號 
    虛析構(gòu)函數(shù) 鏈接出錯,提示析構(gòu)函數(shù)為未決的外部符號。 - 
    純虛析構(gòu)函數(shù) 鏈接出錯,提示析構(gòu)函數(shù)為未決的外部符號。 - 

    可見文檔中說的規(guī)則屬實,純虛析構(gòu)函數(shù)被當(dāng)作虛析構(gòu)函數(shù)對待。但另一個問題是,為什么虛函數(shù)(包括析構(gòu)函數(shù))就一定要在其聲明所在的工程中被定義,而普通函數(shù)就不必呢?我分析原因估計如下:
    我們知道,C++類如果有虛函數(shù)的話(包括其基類有虛函數(shù)的情況),C++編譯器會在編譯時為其生成一個虛表,并在構(gòu)造函數(shù)中,將對應(yīng)的虛表首地址填到類實例的虛表指針成員中。如果一個虛函數(shù)在派生體系中被多次實現(xiàn),虛表中填入的是最底層(most-derived)實現(xiàn)。這個填表過程是,首先基類對象被構(gòu)造,其構(gòu)造函數(shù)先填入基類的虛表實現(xiàn)入口(這就是為什么,在構(gòu)造函數(shù)中調(diào)用虛函數(shù),只會調(diào)用到當(dāng)前類層次的實現(xiàn),無法調(diào)用到派生類的實現(xiàn)的原因),接著派生類被構(gòu)造,其構(gòu)造函數(shù)將派生類的虛表入口填入,覆蓋掉剛才基類記錄的入口地址。這個過程一直進行,直到整個對象構(gòu)造完成。
    說了這么多,其實其中最關(guān)鍵的一點就是:構(gòu)造函數(shù)必須知道當(dāng)前類的虛表入口地址,而這就需要知道虛表里填寫的所有虛函數(shù)的入口地址!否則,編譯器將無法生成構(gòu)造函數(shù)的填虛表的功能。于是,編譯器只好開始抱怨了。
因此,所有的虛函數(shù)必須在其聲明所在的工程中有定義,在鏈接時能正確找到其入口地址以便編譯器可以生成構(gòu)造函數(shù)的填虛表功能。 

    如果你導(dǎo)出一個類類型的數(shù)據(jù)成員,或是一個返回類的函數(shù),請確保導(dǎo)出那個類。
balon注:這個是一個指導(dǎo)原則,不是一個強制的編譯鏈接要求。只要你不使用返回的類的成員函數(shù),并且返回類沒有虛表,可以不必導(dǎo)出類。但多數(shù)情況下是要導(dǎo)出的,所以作為一個好的原則,按MSDN的建議做就好了。在本文的下一篇中將對此有詳細介紹。 

dllimport Classes
通過dllimport導(dǎo)入類
    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.
    譯:當(dāng)你將一個類聲明為dllimport,其所有的成員函數(shù)和靜態(tài)數(shù)據(jù)成員將被導(dǎo)入。與在非類類型上使用dllimport和dllexport不同的是,不能在有dllimport類的定義的同一個程序中指給出靜態(tài)數(shù)據(jù)成員的定義。
balon注:這句話譯的有些拗口,原文也好不到哪里。其實看了通過dllexport導(dǎo)出類一節(jié)的注解,就好理解這里想要說的意思了。意思就是:靜態(tài)數(shù)據(jù)成員不能像其它成員函數(shù)那樣,可以在使用者工程中定義,而不在DLL本身工程中定義。
方法一、按要求在DLL中定義靜態(tài)數(shù)據(jù)成員:
// 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類的定義的同一個程序中指給出靜態(tài)數(shù)據(jù)成員的定義”,則在客戶程序編譯時 出現(xiàn)編譯錯誤:
// 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
繼承與可導(dǎo)出類
    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.
    譯:一個可導(dǎo)出類的所有基類都必須可導(dǎo)出,否則會產(chǎn)生一個編譯錯誤。
    balon注:事實上,這條規(guī)則也不是一定的,在滿足一定的條件情況下,基類不可導(dǎo)出程序也是正常的。當(dāng)然,多數(shù)情況下還是要導(dǎo)出的,建議還是按MSDN的要求做好了。在本文的下一篇中將對此有詳細介紹。
如下例中,A是一個可導(dǎo)出類,則A的基類Base也應(yīng)當(dāng)是一個可導(dǎo)出類。

#define DllExport   __declspec( dllexport )
class DllExport A : public Base
{
    // ...
};


 此外,所有可訪問的類類型成員也必須是可導(dǎo)出的。這條規(guī)則允許了一個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
{
    // ...
};


    結(jié)果就是,這個DLL的客戶可訪問的所有東西(依照C++的訪問規(guī)則)都應(yīng)當(dāng)是導(dǎo)出接口的一部分。包括被inline函數(shù)引用的私有成員。
    balon注:這句話其實是全文中最重要的一句話,其實這篇文檔如果把這句話展開說清楚了,也不用我在這里寫這篇文章了。在本文的下一篇中將有對于這句話的深入討論 

Selective Member Import/Export
選擇性成員導(dǎo)入導(dǎo)出

譯: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).
    譯:因為類中的成員函數(shù)和靜態(tài)數(shù)據(jù)隱含進行外部鏈接,你可以在沒有將整個類導(dǎo)出的情況下,在他們聲明中加上dllimport或是dllexport屬性。如果整個類被導(dǎo)入或是導(dǎo)出,將不允許顯式的以dllimport和dllexport對成員函數(shù)和數(shù)據(jù)進行聲明。如果你將類中的一個靜態(tài)數(shù)據(jù)成員聲明為dllexport,在同一個程序中的某個地方應(yīng)當(dāng)有它的定義(如同非類外部鏈接那樣)。
    balon注:前面幾句很好理解。最后一句實際上是在說,你可以把導(dǎo)出一個靜態(tài)數(shù)據(jù)成員,當(dāng)作與一個從DLL中導(dǎo)出一個非類成員的普通變量那樣對待,要在DLL所在工程中有定義。導(dǎo)出一個普通變量方法就是在DLL中的某一個CPP文件中定義此變量,并加上dllexport聲明:
// dll.cpp

__declspec( dllexport ) int x = 0;


    那么,對比一下將一個類的靜態(tài)數(shù)據(jù)成員導(dǎo)出的方法:
// dll.h

#define DllExport   __declspec( dllexport )
class A // 注意,這里沒有導(dǎo)出類A
{
public:
    DllExport static int x; // 所以這里才可以導(dǎo)出個別成員
};  // 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.
譯:類似的,你也可以為一個成員函數(shù)聲明加上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). 
譯:關(guān)于選擇性成員導(dǎo)入導(dǎo)出的一些重點值得我們關(guān)注:
·         選擇性成員導(dǎo)入導(dǎo)出最好用在為一個導(dǎo)出的類接口提供一個更具限制的版本;即是說允許你設(shè)計一個DLL導(dǎo)出比正常情況下語言允許的更少的公共或私有特性。這對于微調(diào)可導(dǎo)出的接口也很有用:如果根據(jù)定義,客戶無法訪問一些私有數(shù)據(jù),你沒必要導(dǎo)出整個類。
·         如果你導(dǎo)出一個類中的某一個虛函數(shù),那你就必須把所有虛函數(shù)一并導(dǎo)出,或至少提供用戶可以直接訪問的版本。
·         如果你在一個類的虛函數(shù)上使用了選擇性成員導(dǎo)入導(dǎo)出,那么這些函數(shù)必須是在可導(dǎo)出接口中,或是內(nèi)聯(lián)定義(對客戶可見)。
·         如果你將一個成員定義為dllexport,但沒有將定義包含在類的定義中,將產(chǎn)生一個編譯器錯誤。你必須在類的頭文件中定義這個成員。
·         盡管允許將類成員定義為dllimport和dllexport,但你無法覆寫這個類的定義。
·         如果你沒有在聲明成員函數(shù)的類體定義處定義一個成員函數(shù),并且此成員函數(shù)被定義為dllexport或dllimport,將產(chǎn)生一個警告(如果定義與在類中指定的聲明不同時)。
END Microsoft Specific


向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI