从普通DLL中导出C++类

(转)Balon白话MSDN:从普通DLL中导出C++类(1) – dllexport和dllimport的使用方法(中英对照、附注解)

转于:http://blog.csdn.net/balonfan/archive/2007/05/09/1602294.aspx

Balon白话MSDN:从普通DLL中导出C++类(1) – dllexport和dllimport的使用方法(中英对照、附注解)

这几天写几个小程序练手,在准备将一个类导出时,发现还真不知道如果不用MFC的扩展DLL,是怎么导出的。但我知道dllexport可以导出函数和变量,而且MFC扩展DLL就算是使用了MFC的功能,但能否导出类应该也不是必须用MFC才能够做到,一定是有相应的机制可以实现。于是查了一下MSDN,发现这个机制简单的可怕,原来就和导出函数一样,把dllexport关键字加到类名前就可以了。估计和我一样的同学大有人在,把MSDN的相关文档翻译出来,附上我的注解,希望对大家有用。
评注程序均在Visual Studio.NET 2005 Professional中测试以得到结果,给出的代码均省略了大量语句,只保留了与主题最相关的部分,因此可能无法编译。
MSDN文档选自2007年4月更新的MSDN Library。
Using dllimport and dllexport in C++ Classes
C++类中使用dllimportdllexport
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++类时使用dllimportdllexport属性。这两个形式将隐含导入或导入整个类。通过这种方法导出的类称为可导出类。
下列范例定义了一个可导出类,其所有的成员函数和静态将被导出:
#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.
注意,禁止在一个可导出类的成员上显式的使用dllimportdllexport属性。
balon 注:如你不能像下例这样写:
#define DllExport    __declspec( dllexport )

class DllExport C {
    DllExport
int i;     // 不可以在成员上使用dllexport
    DllExport int func( void ) { return 1 ; } // 不可以在成员上使用dllexport
};
通过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,其所有的成员函数和静态数据成员将被导入。与在非类类型上使用dllimportdllexport不同的是,不能在有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 ;
}



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值