动态链接库--导出类(二)

写在前面

前面动态链接库(五)–导出类 中有介绍两种导出类的成员的方式, 即在类声明中指定导出声明或在指定成员前指定导出声明.

这种方式的缺点就是:

实例化对象的对象空间还是在使用者的模块里,dll只提供类中的函数代码. 因此使用者需要知道整个类的实现,包括基类、类中成员对象,也就是说所有跟导出类相关的东西,使用者都要知道.

通过dumpbin可以看到,这时候的dll导出的是跟类相关的函数:如构造函数、赋值操作符、析构函数、其它函数,这些都是使用者可能会用到的函数.

即这种导出类的方式,导出的东西太多、导致使用者对类的实现依赖也很多.

例: 在某个DLL1的导出类中, 有引用其他DLL2的变量, 函数或类, 那么实际使用时也需要提供DLL2甚至更多DLL的.h 和 lib文件.

最近在工作中学习到了另一种导出类的高效实现, 结构是这样的:

导出类是一个派生类,派生自一个抽象类——都是纯虚函数.

使用者需要知道这个抽象类的结构, DLL最少只需要提供一个用于获取类对象指针的接口.

使用者跟DLL提供者共用一个抽象类的头文件,使用者依赖于DLL的东西很少,只需要知道抽象类的接口,以及获取对象指针的导出函数,对象内存空间的申请是在DLL模块中做的,释放也在DLL模块中完成,最后记得要调用释放对象的函数.

导出纯虚类

定义纯虚类CDll1如下:

//CDll1.h
#pragma once

#ifdef CDLL1_EXPORTS
#define DLL1_API __declspec(dllexport)
#else
#define DLL1_API __declspec(dllimport)
#endif 

//纯虚类指出导出声明
class DLL1_API CDll1
{
public:
	static CDll1* CreateInstance();
	static void DestroyInstance(CDll1* pInstance);

	virtual void Destroy() = 0;
	virtual void DllTest() = 0;
};

源文件如下:

//CDll1.cpp
#include "CDll1.h"
#include 
#include 

#include "Dll1.h"	//派生类

CDll1* CDll1::CreateInstance()
{
	OutputDebugString(_T("\n CDll1::CreateInstance \n"));

	return new Dll1;	//这里new的是派生类的实例
}

void CDll1::DestroyInstance(CDll1* pInstance)
{
	OutputDebugString(_T("\n CDll1::DestroyInstance \n"));
	pInstance->Destroy();
}

派生类Dll1实现如下:

//Dll1.h
#pragma once

#include "CDll1.h"

//派生类无需指定导出声明
class Dll1 : public CDll1
{
public:
	Dll1();
	~Dll1();
	virtual void Destroy();

	virtual void DllTest();
};

源文件如下:

//Dll1.cpp
#include "Dll1.h"
#include 
#include 


Dll1::Dll1()
{
	OutputDebugString(_T("\n Dll1::Dll1 \n"));
}

Dll1::~Dll1()
{
	OutputDebugString(_T("\n Dll1::~Dll1 \n"));
}

void Dll1::Destroy()
{
	OutputDebugString(_T("\n Dll1::Destroy \n"));
	if (this != NULL)
	{
		delete this;
	}
}

void Dll1::DllTest()
{
	OutputDebugString(_T("\n Dll1::Dll1Test \n"));
}

关于纯虚类中的CDLL1_EXPORTS宏, 在项目属性的预处理器中指定:
动态链接库--导出类(二)_第1张图片

编译生成, 使用dumpbin命令查看Dll:
动态链接库--导出类(二)_第2张图片

有一些编译默认生成的构造析构, 以及一张虚表, 后续调用均通过虚表找到实际派生类函数, 这里并未实际导出声明的纯虚函数.

使用导出的纯虚类

将上面生成的DLL更新到使用项目中, 通过静态的CreateInstance接口创建对象, 调用成员函数, 最后别忘了通过全局的DestroyInstance接口销毁动态创建的对象.

#include 
#pragma comment(lib, "./Dll1.lib")
#include "CDll1.h"

int main()
{
    CDll1* pInstance = CDll1::CreateInstance();
    pInstance->DllTest();
    CDll1::DestroyInstance(pInstance);
    getchar();
}

编译运行可以看到内存有被释放:
动态链接库--导出类(二)_第3张图片
这里可以知道, 即使派生类没有指定导出声明, 也能够通过父类指针调用, 这得益于父类中导出的虚表.

因为父类中的成员函数都是纯虚函数, 且父类指针指向子类对象, 因此实际调用时会通过虚表找到子类的实现:
4

总结

在使用导出的纯虚类时, 实际调用借助了继承体系中的多态机制, 使用父类指针指向子类对象, 因此会调用子类的实现, 即使子类未导出, 因为这里的调用发生在DLL模块中 而不在 使用项目的模块中.

这种方式比较好,通用,产生的DLL没有特定环境限制. 借助了C++类的虚函数, 一般都是采用这种方式.

除了对DLL导出类有好处外,采用接口跟实现分离,可以使得工程的结构更清晰,使用者只需要知道接口,而不需要知道实现.

你可能感兴趣的:(动态链接库,C++,c++,开发语言)