由于某种原因,从DLL函数返回一个字符串会在运行时崩溃我的程序,错误为Unhandled exception at 0x775dfbae in Cranberry Library Tester.exe: Microsoft C++ exception: std::out_of_range at memory location 0x001ef604..
。
我已经通过将DLL代码编译为.exe
并在main
函数中执行一些简单测试来验证函数本身不是问题。
其他返回类型(int
,double
等)的函数可以很好地工作。
DLL的源代码:
// Library.h
#include <string>
std::string GetGreeting();
// Library.cpp
#include "Library.h"
std::string GetGreeting()
{
return "Hello, world!";
}
测试人员的源代码:
// Tester.cpp
#include <iostream>
#include <Library.h>
int main()
{
std::cout << GetGreeting()
}
编辑:我正在使用VS2010。
解决方法是确保使用相同编译器和相同选项等编译库和源。
答案 0 :(得分:6)
这是因为您在一个DLL中分配内存(使用std :: string的构造函数),并在另一个DLL中释放它。你不能这样做,因为每个DLL通常会设置它自己的堆。
答案 1 :(得分:5)
由于您的错误消息表明您正在使用Microsoft C ++,因此我将提供MS特定答案。
只要您使用SAME编译器编译EXE和DLL,并且两者都链接运行时的SAME版本DYNAMICALLY,那么您就可以了。例如,为两者使用“多线程DLL”。
如果您静态链接运行时,或者链接到运行时的不同版本,那么您就是SOL,因为@Billy ONeal指出(内存将分配到一个堆中并在另一个堆中释放)。
答案 2 :(得分:1)
我已经在VS2008和VS2010中编译了您的示例,并且我能够成功编译并执行而没有任何问题。我将库编译为静态库和动态库。
以下内容与我与bdk和imaginaryboy的讨论有关。我没有删除它,因为它可能引起某些人的兴趣。
好的,这个问题确实困扰我,因为它看起来像是通过值传递而不是通过引用传递。似乎没有在堆中创建任何对象,它看起来完全基于堆栈。
我做了一个快速测试来检查Visual Studio中如何传递对象(在发布模式下编译,没有链接时优化和禁用优化)。
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;
}
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]
}
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
即使代码位于单独的DLL中,返回的字符串也会按值复制到调用者堆栈。有一个隐藏的参数将对象传递给GetGreetings()
。我没有看到任何堆被创建。我没有看到堆与问题有任何关系。
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!";
}
答案 3 :(得分:1)
我遇到了这样的问题:
将运行时库用作应用程序和DLL的DLL(以便在独特的位置处理分配)
确保项目中的所有项目设置(编译器,链接器)都相同
答案 4 :(得分:1)
就像snmacdonald一样,在正常情况下我无法重现你的崩溃。其他人已经提到: 配置属性 - &gt;代码生成 - &gt;运行时库必须完全相同
如果我改变其中一个,我就会崩溃。我将DLL和EXE都设置为Multi-Threaded DLL。
答案 5 :(得分:1)
只要您内联创建字符串的函数,就可以从类中返回std::string
。例如,这可以通过Qt工具包在QString::toStdString
方法中完成:
inline std::string QString::toStdString() const
{ const QByteArray asc = toAscii(); return std::string(asc.constData(), asc.length()); }
内联函数是使用使用dll的程序编译的,而不是在编译dll时编译的。这可以避免不兼容的运行时或编译器开关遇到的任何问题。
事实上,您应该只从您的dll返回标准类型(例如上面的const char *
和int
)并添加内联函数以将它们转换为std::string()
。
传入string &
参数也可能不安全,因为它们通常在写入时实现为副本。
答案 6 :(得分:1)
就像user295190和Adam说如果使用完全相同的编译设置它会正常工作。
例如,在Qt中,QString :: toStdString()将返回一个std :: string,您可以在QtCore.dll的EXE中使用它。
如果DLL和EXE具有不同的编译设置和链接设置,则会崩溃。例如,链接到MD和EXE的DLL链接到MT CRT库。