科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件DLL“地狱”的原因及其解决方案

DLL“地狱”的原因及其解决方案

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文将要介绍DLL的向后兼容性问题,也就是著名的“DLL Hell”问题

作者:陆其明 来源:blog 2007年10月19日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
COM及其它

  现在可以看出,DLL的向后兼容性问题是一个很出名的问题。解决这些问题,不仅可以借助于一些约定,而且可以通过其它一些先进的技术,比如COM技术。因此,如果你想摆脱“DLL Hell”问题,请使用COM技术或者其它一些合适的技术。

  让我们回到我接受的那个任务(我在本文开头的地方讲到的那个任务)————解决一个使用DLL的产品的向后兼容性问题。

  我对COM有些了解,因此我的第一个建议是使用COM技术来克服那个项目中的所有问题。但这个建议因为如下原因最终被否决了:

  1. 那个产品已经在某个内部层中有一个COM服务器。

  2. 将一大堆接口类重写到COM的形式,投入比较大。

  3. 因为那个产品是DLL库,而且已经有很多应用程序在使用它了。因此,他们不想强制他们的客户重写他们的应用程序。

  换句话说,我被要求完成的任务是,以最小的代价来解决这个DLL向后兼容性问题。当然,我应该指出,这个项目最主要的问题在于增加新的成员和接口类上的虚回调函数。第一个问题可以简单地通过在类声明中增加一个指向一个数据结构的指针来解决(这样可以任意增加新的成员)。这种方法我在上面已经提到过。但是第二个问题,虚回调函数的问题是新提出的。因此,我提出了下面的最小代价、最有效的解决方法。

  虚回调函数与继承

  然我们想象一下,我们有一个DLL,它导出了几个类;客户应用程序会从这些导出类派生新的类,以实现虚函数来处理回调事件。我们想在DLL中做一个很小的改动。这个改动允许我们将来可以给导出类“无痛地”增加新的虚回调函数。同时,我们也不想影响使用当前版本DLL的应用程序。我们期望的就是,这些应用程序只有在不得已的时候才协同新版本的DLL进行一次重新编译。因此,我给出了下面的解决方案:

  我们可以保留DLL导出类中的每个虚回调函数。我们只需记住,在任何一个类定义中增加一个新的虚函数,如果应用程序不协同新版本的DLL重新编译,将导致严重的问题。我们所做的,就是想要避免这个问题。这里我们可以一个“监听”机制。如果在DLL导出类中定义并导出的虚函数被用作处理回调,我们可以将这些虚函数转移到独立的接口中去。

  让我们来看下面的例子:

// 如果想要测试改动过的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)

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章