Doing something on shared library initialization

January 20, 2005

http://interreality.org/~reed

Abstract

For convenience, maintainability and clean interfaces, it is sometimes desirable for a dynamically linked library to perform some initialization work before the execution of the main program. Unfortunately, the behaviour of the dynamic linker (most notable on Mac OSX) can cause such initialization to be unpredictable or omitted intirely. This brief paper considers some possible solutions that can be used in C++, concluding with one best solution.

The problem

VOS is a network-distributed object system imprementation, in which objects can be extended by objects implementing specific "types" or interfaces for those objects. The type implementations are generated by a (typically) global factory according to a string identifying the type. We wanted to make it easy for the user to add types to his application simply by linking to the shared library defining them, without worrying about registering the type with the factory on program initialization. This means we need some code to be automatically executed at program startup, before main() is entered.

Possible solutions

Many dynamic linkers call a special function when a library is loaded. Linux's ld calls _init, and Windows calls DllMain (Though you can specify alternate names for these, they take platform specific arguments, so you would need to define both functions). I have found this method to be complex. GCC says that such functions need to be defined with __attribute__ ((constructor)) prepended but that doesn't seem to do much. Each linker uses a completely different init function convention, requiring you to anticipate all possible platforms for your code. Furthermore, you cannot be sure that the library will be loaded when your program is (see below for a way to force the library to be loaded).

(By the way, you can also use a diferent name for _init by passing the -init name option to Linux ld)

It turns out there is a very simple way to do it using just C++. You simply define a static object, which does what you want in its constructor, for example, this is a simple class which registers a generator callback with a global (static) "factory" object in its constructor:


class ObjectGenerator {
public:
    ObjectGenerator() {
        GlobalFactory::registerGenerator(&generate);
    }
    static Object* generate() {
        return new Object();
    }
};

By creating a static instance of this class inside the library, we can try to get the registration to happen on program initialization:

static ObjectGenerator GlobalStaticObjectGeneratorInstance;

More Problems!

But there is a big problem with this: it simply fails to work on Mac OSX and Windows! It turns out that the static instance is not actually initialized when the library is loaded, or, the library isn't loaded until it is used (function called, or other symbol referenced). Therefore our action (generator registration) will not be done before entering main(). It will be initialized when used or otherwised referenced. My solution was to add this bit of code to a header file included by the program: redeclare the static instance as extern, then actually make a reference to it, which forces the library to be loaded if it is not, and for the static object to be created:

class ObjectGenerator;
extern ObjectGenerator GlobalStaticObjectGeneratorInstance;
static ObjectGenerator& LocalObjectGeneratorReference = GlobalStaticObjectGeneratorInstance;

Conclusion

So it turns out there are only two working solutions: to use the library initialization functions specific to each platform, or to use a static object, then somehow reference library symbols in our main program, thereby forcing the linker to finish loading the library and initializing a static object. Since it simply uses the C++ language, I like the second method; it ought to be completely portable as well. The only downside is that you must include the local reference in your program, which means that you cannot simply link shared libraries to the program, you also have to modify the program code to reference the library.

You can hide this static-object trick in some macros if you want, which is what we do for VOS (DLLEXPORT is a macro for the annoying Windows DLL export attributes, or empty on other platforms):


/** @def BEGIN_REGISTER_METAOBJECT_FACTORIES
    Use these macros to define a static registration class for
    a metaobject.  This lets you register metaobject factories automatically
    when your library is loaded.

    You must also use the REGISTERS_METAOBJECT_FACTORIES macro in a
    public header file as well.

    Example:
    @code
        BEGIN_REGISTER_METAOBJECT_FACTORIES(Example)
            Site::addLocalMetaObjectFactory(typeid(Example).name(), &LocalExample::new_LocalExample);
            Site::addLocalMetaObjectFactory(typeid(LocalExample).name(), &LocalExample::new_LocalExample);
            Site::addLocalMetaObjectFactory("example:example", &LocalExample::new_LocalExample);

            Site::addRemoteMetaObjectFactory(typeid(Example).name(), "example:example", &RemoteExample::new_RemoteExample);
            Site::addRemoteMetaObjectFactory(typeid(RemoteExample).name(), "example:example", &RemoteExample::new_RemoteExample);
            Site::addRemoteMetaObjectFactory("example:example", "example:example", &RemoteExample::new_RemoteExample);
        END_REGISTER_METAOBJECT_FACTORIES(Example)
      @endcode

      If your metaobject does not need seperate Local and Remote behavior,
      you only need one class, for which you can register the same remote
      and local factory.

      @param cl A symbol that uniquely identifies this registration (e.g. use
      the MetaObject class name)
    */
