65.9K
CodeProject 正在变化。 阅读更多。
Home

PDL 简介

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.14/5 (6投票s)

2007年9月14日

BSD

6分钟阅读

viewsIcon

69607

downloadIcon

147

可移植动态加载器

什么是 PDL?

PDL(Portable Dynamic Loader,便携式动态加载器)是一个轻量级、简洁且便携的库,专门用于创建和使用动态加载的类对象。

为什么我们需要动态加载类?

动态加载类技术的主要目的是创建插件,以扩展主程序的功能。在许多平台上,动态加载模块的主要问题是它们只支持过程式编程范式。当您尝试加载类时,会出现许多问题。PDL 库解决了其中大部分问题(但可惜并非全部)。

在 Win32 平台上,PDL 是 COM 技术的一个非常简单的替代方案,它没有引用计数、全局类注册以及许多其他功能。在 Unix/Linux 平台上,有几个类似的库,例如 C++ Dynamic Class Loader。大型跨平台库 WxWidgets 也提供了动态类加载支持功能。

PDL 开发的主要目标是创建一个跨平台库,能够为 Win32 和 Unix/Linux 平台提供单一的动态类加载机制(与 COM 和 C++ Dynamic Class Loader 不同)。该库还应该是轻量级的且独立的(与庞大的 WxWidgets 不同)。

创建动态加载类

让我们详细介绍使用 PDL 库创建动态加载类的过程。首先,我们需要声明一个接口,该接口将用于处理可加载类的实例。
一个不可或缺的条件是,该接口必须继承自 PDL::DynamicClass。让我们看看 DynamicClass 的声明。

class DynamicClass
{
public:
    /**
     * @brief Get class name
     * return class name
     */ 
    virtual const char * GetClassName() const throw() = 0;   

    /**
     * @brief Destroy class instance
     */
    void Destroy() throw() { delete this; } 

protected: 
    /**
     * @brief Destructor
     */
    virtual ~DynamicClass() throw() { ;; }
};

纯虚函数 GetClassName() 返回类的名称。您不必担心它的定义,稍后我将解释原因。

非虚函数 Destroy() 用于销毁类的实例。类析构函数被声明为 protected,以防止直接调用。稍后我将描述为什么需要这种技巧。

根据 PDL 的理念,我们必须继承我们的接口并定义 protected 虚析构函数。

我们还需要在类声明内部插入 DECLARE_DYNAMIC_CLASS 宏,并将类名作为参数。如果我们查看此宏的定义,会发现它只是定义了虚方法 GetClassName()

#define DECLARE_DYNAMIC_CLASS( className ) \
public: \ 
    virtual const char * GetClassName() const throw() 
    {
        return #className; 
    } 

最后,让我们为接口添加纯虚方法,以实现动态加载类的有用功能。例如,我们添加 DoSomething() 方法。

因此,我们得到了以下接口。

#include <DynamicClass.hpp>


class MyTestInterface : public PDL::DynamicClass
{ 
public:
    /**
     * @brief Test method
     */
    virtual void DoSomething() throw() = 0;

    /**
     * @brief Declare this class dynamically loadable
     */
    DECLARE_DYNAMIC_CLASS( MyTestInterface )
};

我们应该将此声明放在一个单独的头文件中,在本例中是 MyTestInterface.hpp。为了兼容性,构建动态加载类及其直接使用时都必须包含此文件。

然后,我们应该声明一个继承自抽象接口 MyTestInterface 的类,并定义实现其有用功能的类方法。我们还需要使用 EXPORT_DYNAMIC_CLASS 宏来导出该类。请注意,此宏应放置在类声明之外。

#include <MyTestInterface.hpp>

#include <stdio.h>


class MyTestClass1 : public MyTestInterface
{
public:
    /**
     * @brief Test method
     */
    void DoSomething() throw()
    {
        fprintf( stderr, "MyTestClass1::DoSomething()\n" );
    }
};
EXPORT_DYNAMIC_CLASS( MyTestClass1 )

让我们看看 EXPORT_DYNAMIC_CLASS 宏的定义(DynamicClass.hpp 文件)。

#define EXPORT_DYNAMIC_CLASS( className ) \
extern "C" PDL_DECL_EXPORT PDL::DynamicClass * Create##className() \
{ \
    try { return new className(); } \
    catch( ... ) { ;; } \
    return NULL; \
}

