Using STL Objects Across DLL Boundaries

| | Comments (3) | TrackBacks (0)

Ever had one of these problems with using STL objects across DLL boundaries?

  • Export from a DLL a class containing STL objects
  • Access violations with objects passed between DLLs and EXEs

Export from a DLL a class containing STL objects

Apparently there is a compiler limitation for exporting from a DLL standard STL types or classes that contain STL classes. The .NET 2003+ Microsoft compiler (haven't tested older versions) has built in support exporting std::string and std::vector< T >. See Microsoft knowledge base article (http://support.microsoft.com/default.aspx?scid=kb%3ben-us%3b168958) for more details on why other classes aren't supported.  If you take the time, all STL classes can be exported, it's just not a lot of fun redefining all the ancillary methods needed for the proper dll-interface, including the allocator methods.

For instance, if you have exported a class from a DLL that internally uses a std::map< T, U > as in the following code, you get compiler warnings...warnings you shouldn't ignore.

class ENGINE_API PluginManager
{
public:
PluginManager();
~PluginManager();
void Load();
void Init();
IDataProvider* GetDataProvider( string InheritedType );
ISyncProvider* GetSyncProvider( string Guid );
vector< IDataProvider* > GetDataProviders();
vector< ISyncProvider* > GetSyncProviders();
vector< IProvider* > GetProviders();
private:
vector< string > FindDlls( string sStartPath );
map< string, ISyncProvider* > m_SyncProviderMapping;
map< string, IDataProvider* > m_DataProviderMapping;
};

The compiler will output warnings like:

c:\My Projects\Engine\PluginManager.h(56) : warning C4251: 'PluginManager::m_SyncProviderMapping' : class 'std::map<_Kty,_Ty>' needs to have dll-interface to be used by clients of class 'PluginManager'
with
[
_Kty=std::string,
_Ty=Idria::ISyncProvider *
]
c:\My Projects\Engine\PluginManager.h(57) : warning C4251: 'PluginManager::m_DataProviderMapping' : class 'std::map<_Kty,_Ty>' needs to have dll-interface to be used by clients of class 'PluginManager'
with
[
_Kty=std::string,
_Ty=Idria::IDataProvider *
]

There are a few tricks you can use depending on how transparent you want it to be.

  1. Wrap the STL containers with or without accessors (encapsulate data members, or just make them public) into a non-exported class. You could then forward-declare the class and completely hide the implementation from someone peeking into your header files.
  2. My favorite way is to not create an entire class, but rather create a locally-scoped private structure within the class you are exporting. This doesn't cause the compiler to think that you need to export all the STL ancillary methods for the templated class. Example code:

private:
vector< string > FindDlls( string sStartPath );
struct PluginManager_impl
{
map< string, ISyncProvider* > m_SyncProviderMapping;
map< string, IDataProvider* > m_DataProviderMapping;
};
PluginManager_impl *m_PluginManager;
};

Now, in your class constructor, allocate a PluginManager_impl struct and use the std::map< T, U > objects directly. The implementation isn't hidden, but it's simple and effective.

As mentioned above, the compiler has built-in support for std::string and std::vector< T > so if you are using those, this trick isn't necessary, just ignore the warnings as recommend by Microsoft by using the following pragma:

#pragma warning( disable: 4251 )

Access violations with objects passed between DLLs and EXEs

Recently on a project, I ran into error messages like this:

ASSERT!
_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)
or
>HEAP[MyProgram.exe]: Invalid Address specified to RtlValidateHeap( 00360000, 00353FA0 )
Unhandled exception at 0x77f7f570 in MyProgram.exe.exe:
User breakpoint.

The code looked simple enough, I was returning a std::string object from a class to another DLL. I wasn't passing by reference or pointer as a Microsoft knowledge base article warned me about (http://support.microsoft.com/default.aspx?scid=kb%3ben-us%3b172396). Rather, I was passing by value as shown in the code:

string MyNewString = ExternalDllGetString();

In my case, if the string was larger than about 10 characters, I received the access violation.

Remember C 101 that when you return an object by value, a copy of it is made. If this is a class, the copy constructor is called. Therefore, when my object allocated in one DLL was returned passed by value to another DLL or EXE, the allocation and deallocations were occuring in different heap managers since it was across the DLL boundary. After the line of code was executed, the deconstructor for that newly passed value is called causing the access violation.

If you are using different runtime libraries in your DLLs, you will get access violations very quickly because each runtime has its own heap manager and when memory is allocated with one heap manager, it can't be deallocated by a different one.

To fix this issue, all libraries need to use the same runtime library, i.e. /MD, /MDd.

0 TrackBacks

Listed below are links to blogs that reference this entry: Using STL Objects Across DLL Boundaries.

TrackBack URL for this entry: http://www.kcfilms.com/MT/mt-tb.cgi/20

3 Comments

Hey,

thanks for this! It's late, I'm tired and I'm baffled why I'm getting heap errors after my plugins return std::strings. Never even thought to check the code generation options, though it makes total sense when I see it in text.

Thanks again :-)

Kev

Note to self: stop working at a reasonable time....

Hi there,

This std::string being copied from another DLL problem might be why my Lua and LuaBind dlls are blowing up. ;-)
Thanks for the lead...

Nigel

Hi,
i have a return std::sting in dll issue.
Thanks for the explaination! Helps a lot!

Leave a comment

Verification (needed to reduce spam):

About this Entry

This page contains a single entry by Shawn published on January 26, 2006 1:57 PM.

Newest Galleries and Website News was the previous entry in this blog.

The Dreaded "[DBNETLIB][ConnectionOpen (Connect()).]SQL Server does not exist or access denied" Error is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.