Sunday 11 January, 2009

Backward Compatibility with DLLs – Part 2 (Private Implementation)

A very Happy New Year to all of the readers!

This article is in continuation with the previous one, about preserving DLL exported APIs’ backward compatibility.

The last article described a technique of achieving this by privatizing the API’s constructor. This has many disadvantages of its own, as the c’tor is private, ex: Object can not be instantiated on stack.

An alternative technique is to have a “Private Implementation”.

If you have a class, which you intend to export, separate out all the internal implementation including the data members and the functions which you think can be hidden. The exported class will only have the functions exported along with a private member pointer to the new internal class.

As an example, consider the following class,

#define DLL_EXPORT __declspec(dllexport)
class DLL_EXPORT ExampleExport
{
public: // c'tors and d'tors
    ExampleExport();
    ~ExampleExport();

public: // functions
    void function1();
    int function2();

private: // private data members
    int m_member1;
    int m_member2;
};

We have 2 integer member variables. Adding or removing the member variables later, will always cause a binary compatibility break as described in the previous article.

Let’s try to separate out the internal implementation. We will move all the private data members to a different class – ExampleExportPrivate.

class ExampleExportPrivate
{
public: // c'tors and d'tors
    ExampleExportPrivate();
    ~ExampleExportPrivate();

public: // data members
    int m_member1;
    int m_member2;
};

We will redefine ExampleExport to use ExampleExportPrivate.

#define DLL_EXPORT __declspec(dllexport)

class ExampleExportPrivate; // <<-- forward declaration of the private implementation which is defined in cpp file


class DLL_EXPORT ExampleExport
{
public: // c'tors and d'tors
    ExampleExport();
    ~ExampleExport();

public: // functions
    void function1();
    int function2();

private:
    ExampleExportPrivate *m_private; // <—Pointer to private implementation. };

As a first step, forward declare the private implementation class – ExampleExportPrivate. The actual definition of this private implementation class can be placed as part of the cpp file. So, we are not exposing this class outside, even in the header files exported. Next step is to have a private pointer member variable which points to ExampleExportPrivate. Any references to the data will be redirected to the internal private implementation.

This technique will help us to not change the size of the exported class. If we need to add or delete a new member variable, ExampleExportPrivate will be the class which will have those changes, but not ExampleExport.

This technique is usually called as “PImpl” (Private Implementation). Here is an interesting article http://www.gotw.ca/publications/mill04.htm

No comments: