For some reason, returning a string from a DLL function crashes my program on runtime with the error Unhandled exception at 0x775dfbae in Cranberry Library Tester.exe: Microsoft C++ exception: std::out_of_range at memory location 0x001ef604..
.
I have verified it's not a problem with the function itself by compiling the DLL code as an .exe
and doing a few simple tests in the main
function.
Functions with other return types (int
, double
, etc.) work perfectly.
- Why does this happen?
- Is there a way to work around this behavior?
Source code for DLL:
// Library.h
#include <string>
std::string GetGreeting();
.
// Library.cpp
#include "Library.h"
std::string GetGreeting()
{
return "Hello, world!";
}
Source code for tester:
// Tester.cpp
#include <iostream>
#include <Library.h>
int main()
{
std::cout << GetGreeting()
}
EDIT: I'm using VS2010.
Conclusion
A workaround is to make sure the library and source are compiled using the same compiler with the same options, etc.
Since your error message indicates you're using Microsoft C++ I'll offer an MS specific answer.
As long as you compile both the EXE and the DLL with the SAME compiler, and both link the the SAME version of the runtime DYNAMICALLY then you'll be just fine. For example, using "Multi-threaded DLL" for both.
If you link against the runtime statically, or link against different versions of the runtime then you're SOL for the reasons @Billy ONeal points out (memory will be allocated in one heap and freed in another).
This occurs because you're allocating memory in one DLL (using std::string's constructor), and freeing it in another DLL. You can't do that because each DLL typically sets up it's own heap.
I had such problem that has been solved by:
using runtime library as DLL for both the application and the DLL (so that allocation is handle in a unique place)
making sure all project settings (compiler, linker) are the same on both project
Update:
I have compiled your sample in both VS2008 and VS2010 and I was able to successfully compile and execute without a problem. I compiled the library both as a static and a dynamic library.
Original:
The following relates to my discussion with bdk and imaginaryboy. I did not delete it as it might be of some interest to someone.
Ok this question really bothers me because it looks like its being passed by value not by reference. There does not appear to be any objects created in the heap it looks to be entirely stack based.
I did a quick test to check how objects are passed in Visual Studios (compiled in release mode without link time optimization and optimization disabled).
The Code:
class Foo {
int i, j;
public:
Foo() {}
Foo(int i, int j) : i(i), j(j) { }
};
Foo builder(int i, int j)
{
Foo built(i, j);
return built;
}
int main()
{
int i = sizeof(Foo);
int j = sizeof(int);
Foo buildMe;
buildMe = builder(i, j);
//std::string test = GetGreeting();
//std::cout << test;
return 0;
}
The Disassembly:
int main()
{
00AD1030 push ebp
00AD1031 mov ebp,esp
00AD1033 sub esp,18h
int i = sizeof(Foo);
00AD1036 mov dword ptr [i],8
int j = sizeof(int);
00AD103D mov dword ptr [j],4
Foo buildMe;
buildMe = builder(i, j);
00AD1044 mov eax,dword ptr [j] ;param j
00AD1047 push eax
00AD1048 mov ecx,dword ptr [i] ;param i
00AD104B push ecx
00AD104C lea edx,[ebp-18h] ;buildMe
00AD104F push edx
00AD1050 call builder (0AD1000h)
00AD1055 add esp,0Ch
00AD1058 mov ecx,dword ptr [eax] ;builder i
00AD105A mov edx,dword ptr [eax+4] ;builder j
00AD105D mov dword ptr [buildMe],ecx
00AD1060 mov dword ptr [ebp-8],edx
return 0;
00AD1063 xor eax,eax
}
00AD1065 mov esp,ebp
00AD1067 pop ebp
00AD1068 ret
Foo builder(int i, int j)
{
01041000 push ebp
01041001 mov ebp,esp
01041003 sub esp,8
Foo built(i, j);
01041006 mov eax,dword ptr [i]
01041009 mov dword ptr [built],eax ;ebp-8 built i
0104100C mov ecx,dword ptr [j]
0104100F mov dword ptr [ebp-4],ecx ;ebp-4 built j
return built;
01041012 mov edx,dword ptr [ebp+8] ;buildMe
01041015 mov eax,dword ptr [built]
01041018 mov dword ptr [edx],eax ;buildMe (i)
0104101A mov ecx,dword ptr [ebp-4]
0104101D mov dword ptr [edx+4],ecx ;buildMe (j)
01041020 mov eax,dword ptr [ebp+8]
}
The Stack:
0x003DF964 08 00 00 00 ....
0x003DF968 04 00 00 00 ....
0x003DF96C 98 f9 3d 00 ˜ù=.
0x003DF970 55 10 ad 00 U..
0x003DF974 80 f9 3d 00 €ù=.
0x003DF978 08 00 00 00 .... ;builder i param
0x003DF97C 04 00 00 00 .... ;builder j param
0x003DF980 08 00 00 00 .... ;builder return j
0x003DF984 04 00 00 00 .... ;builder return i
0x003DF988 04 00 00 00 .... ;j
0x003DF98C 08 00 00 00 .... ;buildMe i param
0x003DF990 04 00 00 00 .... ;buildMe j param
0x003DF994 08 00 00 00 .... ;i
0x003DF998 dc f9 3d 00 Üù=. ;esp
Why it applies:
Even if the code is in a seperate DLL the string that is returned is copied by value into the callers stack. There is a hidden param that passes the object to GetGreetings()
. I do not see any heap getting created. I do not see the heap having anything to do with the problem.
int main()
{
01021020 push ebp
01021021 mov ebp,esp
01021023 push 0FFFFFFFFh
01021025 push offset __ehhandler$_main (10218A9h)
0102102A mov eax,dword ptr fs:[00000000h]
01021030 push eax
01021031 sub esp,24h
01021034 mov eax,dword ptr [___security_cookie (1023004h)]
01021039 xor eax,ebp
0102103B mov dword ptr [ebp-10h],eax
0102103E push eax
0102103F lea eax,[ebp-0Ch]
01021042 mov dword ptr fs:[00000000h],eax
std::string test = GetGreeting();
01021048 lea eax,[ebp-2Ch] ;mov test string to eax
0102104B push eax
0102104C call GetGreeting (1021000h)
01021051 add esp,4
01021054 mov dword ptr [ebp-4],0
std::cout << test;
0102105B lea ecx,[ebp-2Ch]
0102105E push ecx
0102105F mov edx,dword ptr [__imp_std::cout (1022038h)]
01021065 push edx
01021066 call dword ptr [__imp_std::operator<<<char,std::char_traits<char>,std::allocator<char> > (102203Ch)]
0102106C add esp,8
return 0;
0102106F mov dword ptr [ebp-30h],0
01021076 mov dword ptr [ebp-4],0FFFFFFFFh
0102107D lea ecx,[ebp-2Ch]
01021080 call dword ptr [__imp_std::basic_string<char,std::char_traits<char>,std::allocator<char> >::~basic_string<char,std::char_traits<char>,std::allocator<char> > (1022044h)]
01021086 mov eax,dword ptr [ebp-30h]
}
std::string GetGreeting()
{
01021000 push ecx ;ret + 4
01021001 push esi ;ret + 4 + 4 = 0C
01021002 mov esi,dword ptr [esp+0Ch] ; this is test string
std::string greet("Hello, world!");
01021006 push offset string "Hello, world!" (1022124h)
0102100B mov ecx,esi
0102100D mov dword ptr [esp+8],0
01021015 call dword ptr [__imp_std::basic_string<char,std::char_traits<char>,std::allocator<char> >::basic_string<char,std::char_traits<char>,std::allocator<char> > (1022040h)]
return greet;
0102101B mov eax,esi ;put back test
0102101D pop esi
//return "Hello, world!";
}
Just like snmacdonald I was unable to repro your crash, under normal circumstances. Others already mentioned:
Configuration Properties -> Code Generation -> Runtime Library must be exactly the same
If I change one of them I get your crash. I have both the DLL and EXE set to Multi-Threaded DLL.
You may return a std::string
from a class as long as you inline the function that creates the string. This is done, for example, by the Qt toolkit in their QString::toStdString
method:
inline std::string QString::toStdString() const
{ const QByteArray asc = toAscii(); return std::string(asc.constData(), asc.length()); }
The inline function is compiled with the program that uses the dll, not when the dll is compiled. This avoids any problems encountered by incompatible runtimes or compiler switches.
So in fact, you should only return standard types from your dll (such as const char *
and int
above) and add inline functions to convert them to std::string()
.
Passing in a string &
parameter may also be unsafe because they are often implemented as copy on write.
Just like user295190 and Adam said that it would work fine if with the exactly same compilation settings.
For example, in Qt QString::toStdString() would return a std::string and you could use it in your EXE from QtCore.dll.
It would crash if DLL and EXE had different compiling settings and linking settings. For example, DLL linked to MD and EXE linked to MT CRT library.