Uno/Cpp/Tutorials/Global References

From Apache OpenOffice Wiki
< Uno‎ | Cpp‎ | Tutorials
Jump to: navigation, search

State: draft

Todos
  • make this a tutorial
  • move the tests to an analysis page
  • add a comment (and test :-) for cyclic references
  • add conclusion that dlopened libraries need to be closed properly
  • enable building on Windows ...

Using global references in C++ Uno and how they interfere with "atexit".

Preface

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). Obviously, such a shared library needs to get 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.

class FooBar;
 
uno::Reference<uno::XInterface> g_hold;
 
void myFun() {
  if (!g_hold.is()) {
    uno::Reference<uno::XInterface> tmp(new FooBar());
 
    g_hold = tmp; // This may need 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 a 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 may already be 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.

Tests

I prepared some tests (see the source below) to actually make the C++ vs. shared libraries problem 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 the 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 cases we are unfortunately facing in Uno and OOo. Obviously case #2 can be solved by utilizing the uno.exe to bring up an application (respectively by not using global references in the executables implementation respectively clearing them in time), 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!

GCC

For gcc to follow the above C++ specification '__cxa_atexit' must be enabled. In case it is not enabled, all above tests succeed. Here is the 'gcc -v' output of my local gcc I could reproduce this with:

kr@localhost /usr/local/kr/tests/> gcc -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --enable-languages=c,c++,fortran,objc,obj-c++,treelang --prefix=/usr --enable-shared --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.1.3 \
 --program-suffix=-4.1 --enable-__cxa_atexit --enable-clocale=gnu --enable-libstdcxx-debug --enable-mpfr \
 --enable-checking=release i486-linux-gnu
Thread model: posix
gcc version 4.1.3 20070812 (prerelease) (Debian 4.1.2-15)

And the configuration of the gcc with '__cxa_atexit' disabled (note: this is actually the gcc Hamburg RE builds with, the reason '__cxa_atexit' being disabled is base line compliance):

kr@localhost /usr/local/kr/tests/> gcc -v
Reading specs from gcc_3.4.1_p_linux_libc2.24/lib/gcc/i686-pc-linux-gnu/3.4.1/specs
Configured with: ./configure --prefix=gcc_3.4.1_p_linux_libc2.24 --with-gnu-as --with-as=gcc_3.4.1_p_linux_libc2.24/bin/as \
 --with-gnu-ld --with-ld=gcc_3.4.1_p_linux_libc2.24/bin/ld --enable-languages=c,c++
Thread model: posix
gcc version 3.4.1
See GCC __cxa_atexit for (hopefully) correct information about this. -- SB, Aug 27, 2007

Global Uno References

//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

The tests currently build with Gnu make on Linux and Solaris.

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