Difference between revisions of "Uno/Cpp/Tutorials/Global References"

From Apache OpenOffice Wiki
< Uno‎ | Cpp‎ | Tutorials
Jump to: navigation, search
m (Added MS atexit overwrite.)
m (Added tests and some explanations etc ...)
Line 1: Line 1:
TO BE EXTENDED SOON ...
+
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".

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:

  1. A C++ executable with a global, linked against a C++ library with a global.
  2. A C++ executable with a global, dynamically opening a C++ library with a global.
  3. A C++ executable with a global, dynamically opening a C library with a global.
  4. A C++ executable without a global, but dynamically opening a C++ library which dynamically opens another C++ library.
  5. 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.
  6. A C++ executable with a global, dynamically opening a C++ library with a global, but using "_exit" for termination.
  7. 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

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
Personal tools