Difference between revisions of "Uno/Cpp/Tutorials/Global References"
m (Added MS atexit overwrite.) |
m (Added tests and some explanations etc ...) |
||
Line 1: | Line 1: | ||
− | + | State: draft | |
Using global references in C++ Uno and how they interfere with "atexit". | Using global references in C++ Uno and how they interfere with "atexit". | ||
+ | ==bla== | ||
+ | Uno being a component model, allows the dynamic addition or removal of functionality to a OS process in particular or a Uno context in general. Such functionality may be provided in the form of a [[wikipedia:Shared_library|shared library]]. Most (all?) modern OS provide a method to load and unload such shared libraries dynamically (see dlopen/dlclose respectively LoadLibrary/UnloadLibrary). Ideally such a shared library gets unloaded when its functionality is not needed any more. | ||
− | ==C++ Standard== | + | ==Global References== |
+ | For convenience and simplicity, people tend to use global variables, especially references to hold Uno objects for later use. E.g. | ||
+ | <code>[cpp] | ||
+ | class FooBar; | ||
+ | |||
+ | uno::Reference<uno::XInterface> g_hold; | ||
+ | |||
+ | void myFun() { | ||
+ | if (!_hold.is()) { | ||
+ | uno::Reference<uno::XInterface> tmp(new FooBar()); | ||
+ | |||
+ | g_hold = tmp; // This may needs to be protected by a mutex. | ||
+ | } | ||
+ | |||
+ | // ... do something with g_hold ... | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | ==Lifetime== | ||
+ | Unfortunately this construct may lead to life cycle problems similar to the "The Dead Reference Problem" (see Alexandrescu, | ||
+ | "Modern C++ Design"), because of C++ relationship to "atexit". Leading to a situation that, despite having a valid library handle at hand, a libraries static values with C++ d'tors are already de-initialized. | ||
+ | |||
+ | ===C++ Standard=== | ||
In section 3.6.3. the C++ standard states, that the d'tors of values with "static storage duration" are called in the reverse order of the completion of their construction. It also states, that functions registered with "atexit" are called in some specified order wrt these kind of values, in fact they are called after all values have been destructed which had not been constructed at the time of registration. As "atexit" is called at program termination, this is relevant for termination. | In section 3.6.3. the C++ standard states, that the d'tors of values with "static storage duration" are called in the reverse order of the completion of their construction. It also states, that functions registered with "atexit" are called in some specified order wrt these kind of values, in fact they are called after all values have been destructed which had not been constructed at the time of registration. As "atexit" is called at program termination, this is relevant for termination. | ||
In practice this leads to the situation, that values with "static storage duration" of dynamically loaded libraries may already be destroyed, even the library has not been closed yet. | In practice this leads to the situation, that values with "static storage duration" of dynamically loaded libraries may already be destroyed, even the library has not been closed yet. | ||
− | ==Uno Objects== | + | ===Tests=== |
+ | I prepared some tests to actually make C++ vs. shared libraries problems more visible: | ||
+ | # A C++ executable with a global, linked against a C++ library with a global. | ||
+ | # A C++ executable with a global, dynamically opening a C++ library with a global. | ||
+ | # A C++ executable with a global, dynamically opening a C library with a global. | ||
+ | # A C++ executable without a global, but dynamically opening a C++ library which dynamically opens another C++ library. | ||
+ | # A C++ executable without a global, but dynamically opening a C++ library which dynamically opens another C++ library, forgetting to close the first C++ library. | ||
+ | # A C++ executable with a global, dynamically opening a C++ library with a global, but using "_exit" for termination. | ||
+ | # A C++ executable with a local, dynamically opening a C++ library with a global. | ||
+ | |||
+ | Looking at the output of the tests, we can see that cases #2 and #5 are problematic. | ||
+ | |||
+ | Output of case #2: | ||
+ | <pre> | ||
+ | ./dlopen_cxxBar.cxx.bin | ||
+ | Foo::Foo() | ||
+ | Bar::Bar() | ||
+ | void Foo::openBar(const char*)- handle:0x804b020 | ||
+ | Bar::~Bar() | ||
+ | Foo::~Foo() | ||
+ | Bar is invalid -> statics are BAD! | ||
+ | closing Bar... | ||
+ | </pre> | ||
+ | |||
+ | Output of case #5: | ||
+ | <pre> | ||
+ | ./dlopen_cxxFoo_wo_closing.cxx.bin | ||
+ | int main()- handle(libFoo.cxx.so):0x804a020 | ||
+ | Foo::Foo() | ||
+ | Bar::Bar() | ||
+ | void Foo::openBar(const char*)- handle:0x804a3e0 | ||
+ | Bar::~Bar() | ||
+ | Foo::~Foo() | ||
+ | Bar is invalid -> statics are BAD! | ||
+ | closing Bar... | ||
+ | </pre> | ||
+ | |||
+ | These are exactly the casing we are unfortunately facing in Uno and OOo. Obviously case #2 can be solved by utilizing the uno.exe to bring up an application, which is the right thing anyway. The solution for case #5 relies on correctly closing all dynamically shared libraries before actually terminating, which does not seem to work reliable currently! | ||
+ | |||
+ | ===Uno Objects=== | ||
As a client of some service or object typically does not (should not) know, if the particular object is implemented in another library or uses globals for its implementation, any global references to Uno objects need to be cleared before termination. | As a client of some service or object typically does not (should not) know, if the particular object is implemented in another library or uses globals for its implementation, any global references to Uno objects need to be cleared before termination. | ||
Line 15: | Line 78: | ||
* Win32 "FreeLibrary" - http://msdn2.microsoft.com/en-us/library/ms683152.aspx | * Win32 "FreeLibrary" - http://msdn2.microsoft.com/en-us/library/ms683152.aspx | ||
* MS solution for the atexit problem - http://msdn2.microsoft.com/en-us/library/7977wcck(vs.71).aspx | * MS solution for the atexit problem - http://msdn2.microsoft.com/en-us/library/7977wcck(vs.71).aspx | ||
+ | |||
+ | ===Samples=== | ||
+ | {{:Uno/Cpp/Tutorials/Global_References/Samples}} | ||
+ | |||
[[Category:Uno]] | [[Category:Uno]] |
Revision as of 10:03, 27 August 2007
State: draft
Using global references in C++ Uno and how they interfere with "atexit".
Contents
bla
Uno being a component model, allows the dynamic addition or removal of functionality to a OS process in particular or a Uno context in general. Such functionality may be provided in the form of a shared library. Most (all?) modern OS provide a method to load and unload such shared libraries dynamically (see dlopen/dlclose respectively LoadLibrary/UnloadLibrary). Ideally such a shared library gets unloaded when its functionality is not needed any more.
Global References
For convenience and simplicity, people tend to use global variables, especially references to hold Uno objects for later use. E.g.
[cpp]
class FooBar;
uno::Reference<uno::XInterface> g_hold;
void myFun() {
if (!_hold.is()) { uno::Reference<uno::XInterface> tmp(new FooBar());
g_hold = tmp; // This may needs to be protected by a mutex. }
// ... do something with g_hold ...
}
Lifetime
Unfortunately this construct may lead to life cycle problems similar to the "The Dead Reference Problem" (see Alexandrescu, "Modern C++ Design"), because of C++ relationship to "atexit". Leading to a situation that, despite having a valid library handle at hand, a libraries static values with C++ d'tors are already de-initialized.
C++ Standard
In section 3.6.3. the C++ standard states, that the d'tors of values with "static storage duration" are called in the reverse order of the completion of their construction. It also states, that functions registered with "atexit" are called in some specified order wrt these kind of values, in fact they are called after all values have been destructed which had not been constructed at the time of registration. As "atexit" is called at program termination, this is relevant for termination.
In practice this leads to the situation, that values with "static storage duration" of dynamically loaded libraries may already be destroyed, even the library has not been closed yet.
Tests
I prepared some tests to actually make C++ vs. shared libraries problems more visible:
- A C++ executable with a global, linked against a C++ library with a global.
- A C++ executable with a global, dynamically opening a C++ library with a global.
- A C++ executable with a global, dynamically opening a C library with a global.
- A C++ executable without a global, but dynamically opening a C++ library which dynamically opens another C++ library.
- A C++ executable without a global, but dynamically opening a C++ library which dynamically opens another C++ library, forgetting to close the first C++ library.
- A C++ executable with a global, dynamically opening a C++ library with a global, but using "_exit" for termination.
- A C++ executable with a local, dynamically opening a C++ library with a global.
Looking at the output of the tests, we can see that cases #2 and #5 are problematic.
Output of case #2:
./dlopen_cxxBar.cxx.bin Foo::Foo() Bar::Bar() void Foo::openBar(const char*)- handle:0x804b020 Bar::~Bar() Foo::~Foo() Bar is invalid -> statics are BAD! closing Bar...
Output of case #5:
./dlopen_cxxFoo_wo_closing.cxx.bin int main()- handle(libFoo.cxx.so):0x804a020 Foo::Foo() Bar::Bar() void Foo::openBar(const char*)- handle:0x804a3e0 Bar::~Bar() Foo::~Foo() Bar is invalid -> statics are BAD! closing Bar...
These are exactly the casing we are unfortunately facing in Uno and OOo. Obviously case #2 can be solved by utilizing the uno.exe to bring up an application, which is the right thing anyway. The solution for case #5 relies on correctly closing all dynamically shared libraries before actually terminating, which does not seem to work reliable currently!
Uno Objects
As a client of some service or object typically does not (should not) know, if the particular object is implemented in another library or uses globals for its implementation, any global references to Uno objects need to be cleared before termination.
Links
- Win32 "FreeLibrary" - http://msdn2.microsoft.com/en-us/library/ms683152.aspx
- MS solution for the atexit problem - http://msdn2.microsoft.com/en-us/library/7977wcck(vs.71).aspx
Samples
Foo.hxx
struct Foo { void * m_dlhandle; Foo(); ~Foo(); void openBar(char const * libName); }; extern "C" void openBar(char const * libName);
Bar.cxx
#include <iostream> struct Bar { int m_a; Bar(); ~Bar(); }; Bar::Bar() : m_a(1) { std::cerr << "Bar::Bar()" << std::endl; } Bar::~Bar() { m_a = 0; std::cerr << "Bar::~Bar()" << std::endl; } static Bar bar; extern "C" int Bar_checkValid() { return bar.m_a; }
Foo.cxx
#include "Foo.hxx" #include <iostream> #include <dlfcn.h> Foo::Foo() : m_dlhandle(RTLD_DEFAULT) { std::cerr << "Foo::Foo()" << std::endl; } Foo::~Foo() { std::cerr << "Foo::~Foo()" << std::endl; int (*Bar_checkValid )() = (int (*)())dlsym(m_dlhandle, "Bar_checkValid"); if (Bar_checkValid()) std::cerr << "\tBar is valid -> statics are OK!" << std::endl; else std::cerr << "\tBar is invalid -> statics are BAD!" << std::endl; if (m_dlhandle) { std::cerr << "\tclosing Bar..." << std::endl; dlclose(m_dlhandle); } } void Foo::openBar(char const * libName) { m_dlhandle = dlopen(libName, RTLD_NOW); std::cerr << "Foo::openBar(char const * libname)" << "- handle:" << m_dlhandle << std::endl; } extern "C" void openBar(char const * libName) { static Foo foo; foo.openBar(libName); }
dlopen_cBar.cxx
#include "Foo.hxx" static Foo foo; int main() { foo.openBar("libBar.c.so"); return 0; }
dlopen_cxxBar.cxx
#include "Foo.hxx" static Foo foo; int main() { foo.openBar("libBar.cxx.so"); return 0; }
dlopen_cxxBar_uexit.cxx
#include "Foo.hxx" #include <unistd.h> static Foo foo; int main() { foo.openBar("libBar.cxx.so"); _exit(0); }
dlopen_cxxFoo.cxx
#include "Foo.hxx" #include <dlfcn.h> #include <iostream> int main() { void * dlhandle = dlopen("libFoo.cxx.so", RTLD_NOW); std::cerr << "main" << "- handle(libFoo.cxx.so):" << dlhandle << std::endl; void (* openBar)(char const *) = (void (*)(char const * libNanme))dlsym(dlhandle, "openBar"); openBar("libBar.cxx.so"); dlclose(dlhandle); // Please note how important this is, not calling it renders // this test invalid! return 0; }
dlopen_cxxFoo_wo_closing.cxx
#include "Foo.hxx" #include <dlfcn.h> #include <iostream> int main() { void * dlhandle = dlopen("libFoo.cxx.so", RTLD_NOW); fprintf(stderr, "main - handle(libFoo.cxx.so): %p\n", dlhandle); void (* openBar)(char const *) = (void (*)(char const * libNanme))dlsym(dlhandle, "openBar"); openBar("libBar.cxx.so"); //dlclose(dlhandle); // Please note how important this is, not calling it renders // this test invalid! return 0; }
localFoo.cxx
#include "Foo.hxx" #include <dlfcn.h> #include <iostream> int main() { void * dlhandle = dlopen("libBar.cxx.so", RTLD_NOW|RTLD_GLOBAL); std::cerr << "main" << "- handle(libBar.cxx.so):" << dlhandle << std::endl; static Foo foo; foo.m_dlhandle = dlhandle; return 0; }
plain.cxx
#include "Foo.hxx" static Foo foo; int main() { return 0; }
.SUFFIXES: ifeq ($(VENDOR),sun) CC := cc CXX := CC SHARED := -G CPPRT := -lCstd CFLAGS := -KPIC RPATH := -R. else CC := gcc CXX := g++ SHARED=-shared CPPRT := CFLAGS := -fpic RPATH := -Wl,-rpath,. endif LINK_CC := $(CC) $(RPATH) LINK_CC_LIB := $(LINK_CC) $(SHARED) $(RPATH) LINK_CXX := $(CXX) $(CPPRT) $(RPATH) LINK_CXX_LIB := $(LINK_CXX) $(SHARED) $(RPATH) All: run build: \ plain.cxx.bin \ dlopen_cBar.cxx.bin libBar.c.so \ dlopen_cxxBar.cxx.bin \ dlopen_cxxFoo.cxx.bin \ libBar.cxx.so \ libFoo.cxx.so \ dlopen_cxxBar_uexit.cxx.bin \ localFoo.cxx.bin \ dlopen_cxxFoo_wo_closing.cxx.bin run: build ./plain.cxx.bin ./dlopen_cBar.cxx.bin ./dlopen_cxxBar.cxx.bin ./dlopen_cxxFoo.cxx.bin ./dlopen_cxxBar_uexit.cxx.bin ./localFoo.cxx.bin ./dlopen_cxxFoo_wo_closing.cxx.bin clean: rm -f *.o *.so *.bin .PHONY: All build run clean %.cxx.o: %.cxx $(CXX) -g -c $(CFLAGS) $^ -o $@ %.c.o: %.c $(CC) -g -c $(CFLAGS) $^ -o $@ lib%.c.so: %.c.o $(LINK_CC_LIB) $^ -o $@ lib%.cxx.so: %.cxx.o $(LINK_CXX_LIB) $^ -o $@ %.cxx.bin: %.cxx.o $(LINK_CXX) $^ -o $@ -ldl plain.cxx.bin: plain.cxx.o Foo.cxx.o libBar.cxx.so dlopen_cBar.cxx.bin: dlopen_cBar.cxx.o Foo.cxx.o dlopen_cxxBar.cxx.bin: dlopen_cxxBar.cxx.o Foo.cxx.o dlopen_cxxFoo.cxx.bin: dlopen_cxxFoo.cxx.o localFoo.cxx.bin: localFoo.cxx.o Foo.cxx.o dlopen_cxxBar_uexit.cxx.bin: dlopen_cxxBar_uexit.cxx.o Foo.cxx.o dlopen_cxxFoo_wo_closing.cxx.bin: dlopen_cxxFoo_wo_closing.cxx.o