![]() |
General Programming »
DLLs & Assemblies »
General
Intermediate
The DLL Hell - Problems and SolutionsBy Dr. Ivan S ZapreevIn this article I am going to touch on the DLL backwards compatibility problem, which is also well known as the 'DLL Hell'. |
VC6, VC7, VC7.1Win2K, WinXP, Win2003, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
In 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.
Once 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.
As 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:
B and we are adding a new one named A before it, then we are changing the table of virtual methods. Now the first virtual method in the table will be A but not B and the client program which calls B will fail without recompilation as the call of B will cause the call of A and this is another method which possibly has other parameters and return type.
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.
This 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.
Let 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();
This will symbolize some changes in the SDK classes.
Now if we won�t recompile the client�s application, then the VirtFunctClassChild class will not know anything about the new virtual method void DoAnything() that was added to its base class, so its table of virtual methods (vtable) will contain only the two methods in the following order:
void DoSmth()
void DoSomething() This is not correct because the vtable should be as follows:
void DoSmth()
void DoAnything()
void DoSomething() Then if after instantiation of the VirtFunctClassChild class, someone will call its void DoSmth() method, it will cause calling of the new virtual method void DoAnything(). The base class knows that it should be a second method in the vtable and so it will try to call it. Obviously this will cause calling of the void DoSomething() method of the child class as the vtable of the VirtFunctClassChild class contains this method in the second position and it doesn�t contain the void DoAnything() method at all !!!!
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 virtual void DoSomething(){} method in the VirtFunctClassChild class), the call of the void DoAnything() method from the base class will cause referencing to the empty memory as there will be only one member in the vtable.
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.
Now 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.
Let 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:
ICallBack * SetCallBackHandler(ICallBack *handler); method;
CallBackI interface. I have added the ICallBack01 interface definition here as an example of how the CExample class can be extended with the new virtual callback method.
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 ICallBack01. If in the next release we will need to add a new call back virtual method to this class then we will create a new interface ICallBack02 that will extend the ICallBack01 interface in the way the ICallBack01 extends the ICallBack.
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 ICallBack01 with the new method DoCallBack01, we should check (in the CExample class) if we can call it on the ICallBack * mpHandler; member. This check should be done in the following way:
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 CExample class, so it is very easy.
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 ICallBack definition, SetCallBackHandler method, ICallBack pointer) is the client application's recompilation.
Later when someone will add the new callback, he will do it via the new interface addition (like adding of the ICallBack01 interface in our example). It is obvious that such change would not affect anything, as the order of virtual methods will not be changed. So the client applications will work in the old manner. The only thing you should remember is that client applications will not receive the new callbacks until they implement the new interface.
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.
In 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:
DLL_EXAMPLE_MODIFIED undefined) to emulate the initial situation. Run client application.
DLL_EXAMPLE_MODIFIED macro definition. Build the Dll_example. Run the client application. Everything will work as it should!
That is all about the example project. Just download and explore it to get all details.
In this article:
I believe that this article will be helpful for anyone who has come across the �DLL Hell�.
While 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.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 12 Nov 2003 Editor: Smitha Vijayan |
Copyright 2003 by Dr. Ivan S Zapreev Everything else Copyright © CodeProject, 1999-2009 Web22 | Advertise on the Code Project |