views:

485

answers:

4

Hello together.

I have some trouble figuring out why the following crashes (MSVC9):

//// the following compiles to A.dll with release runtime linked dynamically
//A.h
class A {
  __declspec(dllexport) std::string getString();
};
//A.cpp
#include "A.h"
std::string A::getString() {
   return "I am a string.";
}

//// the following compiles to main.exe with debug runtime linked dynamically
#include "A.h"
int main() {
   A a;
   std::string s = a.getString();
   return 0;
} // crash on exit

Obviously (?) this is due to the different memory models for the executable and DLL. Could it be that the string A::getString() returns is being allocated in A.dll and freed in main.exe?

If so, why - and what would be a safe way to pass strings between DLLs (or executables, for that matter)? Without using wrappers like shared_ptr with a custom deleter.

+1  A: 

This might be because the DLL and the EXE are compiled with different CRT settings. So when you pass a string, some resource conflict happens. Check your project settings for both the DLL and the executable.

Kerido
I deliberatley chose those different settings to test where pitfalls in this configuration would lie. This string issue just left me wondering because I thought the memory mgmt would happen entirely in each the DLL or the EXE.
msiemeri
+2  A: 

Could it be that the string A::getString() returns is being allocated in A.dll and freed in main.exe?

Yes.

If so, why - and what would be a safe way to pass strings between DLLs (or executables, for that matter)? Without using wrappers like shared_ptr with a custom deleter.

Using a shared_ptr sounds like a sensible thing to me. Remember, as a rule of thumb, allocations and deallocations should be done by the same module to avoid glitches like these.

Exporting STL objects across dlls is at best a tricky pony. I suggest you check out this MSDN KB article first and this post.

dirkgently
Thanks for the article links +1
msiemeri
Uhmm. This won't really work if the class's layout is different between DLLs? Attempting to access the class will still fail. (Deallocate/release the shared_ptr and/or pass back to the DLL will work, but attempting to use it won't)
Marcus Lindblom
A: 

You need to link to the same runtime lib (the DLL one), either debug or release, for every DLL in your app where memory is allocated in one and freed in another. (The reason for using the dynamically linked runtime lib is that then there will be one heap for your entire process, as opposed to one per dll/exe that links to the static one.)

This includes returning std::string and stl-containers by value, as that is what you do.

The reasons are twofold (updated section):

  • the classes have different layouts/sizes, so differently compiled code assumes data is in different places. Whoever created it first gets right, but the other one will cause a crash sooner or later.
  • the msvc heap-implementations are different in each runtime-lib which means that if you try to free a pointer in the heap that didn't allocate it, it will go bonkers. (This happens if the layouts are similar, i.e. where you outlive the first case.)

So, get your runtime libs straight, or stop freeing/allocating in different dlls (i.e. stop passing stuff by value).

Marcus Lindblom
downvote without explanation? .. I'd like to know what's wrong.
Marcus Lindblom
Your answer states that the crash ia being caused by mismatched memory allocation functions, but it's actually being caused by mismatched std::string definitions.
Joe Gauterin
Ok. Thanks. In this case it might be the different sizes of the object, but if the std::string would've had similar layout in debug/release, heap allocation would've bitten him anyway.
Marcus Lindblom
+13  A: 

This isn't actually being caused by differing heap implementations - the MSVC std::string implementation doesn't use dynamically allocated memory for strings that small (it's got a built in buffer). The CRTs do need to match, but that isn't what bit you this time.

What's happening is that you're invoking undefined behaviour by violating the One Definition Rule.

The release and debug builds will have different preprocessor flags set, and you'll find that std::string has a different definition in each case. Ask your compiler what sizeof(std::string) is - MSVC10 tells me that it's 32 in a debug build and 28 in a release build (this isn't padding - 28 and 32 are both 4 byte boundaries).

So what's happening? Variable s is initialized using the debug version of the copy constructor to copy a release version of std::string. The offsets of the member variables are different between the versions, so you copy garbage. The MSVC implementation effectively stores begin and end pointers - you've copied garbage into them; because they're no longer null, the destructor tries to free them and you get an access violation.

Even if the heap implentations were the same it would crash, as you're freeing garbage pointers to memory that was never allocated in the first place.


In summary: the CRT versions need to match but so do the definitions - including the definitions in the standard library.

Joe Gauterin
Nice analysis, +1.
Mark Ransom