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.