Why does this program crash: passing of std::string between DLLs
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 mod开发者_StackOverflow社区els 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.
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 uses the small string optimization). 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 bytes` 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 implementations 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.
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.
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).
In addition to what has been said above, make sure that the Platform Toolset (under Properties->General) is identical in both projects. Otherwise the string content at the arriving side might be bogus.
That happened to me when a console application project with v100 toolset version consumed a library that was set to v90.
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.
Ensure both projects (App & Dll) are using one of the "Multi threaded DLL" runtime libraries & not the static version.
Properties --> C/C++ --> Code Generation--> (/MD or /MDd)
NOTE: If you use any 3rd party libraries in your app then you may also need to recompile those, the linker will genrally notify you of this with mismatch/duplicate runtime errors.
精彩评论