龙盟编程博客 | 无障碍搜索 | 云盘搜索神器
快速搜索
主页 > 软件开发 > VC开发 >

经典编程:DLL地狱及其解决方案(4)

时间:2009-12-30 15:42来源:未知 作者:admin 点击:
分享到:
让我们来看下面的例子: // 如果想要测试改动过的DLL,请将下面的定义放开 //#define DLL_EXAMPLE_MODIFIED #ifdef DLL_EXPORT #define DLL_PREFIX __declspec(dllexport) #else #de
  让我们来看下面的例子:
  
  // 如果想要测试改动过的DLL,请将下面的定义放开
  //#define DLL_EXAMPLE_MODIFIED
  
  #ifdef DLL_EXPORT
   #define DLL_PREFIX __declspec(dllexport)
  #else
   #define DLL_PREFIX __declspec(dllimport)
  #endif
  
  /********** DLL的导出类 **********/
  #define CLASS_UIID_DEF static short GetClassUIID(){return 0;}
  #define OBJECT_UIID_DEF virtual short
   GetObjectUIID(){return this->GetClassUIID();}
  
  // 所有回调处理的基本接口
  struct DLL_PREFIX ICallBack
  {
   CLASS_UIID_DEF
   OBJECT_UIID_DEF
  };
  
  #undef CLASS_UIID_DEF
  
  #define CLASS_UIID_DEF(X) public: static
   short GetClassUIID(){return X::GetClassUIID()+1;}
  
  // 仅当DLL_EXAMPLE_MODIFIED宏已经定义的时候,进行接口扩展
  #if defined(DLL_EXAMPLE_MODIFIED)
  // 新增加的接口扩展
  struct DLL_PREFIX ICallBack01 : public ICallBack
  {
   CLASS_UIID_DEF(ICallBack)
   OBJECT_UIID_DEF
   virtual void DoCallBack01(int event) = 0; // 新的回调函数
  };

  #endif // defined(DLL_EXAMPLE_MODIFIED)
  
  class DLL_PREFIX CExample{
  public:
   CExample(){mpHandler = 0;}
   virtual ~CExample(){}
   virtual void DoCallBack(int event) = 0;
   ICallBack * SetCallBackHandler(ICallBack *handler);
   void Run();
  private:
   ICallBack * mpHandler;
  };
  
  很显然,为了给扩展DLL的导出类(增加新的虚函数)提供方便,我们必须做如下工作:
  1. 增加ICallBack * SetCallBackHandler(ICallBack *handler);函数;
  2. 在每个导出类的定义中增加相应的指针;
  3. 定义3个宏;
  4. 定义一个通用的ICallBack接口。
  
  为了演示给CExample类增加新的虚回调函数,我在这里增加了一个ICallBack01接口的定义。很显然,新的虚回调函数应该加在新的接口中。每次DLL更新都新增一个接口(当然,每次DLL更新时,我们也可以给一个类同时增加多个虚回调函数)。
  
  注意,每个新接口必须从上一个版本的接口继承。在我的例子中,我只定义了一个扩展接口ICallBack01。如果DLL再下个版本还要增加新的虚回调函数,我们可以在定义一个ICallBack02接口,注意ICallBack02接口要从ICallBack01接口派生,就跟当初ICallBack01接口是从ICallBack接口派生的一样。
  
  上面代码中还定义了几个宏,用于定义需要检查接口版本的函数。例如我们要为新接口ICallBack01增加新函数DoCallBack01,如果我们要调用ICallBack * mpHandler; 成员的话,就应该在CExample类进行一下检查。这个检查应该如下实现:
  
  if(mpHandler != NULL && mpHandler->GetObjectUIID()>=ICallBack01::GetClassUIID()){
   ((ICallBack01 *) mpHandler)->DoCallBack01(2);
  }
  
  我们看到,新回调接口增加之后,在CExample类的实现中只需简单地插入新的回调调用。
  
  现在你可以看出,我们上述对DLL的改动并不会影响客户应用程序。唯一需要做的,只是在采用这种新设计后的第一个DLL版本(为DLL导出类增加了宏定义、回调基本接口ICallBack、设置回调处理的SetCallBackHandler函数,以及ICallBack接口的指针)发布后,应用程序进行一次重编译。(以后扩展新的回调接口,应用程序的重新编译不是必需的!)
  
  以后如果有人想要增加新的回调处理,他就可以通过增加新接口的方式来实现(向上例中我们增加ICallBack01一样)。显然,这种改动不会引起任何问题,因为虚函数的顺序并没有改变。因此应用程序仍然以以前的方式运行。唯一你要注意的是,除非你在应用程序中实现了新的接口,否则你就接收不到新增加的回调调用。
  
  我们应该注意到,DLL的用户仍然能够很容易与它协同工作。下面是客户程序中的某个类的实现例子:
  
  // 如果DLL_EXAMPLE_MODIFIED没有定义,使用以前版本的DLL
  #if !defined(DLL_EXAMPLE_MODIFIED)
  // 此时没有使用扩展接口ICallBack01
  class CClient : public CExample{
  public:
   CClient();
   void DoCallBack(int event);
  };
  
  #else // !defined(DLL_EXAMPLE_MODIFIED)
  // 当DLL增加了新接口ICallBack01后,客户程序可以修改自己的类
  // (但不是必须的,如果他不想处理新的回调事件的话)
  class CClient : public CExample, public ICallBack01{
  public:
   CClient();
   void DoCallBack(int event);
  
   // 声明DoCallBack01函数(客户程序要实现它,以处理新的回调事件)
   // (DoCallBack01是ICallBack01接口新增加的虚函数)
   void DoCallBack01(int event);
  };
  #endif // defined(DLL_EXAMPLE_MODIFIED)
  
  例程 ---> 代码下载(6.26K)
  
  与本文的内容配套,我提供了演示程序Dll_Hell_Solution。
  
  1. Dll_example: DLL的实现项目;
  2. Dll_Client_example: DLL的客户应用程序项目。
  
  注意:目前Dll_Hell_Solution/Dll_example/dll_example.h文件中的DLL_EXAMPLE_MODIFIED定义被注释掉了。如果放开这个注释,可以生成更新后的DLL版本;然后可以再次测试客户应用程序。
  
  为了保证读者能够正常演示,请遵循如下步骤:
  1. 不要改动任何代码(此时DLL_EXAMPLE_MODIFIED没有定义)编译Dll_example和Dll_Client_example两个项目。运行客户程序,体验最初的情况。
  2. 放开DLL_EXAMPLE_MODIFIED的注释,然后重新编译Dll_example。重新运行客户程序(此时使用了新版本的DLL),应该仍然运行正常。
  3. 重新编译Dll_Client_example,生成新的客户程序。我们看到新增加的回调函数被调用了!

精彩图集

赞助商链接