|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
AbstractIn this article, I am going to touch on problems of DLL backwards compatibility, which are also well known as the ‘DLL Hell’. I am going to list results of my own investigation and also refer to other investigators’ results. At the end of the article, I will give my approach for solving one of the ‘DLL hell’ problems. IntroductionOnce I’ve got a task to solve the problem with the DLL updates. Some company supplied users with DLLs that contained classes that developers used in their C++ applications. In other words, it was some kind of a SDK in the DLL. There were no restrictions on how to use these classes, so developers derived from them while creating their applications. Soon they realized that their applications started to crash after the DLL updates. Developers were required to rebuild their applications to get versions that can work with the new DLL. Further I will provide you with my research results on this problem, with the information I have gathered from the external sources and finally I will give my solution for one of the ‘DLL Hell’ problems. Research resultsAs I have understood, the issue was in the modification of the base classes placed in the supplied DLL. I’ve overviewed several articles and found that the problem of the DLL backwards compatibility is not a new one but as a true researcher I’ve decided to make several experiments personally. As a result, I have got the following list of problems:
There are other reasons that can cause DLL backwards compatibility problems, so below is the list of solutions that are usually offered to overcome most of them. DLL coding conventions in briefThis is the summary of solutions I’ve found in articles on the web and I’ve got while talking with different developers. The following conventions of a DLL development are usually offered to support DLLs backwards compatibility:
Below I am going to discuss the problem of virtual methods and I will also give a solution for one of its variants. Virtual methods and inheritanceLet us investigate an example with virtual methods and inheritance: /**********Exported class (dll) **********/
class EXPORT_DLL_PREFIX VirtFunctClass{
public:
VirtFunctClass(){}
~VirtFunctClass(){}
virtual void DoSmth(){
//this->DoAnything();
// Uncomment of this line after the corresponding method
//will be added to the class declaration
}
//virtual void DoAnything(){}
// Adding of this virtual method will make shift in
// table of virtual methods
};
/**********Inherited class (client app) **********/
class VirtFunctClassChild : public VirtFunctClass {
public:
VirtFunctClassChild() : VirtFunctClass (){}
~VirtFunctClassChild(){};
virtual void DoSomething(){}
};
Let’s imagine that we have classes listed above. The first one is the my.dll member and the second one is the class defined in the client’s application. Now we will make some changes in the DLL class i.e. uncomment the following two lines: //virtual void DoAnything(){} and //this->DoAnything();
Now if we won’t recompile the client’s application, then the
This is not correct because the
Then if after instantiation of the It is important to note that in case of prohibiting of inheritance from the DLL classes with adding of new virtual methods, we will still have the same problem because in this case (just imagine that there was no Now it is obvious that there is a serious problem with adding of the new virtual methods but there is an ability to solve it in case when virtual methods are used to handle callbacks. COM and othersNow it is time to tell that these problems are not only the well know ones and that there are not only special conventions that can help to solve them but that there are technologies that allow to avoid most of the DLL backwards compatibility problems. One of these technologies is Component Object Model (COM). So if you want to forget problems with your DLLs, use COM or any other appropriate technology. Let us now return to the task that was given to me. The task I was talking about in the beginning of the article. It was to solve the backwards compatibility problem of one product that was a DLL. I knew about COM and thus my first proposal was to start using COM in this project to overcome all the issues. This approach was rejected because of the following reasonable causes:
In other words, what was required was to propose the most costless way to check the DLL backwards compatibility problem. Of course, I should say that the main issue of this project was adding the new fields and virtual callback functions to the interface classes. The first problem is simply solved by adding a pointer to a structure (that will contain all new fields) to the class declaration and it is a common approach I have mentioned above. But the problem with the virtual callback functions was new. Further I propose the costless and effective way to solve it. Virtual callback methods and inheritanceLet us imagine that we have a DLL with the exported classes someone has to inherit from, to implement virtual functions to handle callbacks in his application. We want to make minor changes in the DLL. This change should allow us to painlessly add new virtual call back functions to the DLL classes in the future. At the same time, we don’t want to affect applications that use the current DLL version i.e. all we can expect is that they will be once recompiled with the new DLL version but that should be done for the last time. So these are target settings and here comes the solution: We can leave every virtual callback method as it is in the DLL's classes. All we have to remember is that adding of the new virtual method to any class definition may cause failure if a client application would not be recompiled with the new DLL version. We want to avoid this problem. The “Listeners” mechanism can help us here! If virtual methods, defined and exported from the DLL classes, are used to handle callbacks, then we can move new virtual methods to the separate interfaces. Let’s look at the following example: // Uncomment this definition to get sources after the DLL
// interface extension
//#define DLL_EXAMPLE_MODIFIED
#ifdef DLL_EXPORT
#define DLL_PREFIX __declspec(dllexport)
#else
#define DLL_PREFIX __declspec(dllimport)
#endif
/**********Exported class (dll) **********/
#define CLASS_UIID_DEF static short GetClassUIID(){return 0;}
#define OBJECT_UIID_DEF virtual short
GetObjectUIID(){return this->GetClassUIID();}
//Base interface for all callback handlers extensions
struct DLL_PREFIX ICallBack
{
CLASS_UIID_DEF
OBJECT_UIID_DEF
};
#undef CLASS_UIID_DEF
#define CLASS_UIID_DEF(X) public: static
short GetClassUIID(){return X::GetClassUIID()+1;}
#if defined(DLL_EXAMPLE_MODIFIED)
//Newly added interface extension
struct DLL_PREFIX ICallBack01 : public ICallBack
{
CLASS_UIID_DEF(ICallBack)
OBJECT_UIID_DEF
virtual void DoCallBack01(int event) = 0; //new call back method
};
#endif // defined(DLL_EXAMPLE_MODIFIED)
class DLL_PREFIX CExample{
public:
CExample(){mpHandler = 0;}
virtual ~CExample(){}
virtual void DoCallBack(int event) = 0;
ICallBack * SetCallBackHandler(ICallBack *handler);
void Run();
private:
ICallBack * mpHandler;
};
It is easy to see that all we have to do to prepare the DLL’s classes for extension with the new virtual methods is to:
I have added the It is obvious that new virtual methods should be added to a new interface. One interface for each DLL update (i.e. if we want to add several new virtual callback methods to one class at the same time [for one DLL release], we should add them to one interface). Each new interface (for one class) should extend the previous interface. In my example I have only one extension interface Macros in the code above are used to define methods required to check the interface version. For example when we are adding the new interface if(mpHandler != NULL && mpHandler->GetObjectUIID()>=ICallBack01::GetClassUIID()){
((ICallBack01 *) mpHandler)->DoCallBack01(2);
}
All these is made in the case when the new call back interface is added and the call back is inserted in the Now you can see that the client’s applications would not be affected by these changes. The only thing will be required after the first DLL classes’ update (adding of macros, base interface Later when someone will add the new callback, he will do it via the new interface addition (like adding of the It is also important to note that DLL users will still be able to work with it easily. This is the client’s class example: #if !defined(DLL_EXAMPLE_MODIFIED)
//Before a new interface is added to the dll.
class CClient : public CExample{
//Everything remains as it was
public:
CClient();
void DoCallBack(int event);
};
#else // !defined(DLL_EXAMPLE_MODIFIED)
//After the interface ICallBack01 was added client may (not necessarily)
//change his class in the following manner to handle new events
class CClient : public CExample, public ICallBack01{
public:
//The constructor changes
CClient();
//Everything remains as it was
void DoCallBack(int event);
//Add the new virtual method definitions
//(for methods inherited from the ICallBack01)
void DoCallBack01(int event);
};
#endif // defined(DLL_EXAMPLE_MODIFIED)
Note that an example project is attached to this article. It is called Dll_Hell_Solution. Example ProjectIn the example workspace, you will find two sub projects:
DLL_EXAMPLE_MODIFIED macro definition. Uncomment it to emulate the DLL's classes update.
To check that everything works, perform the following steps one by one:
That is all about the example project. Just download and explore it to get all details. ConclusionIn this article:
I believe that this article will be helpful for anyone who has come across the ‘DLL Hell’. ReferencesWhile working on this article, I have used the following material: If you have found that this is not the full list of references, then please notify me and I will immediately fix this problem. | ||||||||||||||||||||