分享

DLL接口的实现(虚函数)

 imelee 2017-09-25

       我们在c++编程过程中往往要用到各种不同形式的程序库,这些库的发布方式有动态库和静态库。对于静态类库,设计良好的静态类库能实现功能上的隔离,无法避免类库实现必须重新编译、链接整个应用程序的问题。而调用各种DLL动态库成为我们程序员的家常便饭。

      以什么方式暴露库的接口?可选的做法有:以全局(含 namespace 级别)函数为接口、以 class 的 non-virtual 成员函数为接口、以 virtual 函数为接口(interface)。本文主要讲虚函数实现DLL接口和COM组件。

      虚函数实现接口的做法是定义一个头文件,将类实现的接口声明,可以将这个类的成员函数暴露给客户。而接口的实现部分是在派生类中,用户获得头文件中的接口,无需接口的实现就可以调用接口。往往我们会将这个接口类的成员定义为纯虚函数,这样更直观的体现派生类必须完成对接口的实现部分。

以下是代码实例:

接口头文件 IPerson.h

  1. #include<iostream>  
  2. #include<string>  
  3. using namespace std;  
  4.   
  5. #ifdef _EXPORTING  
  6. #define CLASS_DECLSPEC __declspec(dllexport)  
  7. #else  
  8. #define CLASS_DECLSPEC __declspec(dllimport)  
  9. #endif  
  10.   
  11. class IPerson  
  12. {  
  13. public:  
  14.     IPerson(){};  
  15.   
  16.     //接口  
  17.     virtual void  SetName(const string &strName) = 0;  
  18.     virtual const string GetName() = 0;  
  19.     virtual void  Work() = 0;  
  20. };  


       __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

  1. #include "IPerson.h"  
  2.   
  3. //定义接口  
  4. CLASS_DECLSPEC bool GetIPersonObject(void** _RtObject);  
  5.   
  6. class CTeacher:public IPerson  
  7. {  
  8. public:  
  9.     CTeacher(){}  
  10.     //接口实现  
  11.      void  SetName(const string &strName);  
  12.     const string GetName();  
  13.     void  Work();  
  14. private:  
  15.     string m_strName;  
  16. };  
  17.   
  18. void CTeacher::SetName(const string &strName)  
  19. {  
  20.     m_strName = strName;  
  21. }   
  22. const string CTeacher::GetName()  
  23. {  
  24.     return m_strName;  
  25. }  
  26. void CTeacher::Work()  
  27. {  
  28.     cout<<"I am teaching!"<<endl;  
  29. }  
  30.   
  31. bool GetIPersonObject(void **_RtObject)  
  32. {  
  33.     IPerson* pMan = NULL;  
  34.     pMan =  new CTeacher();  
  35.     *_RtObject = (void*)pMan;  
  36.     return true;  
  37. }  

          这样,我们在工程->属性->配置属性->常规->配置类型 中选择 动态库(.dll)运行就可以生成对应的.dll和.lib。我们在客户程序中就可以使用这个dll。具体做法如下:1.程序中包含接口的头文件,即 IPerson.h      2.包含lib,在 工程->属性->配置属性->VC++目录->库目录中加入.lib的文件目录,在工程->属性->配置属性->连接器->输入->附加依赖项中加入.lib  。

main.cpp

  1. #include "IPerson.h"  
  2.   
  3. #pragma comment(lib,"InterFace.lib")  
  4.   
  5. //导出接口  
  6. bool CLASS_DECLSPEC GetIPersonObject(void** _RtObject);  
  7.   
  8. void main()  
  9. {  
  10.     IPerson * _IPersonObj = NULL;  
  11.     void* pObj=NULL;  
  12.     if (GetIPersonObject(&pObj))          
  13.     {  
  14.         // 获取对象  
  15.         _IPersonObj = (IPerson *)pObj;  
  16.         // 调用接口,执行操作  
  17.         _IPersonObj->SetName("Tom");  
  18.             string strName = _IPersonObj->GetName();  
  19.         _IPersonObj->Work();  
  20.     }  
  21.     if (_IPersonObj !=NULL)  
  22.     {  
  23.         delete _IPersonObj ;  
  24.         _IPersonObj  = NULL;  
  25.     }  
  26. }  


输出如下:

 

         到这里,一个简单的虚函数实现DLL接口就做完了。有一点必须要说,设计库的时候C++ 虚函数为接口是有弊端的。“一旦发布,不能修改”。a)函数重名问题:我们通过函数名来调用DLL的函数,在并行开发中 容易造成函数重名。b)依赖:如果采用常见的隐式连接,那DLL每发行了一个新版本都有 必要和应用程重新链接一次,因为DLL里面函数的地址可能已经发生了改变。

        COM组件的思想正好解决了上述问题,下一节讲COM组件思想的简单实现。

 

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多