http://hi.baidu.com/atry/blog/item/2b57573dbc1f20ee3c6d9752.html
让动态链接库真正的动态其实是一件很麻烦的事情。事实上,可以称得上“动态”的函数一共就只有两种,即全局函数和纯虚函数。所以我们有三种做法:
第一种做法,对于接口,不使用任何类,所有导出函数都用extern "C"的全局函数。
第二种做法就是使用COM,COM做了我这篇文章所说的几乎所有的事情,除此之外还顺便把线程同步这些烦人的事情也一并解决了。
第三种做法,导出一个全局函数,让这个函数返回一个纯虚类的接口,然后让这个接口作为工厂创建的别的接口,所有的接口都是纯虚类,不允许使用new来创建对象,也不允许在栈上分配对象。
对于这样的接口类,其构造函数应该是protected的,析构函数则是一个virtual的public函数,构造函数和析构函数的函数体不允许=0,变通的解决办法是简单的加上一个不执行任何代码的{}函数体
此外还有DLL中导出变量的问题。我的观点是旗帜鲜明地反对,更进一步的说,我反对任何DLL中的全局变量和静态变量,不管是不是要导出 的。我所认同的正确做法是放到上述所说的放到那个唯一的全局函数所返回的接口的实现类的成员里面。也就是下面代码的ServiceEntry_Impl 中。
由于DLL是依赖于文件名的,所以很有可能一个进程中存在很多不同的CRT,而且它们之间的malloc/free,new /delete不能通用,有一个变通的解决方案就是让每一个DLL中的每一个类都用它自己的CRT来管理内存,C++提供给我们一个手段就是重载 delete,因为上面已经说了,我们不允许new,接口的构造函数都是protected,所以实际上不用考虑数组分配的问题,只需要重载delete 就行了,但是,为了解决导出函数的函数名的问题,这是为了跨编译器,我们的导出函数必须是一个全局函数,所以我们还需要导出一个相当于delete功能的 全局函数,然后让我们再写一个基类,让这个基类有一个内联的重载的delete,而这个delete去调用我们导出的那个全局函数。
不要嫌麻烦,事实上,微软自己也没有更好的解决办法,微软的GDI+库的所有导出函数就都是全局函数,然后又把所有的类都写了一个inline的包装去调用那些全局函数。我们这里只不过是包装一个delete而已,别的函数我们用纯虚函数就不用包装了。
最后,别忘了用namespace,这能让你可以放心的用简短的类名而不用担心冲突。
上述这些要求的具体代码类似这样,代码中的#include之类的都被我省略了:
// 在头文件中这样做:
namespace myspace
{
extern "C" XXX_API void DllDelete(void*); //导出的C风格的delete,他的实现就是简单的调用dll中的CRT,这样保证DLL分配的内存由DLL释放
class Allocateable // 所有的接口类都应该从这个类派生,不用考虑菱形继承的问题,Allocateable并没有真的存在任何数据成员
{
public:
operator delete(void* p){DllDelete(p);}
};
class XXX : public Allocateable
{
protected:
XXX(){};
public:
virtual ~XXX(){};
virtual void Foo() = 0;
virtual void Bar() = 0;
};
class ServiceEntry : public Allocateable
{
protected:
ServiceEntry(){};
public:
virtual ~ServiceEntry(){};
virtual XXX* CreateXXX() = 0;
virtual YYY* CreateYYY() = 0;
};
extern "C" XXX_API ServiceEntry* NewServiceEntry(); // 这是第二个导出函数,用户使用所有的功能都得从这里入手。
}
// 在cpp文件里这样做:
namespace myspace
{
void DllDelete(void*p)
{
::operator delete(p);
}
ServiceEntry* NewServiceEntry();
{
return new ServiceEntry_Impl();
}
}
// 此外你还需要写具体的实现:应该放到不同的文件中
// 对于ServiceEntry_Impl,要派生自ServiceEntry并实现那些成员函数
namespace myspace
{
class ServiceEntry_Impl : public ServiceEntry
{
public:
virtual XXX* CreateXXX()
{
return new XXX_Impl();// 类似于ServiceEntry_Impl,XXX_Impl也需要派生自XXX,并且你得实现XXX的接口。
}
virtual YYY* CreateYYY()
{
return new YYY_Impl();// 同上
}
};
}