// @{
#define BEGIN_REGISTER_METAOBJECT_FACTORIES(cl)    \
    class Register##cl {                           \
    public:                                        \
        Register##cl()                             \
        {

#define END_REGISTER_METAOBJECT_FACTORIES(cl)      \
        }                                          \
    };                                             \
    DLLEXPORT Register##cl Register##cl##_globalstatic;
// @}

#define BEGIN_REGISTER_EXTENDERS(cl) BEGIN_REGISTER_METAOBJECT_FACTORIES(cl)
#define END_REGISTER_EXTENDERS(cl) END_REGISTER_METAOBJECT_FACTORIES(cl)

/** Alternate macro for registering extender factories.
    Use this macro when you want to use the same MetaObject class for
    both local and remote objects, and don't need to do anything special
    at registration.  It creates a registration class named from
    a namespace and class name, containing a constructor which registers
    the same factory function for local and remote metaobject creation.

    You must also use the REGISTERS_METAOBJECT_FACTORIES macro in a
    public header file as well, using <ns>_<cl> as its argument (see below)

    @param ns   Namespace
    @param cl   Class name
    @param type Type string, should be the same string that getVOSType()
returns.
    @param factfn Static method that returns an instance of your class (as
        a VOS::MetaObject*), given a "super object" (of type VOS::VobjectBase*)
        and a type string.

    For example, imagine you have a metaobject class called ExampleClass.
    It is in a namespace called EG.  It might be declared in a header file like
    this:
    @code
        #include <vos/vos/vos.hh>

        ...
        namespace EG {
            class ExampleClass : public VOS::MetaObject {
            public:
                ExampleClass(VOS::VobjectBase* s);
                VOS::MetaObject* new_ExampleClass(VOS::VobjectBase* s, const std::string& t) {
                   return new ExampleClass(s);
                }
                ...
            };
        }
    @endcode

    Somewhere in the definition (i.e. in your ".cc" or ".cpp" file), include
    this macro call:
    @code
        REGISTER_METAOBJECT_FACTORIES(EG, ExampleClass, "example:example", &EG::ExampleClass::new_ExampleClass)
        }
    @endcode


    Now to the public header file, add this:
    @code
        ...
        REGISTERS_METAOBJECT_FACTORIES(EG_ExampleClass)
        ...
    @endcode
*/
#define REGISTER_METAOBJECT_FACTORIES(ns, cl, type, factfn) \
    class Register##ns##_##cl { \
    public: \
        Register##ns##_##cl() { \
            Site::addLocalMetaObjectFactory(typeid(ns::cl).name(), factfn); \
            Site::addLocalMetaObjectFactory(type, factfn); \
            Site::addRemoteMetaObjectFactory(typeid(ns::cl).name(), type, factfn); \
            Site::addRemoteMetaObjectFactory(type, type, factfn); \
        } \
    }; \
    DLLEXPORT Register##ns##_cl Register##ns##_cl##_globalstatic;

/** You must use this macro in a public header file for every use of
 * VOS_METAOBJECT_FACTORY or BEGIN_REGISTER_EXTENDERS. (It grabs a reference
 *  to the registration cleass, forcing the dynamic library loader to
 *  initialize it)
 * @param cl    Class name: use the same argument you used in defining the
 *              factory registration class.
 */
#define REGISTERS_METAOBJECT_FACTORIES(cl) \
    class Register##cl; \
    extern DLLIMPORT Register##cl Register##cl##_globalstatic; \
    static Register##cl &Register##cl##_localref = Register##cl##_globalstatic;



http://interreality.org/~reed