此宏定义并导出了一个名为 Create<class_name> 的函数(一个构建函数),该函数会创建一个作为参数传递的类的实例。extern "C" 修饰符是必需的,以防止函数名被修饰。PDL_DECL_EXPORT 宏将此函数声明为可导出。它在 platform.h 中的定义针对不同平台是特定的。

有几个重要问题。构建函数(Create<class_name>)会捕获类构造函数抛出的所有异常,并在发生异常时返回 NULL。这解决了主程序中处理插件抛出的异常的所有问题。在实例创建时,我们的构建函数返回一个指向 PDL::DynamicClass 的指针,因此,如果您忘记继承此类的接口,编译器会通过产生一个类型转换错误来提醒您。

现在我们可以构建我们的插件了。一个插件可以包含多个不同的类,但它们的名称在模块级别上应该是唯一的。每个类都必须使用 EXPORT_DYNAMIC_CLASS 宏导出。

使用动态加载类

此时,我们有了一个包含动态加载类的插件。让我们尝试使用它。

首先,我们需要获取动态类加载器 PDL::DynamicLoader 的实例。这是一个单例。要获取实例的引用,我们应该使用 static 方法 DynamicLoader::Instance()

PDL::DynamicLoader & dynamicLoader = PDL::DynamicLoader::Instance();

然后,我们需要加载类实例并获取其指针。

MyTestInterface * instance =
    dynamicLoader.GetClassInstance< MyTestInterface >
                ( myLibName, "MyTestClass1" );

这里 myLibName 是插件库的文件名,例如 "MyTestClass1.dll" 或 "MyTestClass.so"。别忘了包含带有接口声明的头文件——在本例中是 MyTestInterface.hpp,如前所述。

最后,我们调用加载类的有用方法。

instance -> DoSomething();

由于动态类加载器在失败时会抛出 PDL::LoaderException,因此捕获它是正确的。这是我们示例的完整代码。

#include <MyTestInterface.hpp>

#include <stdio.h> 


try
{
    PDL::DynamicLoader & dynamicLoader = PDL::DynamicLoader::Instance();
    MyTestInterface * instance =
        dynamicLoader.GetClassInstance< MyTestInterface >
                        ( myLibName, "MyTestClass1" );
    instance -> DoSomething();
}
catch( PDL::LoaderException & ex )
{
    fprintf( stderr, "Loader exception: %s\n", ex.what() );
} 

有一个重要的特性:所有动态加载的类都是单例。这意味着对同一个库名和类名调用 DynamicLoader::GetInstance() 的重复调用将返回指向同一类实例的指针。这简化了对加载实例的控制并防止了内存泄漏。如果您需要创建多个类实例,可以实现动态加载类工厂。

让我们检查一下加载的类实例是如何被销毁的。这是 DynamicClass::Destroy() 方法的职责。您不需要直接调用它——DynamicLoader 会在其析构函数或 DynamicLoader::Reset() 中执行此操作。为什么我们不使用通用的析构函数?这是因为内存分配/释放机制的问题,在不同的编译器中略有不同。让我们设想一下:我们用编译器 A 构建了一个插件,用编译器 B 构建了一个主程序。当我们通过动态加载器加载一个类时,它的实例是由编译器 A 生成的代码创建的。但是,如果我们调用析构函数,我们就会调用编译器 B 生成的代码。这可能导致意外的问题。

为了防止这些问题,析构函数 ~DynamicClass() 被声明为 protected,您需要调用 DynamicClass::Destroy() 方法来代替。此方法确保析构函数的代码与构造函数的代码由相同的编译器编译。

美中不足

库名称存在一个问题。如果库文件名发生更改,PDL 会认为它是一个不同的库。然而,不同的名称可能指向同一个库,例如:C:\MyProg\libs\mylib.dllMyLIB.DLL

请注意,PDL 无法解决不同编译器使用的名称修饰问题。这个问题在目前尚未解决。

PDL 库已在以下平台进行测试:

  • FreeBSD 6.2
  • Debian 4.0 Linux 2.6.18-4
  • openSUSE 10.2
  • Windows XP

我将非常感谢有关 PDL 在其他平台使用情况的任何信息。

谢谢

特别感谢 Vladimir 和 Asya Storozhevykh、Alexander Ledenev 和 Valery Artukhin,他们帮助我(希望)使这篇文章变得更好。

链接

© . All rights reserved.