Costin Iordache wrote:
Hi,
I'm using xerces 2.7 on Windows XP SP3, Visual Studio 2003. Everything was
fine until I needed to declare a static variable (at file scope) which, in
turn, contained references to xerces objects that need xercesc_2_7
::Initialize/ xercesc_2_7::Terminate pair functions to be called first.
Thus, I had to find a way to call xercesc_2_7::Initialize before entering
main function. Here is my file:
#include <xercesc/util/PlatformUtils.hpp>
struct dom_initialiser
{
dom_initialiser ()
{
xercesc_2_7::XMLPlatformUtils::Initialize();
}
~ dom_initialiser ()
{
xercesc_2_7::XMLPlatformUtils::Terminate();
}
};
dom_initialiser t;
int main(int argc, char* argv[])
{
return 0;
}
This is a very bad idea, because you risk calling
XMLPlatformUtils::Initialize() before static initialization is complete.
This will lead to undefined behavior.
C++ does not mandate an order for static initialization between
translation units or shared objects. Also, you will be in big trouble if
XMLPlatformUtils::Initialize() throws an exception.
Although the program is trivial, Rational Purify reports memory leaks! I
investigated to see how serious the warnings/memory leaks are and I
discovered the following:
1. xerces uses XMLRegisterCleanup to clean up some stuff. Static objects of
type XMLRegisterCleanup are instantiated through xerces cpp files and
XMLRegisterCleanup::registerCleanup method is afterwards called to register
pointers to functions that will do the actual clean up. Internally, the
class holds two pointers which serve to implement a doubly linked list,
gXMLCleanupList.
2. XMLPlatformUtils::Terminate() (platformsutils.cpp file) method iterates
over gXMLCleanupList and calls doCleanup().
The problem with this approach is that some of (all) the static objects are
used BEFORE their initialization! That's the reason why rational purify
detects memory leaks! For instance, in file TransService.cpp the objects
static XMLRegisterCleanup mappingsCleanup;
static XMLRegisterCleanup mappingsRecognizerCleanup;
are used in the constructor XMLTransService::XMLTransService() before their
initialization (lines 94, 107):
mappingsCleanup.registerCleanup(reinitMappings); // mappingsCleanup not
initialized!!!!
This is because of what I alluded to previously -- your static object is
being constructed and is initializing Xerces-C before static
initialization is complete for Xerces-C. I assume this is happening
because you're linking with the static library, instead of the DLL.
I solved this by creating a new function(s):
XMLRegisterCleanup & mappingsCleanup()
{
static XMLRegisterCleanup mappingsCleanup;
return mappingsCleanup;
}
and calling mappingsCleanup().registerCleanup(reinitMappings);
Question: do you think guys that this approach is ok? Can you spot any
hidden issue that is not so obvious? Do you think that it would be a good
idea to change all static instances of XMLRegisterCleanup objects to
functions?
No, I think it's a big hack and you will find it's potentially not
portable between compilers and platforms, or even versions of a
particular compiler. This is why static instances of Xerces-C objects
are not supported.
If you look inside the file XMLRegisterCleanup.hpp there is a comment
saying:
"// N.B. These objects need to be statically allocated. I couldn't think
// of a neat way of ensuring this - can anyone else?"
That is all with the static construction. Now, let's move on destruction:
xercesc_2_7::XMLPlatformUtils::Terminate() must be called last. Therefore, I
isolated struct test into an hpp that is included FIRST by any project hpp
that uses xerces. The files (hpp and cpp) look like this:
########################################dom_init.hpp#####################
#ifndef DOM_INIT_HPP
#define DOM_INIT_HPP
class dom_initialiser
{
public:
~dom_initialiser();
dom_initialiser();
};
namespace
{
/**
* Dummy object that forces the creation of dom_initialiser.
* This will ensure that creating static or file scope
* instances of xml::str is not done before the XML lib is
* properly initialised AND XMLPlatformUtils::Terminate() is
called
last (after all xerce objects have been destroyed)
*/
dom_initialiser __dummy__dom_initialiser__;
}
#endif
#########################dom_init.cpp#########################
#include "dom_init.hpp"
using namespace xercesc;
dom_initialiser::dom_initialiser()
{
XMLPlatformUtils::Initialize();
}
dom_initialiser::~dom_initialiser()
{
XMLPlatformUtils::Terminate();
}
With this approach I completely eliminated the need of explicit call to
Initialize/Terminate!
This is similar to the method some iostreams implementations use to
ensure static initialization. However, you still aren't handling
exceptions properly, and you can still get tripped up if you
inadvertently violate your header file inclusion scheme.
Please feel free to criticize. Any comments are welcomed!
If you want to do this safely, and you only care about Windows, then you
should put this initialization object in a separate DLL with a dummy
exported function. Call the dummy function in your application's main()
and link with the DLL that contains the dummy function. This will
ensure that Xerces-C is initialized before your application's static
objects are initialized. Note that you'll also have to link Xerces-C
dynamically, not statically.
Finally, this does not address the issue of how to handle the situation
where XMLPlatformUtils::Initialize() throws an exception. There's just
no way to make that work with in this case. You're really better of
just avoiding static Xerces-C objects to begin with.
Dave