Introduction to PDL

What Is PDL?

PDL (Portable Dynamic Loader) is a light, simple, and portable library designed especially for creating and using dynamically loaded class objects.

Why Do You Need to Load Classes Dynamically?

The main purpose of dynamically loadable class technology is creating plug-ins that extend the functionality of the main program. The main problem with dynamically loaded modules on many platforms is that they support a procedure programming paradigm only. When you try to load classes, many problems appear. The PDL library solves most of these problems (but not all of them, sadly).

On a Win32 platform, PDL is very simple alternative to COM technology, without reference counting, global class registration, and many other features. There are several similar libraries for Unix/Linux platforms; for example, C++ Dynamic Class Loader. The dynamic class loading support feature also is present in a large cross-platform library called WxWidgets.

The main goal of PDL development was to create a cross-platform library that could support a single dynamic class loading mechanism for both Win32 and Unix/Linux platforms (unlike COM and C++ Dynamic Class Loader). This library should also be lightweight and independent (unlike huge WxWidgets one).

Creating the Dynamically Loadable Class

Consider the creation of a dynamically loadable class using PDL library in detail. First of all, you need to declare an interface that will be used to work with the instance of a loadable class instance.

Indispensable condition: This interface must be inherited from PDL::DynamicClass.

Look at the DynamicClass declaration:

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() { ;; }
};

The pure virtual method GetClassName() returns the name of the class. You don't have to worry about its definition, and later I'll explain why.

The non-virtual method Destroy() destroys the class instance. The class destructor is declared as protected to prevent its direct call. I'll describe later why you need such a trick.

You have to inherit your interface from PDL::DynamicClass and define protected virtual destructor (according to PDL ideology).

You also need to insert a DECLARE_DYNAMIC_CLASS macro inside of a class declaration with the class name as a parameter. If you look at the definition of this macro, you'll see that it simply defines the virtual method GetClassName():

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

Finally, add to your interface pure virtual methods that implement useful functionality of dynamically loadable class. Let it be the DoSomething() method, for example. As a result, you have the following interface:

#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 )
};

You should put this declaration into a separate header file, in your case MyTestInterface.hpp. For compatibility purposes, this file must be included both for building the dynamically loadable class and its direct usage.

Then, you should declare a class, MyTestInterface, inherited from your abstract interface, and define methods that implement its useful functionality. You also need to export this class using the EXPORT_DYNAMIC_CLASS macro. Please note that this macro should be placed outside of a class declaration:

#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 )

Look at the definition of EXPORT_DYNAMIC_CLASS macro (DynamicClass.hpp file):

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

This macro defines and exports the function with the name Create<class_name> (a builder function), which creates an instance of the class, given as a parameter. The extern "C" modifier is required to prevent function name mangling. The PDL_DECL_EXPORT macro declares this function as exportable. Its definition in platform.h is specific for different platforms.

There are several important issues. The builder function (Create<class_name>) catches all the exceptions, thrown by class constructor, returning NULL in the case of an exception. That solves all problems with handling of exceptions thrown by the plug-in in the main program. In the case of instance creation, your builder function returns a pointer to the PDL::DynamicClass, so, if you forgot to inherit your interface from this class, the compiler will remind you, producing a casting error.

Now you can build your plug-in. A single plug-in can contain several different classes, but their names should be unique at the module level. Each class must be exported using the EXPORT_DYNAMIC_CLASS macro.

Introduction to PDL

Using Dynamically Loadable Classes

At this moment, you have a plug-in that contains a dynamically loadable class. Now, try to use it.

First, you need to get an instance of dynamic class loader—PDL::DynamicLoader. This class is a singleton. To get a reference to the instance, you should use the static method DynamicLoader::Instance():

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

Then, you need to load the class instance and get a pointer to it:

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

Here, myLibName is a filename of the plug-in library, for example "MyTestClass1.dll" or "MyTestClass.so". Don't forget to include header file with interface declaration—in your case, it is MyTestInterface.hpp, as described before.

Finally, you invoke useful method of loaded class:

instance -> DoSomething();

Because the dynamic class loader throws PDL::LoaderException in case of failure, it will be correct to catch it. Here is the full source of your example:

#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() );
}

There is an important feature: All the dynamically loaded classes are singletons. This means that repeated calls of DynamicLoader::GetInstance() with the same both library name and class name will return a pointer to the same class instance. This simplifies the control over loaded instances and prevents memory leaks. If you need to create many class instances, you may implement dynamically loadable class factory.

Now, examine how loaded class instances are being destroyed. This is the responsibility of the DynamicClass::Destroy() method. You don't need to invoke it directly; the DynamicLoader will do it in its destructor or in DynamicLoader::Reset(). Why shouldn't you use a generic destructor? This is because of the memory allocation/freeing mechanism issues that are slightly different in different compilers. Let's imagine: You build a plug-in with compiler A, and a main program with compiler B. When you load a class by dynamic loader, its instance is created by a code produced by the A compiler. But if you call a destructor, you invoke a code produced by compiler B. This may lead to unexpected problems.

To prevent such problems the destructor ~DynamicClass() is declared as protected, and you need to call DynamicClass::Destroy() method instead. This method guarantees that the code of destructor is compiled by the same compiler, as the code of constructor.

Fly in the Ointment

There is an issue with library names. If the filename of the library changes, PDL considers that it is a different library. However, different names may point to a single library; for example: C:\MyProg\libs\mylib.dll P8 MyLIB.DLL. Pay attention that PDL doesn't solve problems with name mangling, used by different compilers. This problem is not set at the current moment.

The PDL library was tested on the following platforms:

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

I will be grateful for any information about usage of PDL on other platforms.

Thanks

Special thanks go to Vladimir and Asya Storozhevykh, Alexander Ledenev, and Valery Artukhin, who helped me make this article better (I hope).

Links



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

  • Learn How A Global Entertainment Company Saw a 448% ROI Every business today uses software to manage systems, deliver products, and empower employees to do their jobs. But software inevitably breaks, and when it does, businesses lose money -- in the form of dissatisfied customers, missed SLAs or lost productivity. PagerDuty, an operations performance platform, solves this problem by helping operations engineers and developers more effectively manage and resolve incidents across a company's global operations. …

Most Popular Programming Stories

More for Developers

RSS Feeds