tags:

views:

111

answers:

2

Hi,

the context

I'm working on a project having some "modules". What I call a module here is a simple class, implementing a particular functionality and derivating from an abstract class GenericModule which force an interface.

New modules are supposed to be added in the future.

Several instances of a module can be loaded at the same time, or none, depending on the configuration file.

I though it would be great if a future developer could just "register" his module with the system in a simple line. More or less the same way they register tests in google test.

the context² (technical)

I'm building the project with visual studio 2005. The code is entirely in a library, except the main() which is in an exec project. I'd like to keep it that way.

my solution

I found inspiration in what they did with google test.

I created a templated Factory. which looks more or less like this (I've skipped uninteresting parts to keep this question somewhat readable ):

class CModuleFactory : boost::noncopyable
{
public:
    virtual ~CModuleFactory() {};

    virtual CModuleGenerique* operator()(
        const boost::property_tree::ptree& rParametres ) const = 0;
};

template <class T>
class CModuleFactoryImpl : public CModuleFactory
{
public:
    CModuleGenerique* operator()(
        const boost::property_tree::ptree& rParametres ) const
    {
        return new T( rParametres );
    }
};

and a method supposed to register the module and add it's factory to a list.

class CGenericModule
{
    // ...
    template <class T>
    static int declareModule( const std::string& rstrModuleName )
    {
        // creation de la factory
        CModuleFactoryImpl<T>* pFactory = new CModuleFactoryImpl<T>();

        // adds the factory to a map of "id" => factory
        CAcquisition::s_mapModuleFactory()[rstrModuleName ] = pFactory;

        return 0;
    }
};

now in a module all I need to do to declare a module is :

static int initModule =
acquisition::CGenericModule::declareModule<acquisition::modules::CMyMod>(
    "mod_name"
    );

( in the future it'll be wrapped in a macro allowing to do

DECLARE_MODULE( "mod_name", acquisition::modules::CMyMod );

)

the problem

Allright now the problem.

The thing is, it does work, but not exactly the way i'd want.

The method declareModule is not being called if I put the definition of the initModule in the .cpp of the module (where I'd like to have it) (or even in the .h). If I put the static init in a used .cpp file .. it works.

By used I mean : having code being called elsewhere.

The thing is visual studio seems to discard the entire obj when building the library. I guess that's because it's not being used anywhere.

I activated verbose linking and in pass n°2 it lists the .objs in the library and the .obj of the module isn't there.

almost resolved?

I found this and tried to add the /OPT:NOREF option but it didn't work. I didn't try to put a function in the .h of the module and call it from elsewhere, because the whole point is being able to declare it in one line in it's file.

Also I think the problem is similar to this one but the solution is for g++ not visual :'(

edit: I just read the note in the answer to this question. Well if I #include the .h of the module from an other .cpp, and put the init in the module's .h. It works and the initialization is actually done twice ... once in each compilation unit? well it seems it happens in the module's compilation unit ...

side notes

Please if you don't agree with what I'm trying to do, fell free to tell, but I'm still interested in a solution

+1  A: 

I've got something similar based on the code from wxWidgets, however I've only ever used it as a DLL. The wxWidgets code works with static libs however.

The bit that might make a difference is that in wx the equivelant of the following is defined at class scope.

static int initModule =
acquisition::CGenericModule::declareModule<acquisition::modules::CMyMod>(
    "mod_name"
    );

Something like the following where the creation of the Factory because it is static causes it to be loaded to the Factory list.

#define DECLARE_CLASS(name)\
class name: public Interface { \
    private: \
        static Factory m_reg;\
        static std::auto_ptr<Interface > clone();

#define IMPLEMENT_IAUTH(name,method)\
    Factory name::m_reg(method,name::clone);\
Greg Domjan
I think I've tried to put initModule at class scope but it didn't work. That was some time ago the code has 'evolved' since and I might give it an other try as soon as I have access to my computer. Thanks.
f4
A: 

If you want this kind of self-registering behavior in your "modules", your assumption that the linker is optimizing out initModule because it is not directly referenced may be incorrect (though it could also be correct :-).

When you register these modules, are you modifying another static variable defined at file scope? If so, you at least have an initialization order problem. This could even manifest itself only in release builds (initialization order can vary depending on compiler settings) which might lead you to believe that the linker is optimizing out this initModule variable even though it may not be doing so.

The module registry kind of variable (be it a list of registrants or whatever it is) should be lazy constructed if you want to do things this way. Example:

static vector<string> unsafe_static; // bad

vector<string>& safe_static()
{
    static vector<string> f;
    return f;
} // ok

Note that the above has problems with concurrency. Some thread synchronization is needed for multiple threads calling safe_static.

I suspect your real problem has to do with initialization order even though it may appear that the initModule definition is being excluded by the linker. Typically linkers don't omit references which have side effects.

If you find out for a fact that it's not an initialization order problem and that the code is being omitted by the linker, then one way to force it is to export initModule (ex: dllexport on MSVC). You should think carefully if this kind of self-registration behavior really outweighs the simple process of adding on to a list of function calls to initialize your "modules". You could also achieve this more naturally if each "module" was defined in a separate shared library/DLL, in which case your macro could just be defining the function to export which can be added automatically by the host application. Of course that carries the burden of having to define a separate project for each "module" you create as opposed to just adding a self-registering cpp file to an existing project.

in my code the only other static variable is accessed exactly as you suggest through s_mapModuleFactory(). And I know the function declareModule is being called or not because it outputs some text to the console
f4
In that case, here's a very heavy-handed way to try to force the compiler to include the module for MSVC:namespace{__declspec(dllexport) static int initModule =acquisition::CGenericModule::declareModule<acquisition::modules::CMyMod>("mod_name");}Same idea for other compilers/platforms: export the symbol."And I know the function declareModule is being called or not because it outputs some text[...]"This can be invoking undefined behavior if you are using cout. It is like writing this at file scope: static bool success = cout << "hello world"; /*cout may not be initialized*/
I'm not using cout but ACE logging mechanism which, I believe, is initialized on first use
f4