DLL接口的实现(虚函数)
我们在c++编程过程中往往要用到各种不同形式的程序库,这些库的发布方式有动态库和静态库。对于静态类库,设计良好的静态类库能实现功能上的隔离,无法避免类库实现必须重新编译、链接整个应用程序的问题。而调用各种DLL动态库成为我们程序员的家常便饭。
以什么方式暴露库的接口?可选的做法有:以全局(含 namespace 级别)函数为接口、以 class 的 non-virtual 成员函数为接口、以 virtual 函数为接口(interface)。本文主要讲虚函数实现DLL接口。
虚函数实现接口的做法是定义一个头文件,将类实现的接口声明,可以将这个类的成员函数暴露给客户。而接口的实现部分是在派生类中,用户获得头文件中的接口,无需接口的实现就可以调用接口。往往我们会将这个接口类的成员定义为纯虚函数,这样更直观的体现派生类必须完成对接口的实现部分。
以下是代码实例:
接口头文件 IMPerson.h
#include #include using namespace std;#ifdef _EXPORTING
#define CLASS_DECLSPEC __declspec(dllexport)
#else
#define CLASS_DECLSPEC __declspec(dllimport)
#endif
CLASS_DECLSPEC bool GetIPersonObject(void** _RtObject);
class IMPerson: public CObject
{
public:
IMPerson(){};
//接口
virtual void SetName(const string &strName) = 0;
virtual const string GetName() = 0;
virtual void Work() = 0;
};
__declspec(dllexport)用于导出符号,也就是定义该函数的dll;__declspec(dllimport)用于导入,也就是使用该函数。因为这个头文件既要被定义该函数的dll包含,也要被使用该函数的程序包含,当被前者包含时我们希望使用__declspec(dllexport)定义函数,当被后者包含时我们希望使用dllimport。于是我们使用
#ifdef _EXPORTING
#define CLASS_DECLSPEC __declspec(dllexport)
#else
#define CLASS_DECLSPEC __declspec(dllimport)
#endif
这种技巧,在定义该函数的dll中,其编译选项定义了_EXPORTING而使用该函数的程序则没有定义。反正我们的头文件是要暴露给用户的,这样定义可以保持DLL库头文件和用户库头文件保持一致。
接口实现部分:CTeacher.cpp
#include "IMPerson.h"//定义接口
//CLASS_DECLSPEC bool GetIPersonObject(void** _RtObject);
class CTeacher:public IMPerson
{
public:
CTeacher(){}
//接口实现
void SetName(const string &strName);
const string GetName();
void Work();
private:
string m_strName;
};
void CTeacher::SetName(const string &strName)
{
m_strName = strName;
}
const string CTeacher::GetName()
{
return m_strName;
}
void CTeacher::Work()
{
cout<<“I am teaching!”<<endl;
}
bool GetIPersonObject(void *_RtObject)
{
IPerson pMan = NULL;
pMan = new CTeacher();
_RtObject = (void)pMan;
return true;
}
这样,我们在工程->属性->配置属性->常规->配置类型 中选择 动态库(.dll)运行就可以生成对应的.dll和.lib。我们在客户程序中就可以使用这个dll。具体做法如下:1.程序中包含接口的头文件,即 IMPerson.h 2.包含lib,在 工程->属性->配置属性->VC++目录->库目录中加入.lib的文件目录,在工程->属性->配置属性->连接器->输入->附加依赖项中加入.lib 。
main.cpp
#include "IMPerson.h"#pragma comment(lib,“InterFace.lib”)
//导出接口
bool CLASS_DECLSPEC GetIMPersonObject(void** _RtObject);
void main()
{
IMPerson * _IPersonObj = NULL;
void* pObj=NULL;
if (GetIMPersonObject(&pObj))
{
// 获取对象
_IPersonObj = (IMPerson *)pObj;
// 调用接口,执行操作
_IPersonObj->SetName(“Tom”);
string strName = _IPersonObj->GetName();
_IPersonObj->Work();
}
if (_IPersonObj !=NULL)
{
delete _IPersonObj ;
_IPersonObj = NULL;
}
}
; 动态加载DLL步骤如下:
1.HINSTANCE m_hDll = LoadLibrary(L"InterFacel.dll"); 。
2. typedef int (*GetIMPersonObject)(void** _RtObject);
3. GetIMPersonObject m_GetIMPersonObject =(GetIMPersonObject)GetProcAddress(m_hDll,"GetIMPersonObject");4. FreeLibrary(m_hDll);
5. IPerson * _IPersonObj = NULL;
6. void* pObj=NULL;
7. if (m_GetIMPersonObject(&pObj))
8. FreeLibrary(m_hDll);
到这里,一个简单的虚函数实现DLL接口就做完了。有一点必须要说,设计库的时候C++ 虚函数为接口是有弊端的。“一旦发布,不能修改”。a)函数重名问题:我们通过函数名来调用DLL的函数,在并行开发中 容易造成函数重名。b)依赖:如果采用常见的隐式连接,那DLL每发行了一个新版本都有 必要和应用程重新链接一次,因为DLL里面函数的地址可能已经发生了改变。
----------------------------------------------------------------------------------------------------------------------------
新增智能指针调用方式
使用智能指针:
在纯c的方式导出类中,开发人员必须通过显式函数调用来释放资源,为了确保能够将资源释放,这里使用标准 C++ 库提供的智能指针。
#include "IMPerson.h"#pragma comment(lib,“InterFace.lib”)
typedef std::shared_ptr IMPersonPtr;
IMPersonPtr ptrPerson(::GetIMPersonObject(), std::mem_fn(&IMPerson ::Release));
if(ptrPerson)
{
ptrPerson->Work();
}
优点:
导出的C++类可以通过抽象接口与任何C++编译器一起使用,这是因为抽象接口作为模块之间的接口采用的是COM技术,该技术可以与其他编译器一起使用。 实现了真正的模块分离,可以重新设计和重新生成DLL模块,而不会影响其他部分。 这种导出方法和智能指针一起使用与任何其他C++类的用法几乎相同,推荐使用这种方法。
缺点:创建对象的时候将其删除必须显示函数调用,但是可以用智能指针来解决。抽象接口方法不能将C++对象作为入参或者返回值。
本文详细介绍了如何使用C++的虚函数设计DLL接口,并探讨了使用智能指针来管理内存的利弊。重点讲解了导出接口、接口的实现以及使用虚函数避免函数重名和依赖问题。同时提到了智能指针带来的模块化优势和限制。

被折叠的 条评论
为什么被折叠?



