从DLL函数返回字符串

时间:2010-12-15 04:12:14

标签: c++ string visual-studio-2010 dll

由于某种原因,从DLL函数返回一个字符串会在运行时崩溃我的程序,错误为Unhandled exception at 0x775dfbae in Cranberry Library Tester.exe: Microsoft C++ exception: std::out_of_range at memory location 0x001ef604..

我已经通过将DLL代码编译为.exe并在main函数中执行一些简单测试来验证函数本身不是问题。

其他返回类型(intdouble等)的函数可以很好地工作。

  • 为什么会这样?
  • 有办法解决这种问题吗?

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。


结论

解决方法是确保使用相同编译器相同选项等编译库和源。

7 个答案:

答案 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和Ad​​am说如果使用完全相同的编译设置它会正常工作。

例如,在Qt中,QString :: toStdString()将返回一个std :: string,您可以在QtCore.dll的EXE中使用它。

如果DLL和EXE具有不同的编译设置和链接设置,则会崩溃。例如,链接到MD和EXE的DLL链接到MT CRT库。