Maintaining state across a DLL boundary - help very much needed

Started by
10 comments, last by hymerman 17 years, 4 months ago
Hi all, I'm now abandoning my all-singing all-dancing classloader I'm having troubles with in this thread (unless someone gives me a solution!), and using an inferior but simpler alternative instead. However, I'm having absolutely no luck getting anything to work across a DLL boundary, full stop. Can any of you tell me a foolproof way to make sure (as an example) to ensure something added to a std::map in the main application from code in a DLL stays in the map for use by the rest of the application? A bit of code will go a very long way. I've tried all sorts of combinations of __declspecs and externs and statics and compiler/linker settings, but it just won't work. If it's of any use, I'm using MSVC2005. Thanks in advance!
Advertisement
You must use an exported accessor function, or an interface, to the instance of the map in the dll - otherwise, I'm guessing the problem you're having, is that the map is getting instantiated in each module.

So, for example, in my main DLL I have some code that looks like this in a source file called Factory.cpp

namespace{	typedef std::map< UUID, Factory::FactoryFunc > FactoryMapT;	FactoryMapT& GetFactoryMap()	{		static FactoryMapT fm;		return fm;	}}void Factory::Register( const UUID& name, FactoryFunc func ){	FactoryMapT& fm = GetFactoryMap();	Debug::Assert( fm.find( name ) == fm.end(),		L"That CLSID has already been registered." );	fm.insert( FactoryMapT::value_type( name, func ) );}


And in my Factory.h file, I export a couple of methods of the Factory class:

class Factory{public:	typedef Object*(*FactoryFunc)();	CORE_API static void Register( const UUID& clsid, FactoryFunc func );	CORE_API static Object* CreateObject( const UUID& clsid );};


note, that CORE_API is defined as

#if defined WIN32#if defined CORE_EXPORTS	#define CORE_API __declspec( dllexport )#else	#define CORE_API __declspec( dllimport )#endif#elif defined LINUX#define CORE_API#else#error "platform not supported."#endif


Hope this helps.
From what I've heard you shouldn't pass STL objects across DLL boundaries.

Something to do with the DLL having a seperate heap.

I wish I could be more specific, but Im sure someone more knowlegable will be able to expand on ( or disprove [smile] ) what I'm saying.
Quote:Original post by rip-off
From what I've heard you shouldn't pass STL objects across DLL boundaries.

Something to do with the DLL having a seperate heap.

I wish I could be more specific, but Im sure someone more knowlegable will be able to expand on ( or disprove [smile] ) what I'm saying.


This is basically true as a general rule. You never want to pass a reference to or a copy of an STL object across a DLL boundary unless you guarantee yourself that everything is built with the same STL code and C runtime libraries.

But this also holds true with a variety of other objects, not just STL.

Ah, don't worry, that was just an example. Actually I'm doing much the same thing as you; passing a pointer to a factory function and an identifier for that, to be registered in a factory. I can step through the code and see that the classes in the DLLs are statically calling the register methods, but when I come to read from the factory during main (as a test), there is nothing in it. I'm sure it's to do with the factory getting instantiated multiple times in different address spaces, but I didn't know how to stop this.

I do have a quick question for you, krum: where are each of those code snippets from? Which are compiled into DLLs and which are part of the application/API? What I guess I'm really asking is what parts need to be exported, and where?
Basically, what you must do is ensure that you don't have two things one in main program other in DLL. It looks like your problem is that your global data exist in two instances one in the DLL and other in main app. There isn't any magical transfers of changes. It's just that either main app and dll is using two different or two same datas.

Instead of putting line like
std::map lalala;
in header, you must put something like that in header
[dllexport or whatever] std::map &GetLalala();
and put the
std::map lalala;
only in one dll or only in main app , together with implementation of GetLalala(); that should return it.
Quote:Original post by hymerman
krum: where are each of those code snippets from? Which are compiled into DLLs and which are part of the application/API? What I guess I'm really asking is what parts need to be exported, and where?


Factory.cpp is compiled into a core.dll.

Consumers would need to include Factory.h - which also includes some static methods to ease object creation. In fact, here's the whole Factory.h:

#ifndef included_pyx_core__Factory_h#define included_pyx_core__Factory_h#include "core/Core.h"#include "core/Object.h"BEGIN_NAMESPACE_PYX;//////////////////////////////////////////////////////////////////////////class Factory{public:	typedef Object*(*FactoryFunc)();	CORE_API static void Register( const UUID& clsid, FactoryFunc func );	CORE_API static Object* CreateObject( const UUID& clsid );};// --------------------------------------------------------------------------template< class T >inline T* CreateObject(){	Object* obj = Factory::CreateObject( T::GetClassID() );	return static_cast<T*>( obj );}// --------------------------------------------------------------------------template< class T >inline bool CreateObject( const UUID& clsid, T** result ){	*result = static_cast<T*>( Factory::CreateObject( clsid ) );	return *result != 0;}// --------------------------------------------------------------------------template< class T >inline bool CreateObject( const UUID& clsid, boost::shared_ptr<T>& result ){	T* obj = static_cast<T*>( Factory::CreateObject( clsid ) );	result = boost::shared_ptr< T >( obj );	return obj != 0;}// --------------------------------------------------------------------------template< class T >class AutoRegister{public:	AutoRegister()	{		Factory::Register( T::GetClassID(), T::CreateObject );	}};// --------------------------------------------------------------------------END_NAMESPACE;#endif // included_pyx_core__Factory_h


Consumers usually include Factory.h either in their precompiled header, or in a source file that uses it (since objects that register with Factory use the AutoRegister class). Usually the only consumer for the Factory is the serialization engine; an application developer wouldn't normally use this to create an object unless they wanted to create an object that had an abstract base type (like the renderer).

Also, come to think of it, is your map instance in a static library that you're linking with the DLLs? If so, that would definitely result in the behavior that you describe.
Thanks very much for that, krum, I think that's pretty much exactly how I'm going to have to implement it now.

Dymytry: Okay, I now see why I have to do it this way. If my actual code were as simple as just a single std::map used as a factory, I'd do that straight away, but unfortunately I'm using a templated class, and my understanding of templated classes is that the implementation must go in the same header file, as mentioned at the bottom of this page. So, is what I'm trying to do basically impossible? Do the requirements of having to separate implementation and declaration, as well as keep them together conflict to such a point that I have to choose one or the other? It's such a shame if that's the case, since otherwise I'd have a pretty swish classloader :(
Actually they don't have to go into the same header. I use .inl files for "inline" code like templates. You just put in a #include "somefilename.inl". I find it keeps code a lot cleaner and does the same thing as having it in the header. Anyways, hope your issue is solved.

This topic is closed to new replies.

Advertisement