Today, I was rating some students group for one of their project (called Zia, a Third Year project about creating an HTTP server) and they told me that they could not understand some very strange behavior of their software. To keep it short, they were having some really strange jumps from some functions to others, in some unrelated places. This is unusual, particularly when the function actually called is not the one that should be called in a virtual function context.
The thing is, about this project, that it is required to have an extensible way of dealing with functionalities of an HTTP server in the form of Modules or Plug-ins. In an object oriented way of seeing things, two methods exist :
- The first one is about using static referencing of types in a DLL, by means of directives like __dllimport set on classes. This is an impracticable way for plug-in enumeration as this is "dynamic static" linking of functions, which is much like linking using a static library. An other problem with this kind of implementation is that it cannot be used to efficiently achieve abstraction using the C#-style (or java) interface because types are explicitly referenced from the dynamic library. Generally speaking, this is not a good choice (and this is also not portable).
- An other one is about using a common interface (or fully abstract class, only pure virtual functions and no data members) shared by both plug-ins and the host, and allowing plug-ins to create concrete instances of the common interface. This has multiple advantages :
- The host only imports a few "C-Style" functions (generally declared as extern "C") used, for example, to create instances of concrete classes or to enumerate types that can be created by the module. This also removes the implications of the symbols decoration (or name mangling) generated by the C++ compiler, (By the way, the dependency walker is a great tool to see this)
- Types Virtual Tables are automatically built using the DLL memory space, letting the C++ plumbing doing the job for the user,
- It is also possible to enumerate, load or unload plug-ins on the fly,
- And obviously, the host only relies on the interface exposed by a plug-in to use its services. This is not specific to this method, but rather an other way of using interfaces with a greater level of abstraction, because concrete types are not known by the host.
In many ways, true dynamic loading of DLLs using this method is better than the static one. But we’re not in a perfect world and this method also has its drawbacks, some being really vicious.
There are multiple ways for including the Runtime Library, which is a kind of libc that can be found under Unices. Actually there are three ways:
- Including the “SingleThreaded” (ST) version of the library, which is mainly suited for applications that do not use threads,
- Including the “MultiThreaded” (MT) version that is used when the application is MultiThreaded. RT functions are then optimized to be ThreadSafe, which is not the case for the ST version. This is a static version of the RT that is completely included in the final binary minus, of course, methods you do use.
- Including the “MultiThreaded DLL” (MD) version – the mostly used version – that references the msvcr7x.dll file. (This changes depending on the C++ compiler. Actually, this used not to change but it created havoc so…) This is, so far, the best way of including the RT as it lightens the weight of binaries and has other implications I will discuss later on.
This parameter can be found in :
Project Properties / C/C++ / Code Generation / Runtime Library
There is a thing about DLLs and static variables: These are local to their modules (understand DLL in this context) and not the current process. In other words, including the RT as a static library in a plug-in/host context creates multiple “instances” of the static variables found in the RT. In particular, it creates independent versions of internal lists used to maintain heap memory allocations. These lists are used, for instance, by malloc or other memory allocation functions.
Knowing this, it becomes obvious that allocating one object from one module (some plug-in) and freeing it in an other module (say the host) leads toward freeing a pointer that does not exist in the destination module. There are many behaviors that can be observed:
- The first – which the most common – is the RT assertion dialog box showing up and saying that the pointer being freed is not valid. Most programmers don’t really understand this message and choose to ignore it because they don’t understand it, which is not that good…
- The second, found if debugging features of the compiler are deactivated, is a simple crash but a really hard bug to spot.
Back in the object oriented world, where everything is encapsulated, you can find that kind of code in the shared interfaces:
virtual std::vector<int> GetRefs() = 0;
Although this is completely correct, the problem with that kind of code is that the vector object itself is generally allocated on the stack (although it depends on the left value used in the assignation) but contained data is allocated on the heap. This is a hidden memory allocation that can cause trouble when using the Static MultiThreaded version of the RT, leading – when you are lucky – to a crash and when you’re not, the kind of behavior my students have been experiencing, like a partial destruction of the heap and/or the stack…
In other words: Use the “MultiThreaded DLL” version of the Runtime Library in all the binaries and static libraries of your projects. Note that I insist heavily on the fact that the same runtime must be used everywhere, otherwise you’ll get a lot of strange linker warnings and errors. (This is mainly because the same symbols are not exposed in the same way, static and dll imported)
By the way, in the .NET world it is mandatory when using Managed C++ extensions, probably for the same reason…