Showing posts with label PIMPL. Show all posts
Showing posts with label PIMPL. Show all posts

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

Monday, 1 December 2008

Backward Compatibility with DLLs

This and the following articles, I personally feel very interesting.

It’s related to the “backward compatibility” of the DLLs.

If you are distributing your DLLs to people or 3rd party, whom you expect to use them, you should be very careful about the interface you expose out of the DLLs.

Consider you are distributing a DLL, which has an exported class of some size and few public functions. If you update the same DLL in future and redistribute it, you cannot always expect the users to re-link their application with your new DLL. In such cases, users of the DLL expect the same interface what you had provided in the earlier release. If by any way there is a change in the interface, it results in failure of backward compatibility.

Let’s take an example to explain this situation. The following is a class which you are exporting as part of your DLL,

#define DLL_EXPORT __declspec(dllexport)

class DLL_EXPORT ExampleExport
{
    public:
        ExampleExport();
        ~ExampleExport();

        void function1();
        int function2();

    private:

        int m_member1;
        int m_member2;
};

In a future release you updated this class to have an additional member variable,

class DLL_EXPORT ExampleExport
{
    public:
        ExampleExport();
        ~ExampleExport();

        void function1();
        int function2();

    private:

        int m_member1;
        int m_member2;
        int m_member3; // <<---- a new member variable added
};

You can observer that the class size has been changed. Earlier it was 8 bytes (assuming int as 4 bytes) and it’s changed to 12 by adding an additional integer member variable.

Consequence: For an end user application, which is not re-linked with the updated DLL, the size of the class is still 8 bytes. Since the application was compiled and linked with the first version of DLL, the size is still the same even after they started using the new DLL. The application may have unpredicted behavior.

Resolution: This problem needs to be addressed before we release the first version of the DLL. Of course we cannot restrict the size of the class to not to change in future. We may need to update it by adding or removing new member variables. One solution is to force the end user application to not to construct the object by itself.

  • Make constructor private.
  • Export a new static member function which creates an object by calling private constructor

class DLL_EXPORT ExampleExport
{
public:
    ~ExampleExport();

public:
    static ExampleExport* Instance();

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

private:
    ExampleExport(); // <<---- private constructor

private:
    int m_member1;
    int m_member2;
    int m_member3; // <<---- a new member variable added
};

This forces the client application to use ExampleExport::Instance() function always to construct an object and since the definition of Instance() is part of the DLL, 12 bytes will be allocated with the new DLL.

Let’s have a look at another solution for the same problem, in the next article.