Возвращение строк из функций DLL - PullRequest
10 голосов
/ 15 декабря 2010

По какой-то причине возврат строки из функции 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.


Заключение

Обходное решение - убедиться, что библиотека и исходный код скомпилированы с использованием того же компилятора с такими же параметрами и т. Д.

Ответы [ 7 ]

6 голосов
/ 15 декабря 2010

Это происходит потому, что вы выделяете память в одной DLL (используя конструктор std :: string) и освобождаете ее в другой DLL.Вы не можете сделать это, потому что каждая DLL обычно устанавливает свою собственную кучу.

5 голосов
/ 15 декабря 2010

Поскольку в вашем сообщении об ошибке указано, что вы используете Microsoft C ++, я предложу специальный ответ для MS.

Пока вы компилируете EXE и DLL с помощью ЖЕ компилятора, и оба связывают ЖЕ версию ДИНАМИЧЕСКИ, то все будет в порядке. Например, используя «Многопоточную DLL» для обоих.

Если вы статически ссылаетесь на среду выполнения или ссылаетесь на разные версии среды выполнения, то вы по причинам SOL @Billy ONeal указывает (память будет выделяться в одной куче и освобождаться в другой).

1 голос
/ 01 июня 2012

Так же, как user295190 и Адам сказали, что это будет работать нормально, если с такими же настройками компиляции.

Например, в Qt QString :: toStdString () вернул бы std :: string, и вы могли бы использовать его в своем EXE-файле из QtCore.dll.

Сбой, если DLL и EXEбыли разные настройки компиляции и ссылок.Например, DLL, связанная с MD, и EXE, связанная с библиотекой CR CRT.

1 голос
/ 06 августа 2011

Вы можете вернуть 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 & также может быть небезопасной, поскольку они часто реализуются как копии при записи.

1 голос
/ 15 декабря 2010

Точно так же, как snmacdonald, я не смог воспроизвести ваш сбой при нормальных обстоятельствах.Другие уже упоминали: Свойства конфигурации -> Генерация кода -> Библиотека времени выполнения должно быть точно то же самое

Если я изменю одно из них, я получу ваш сбой.У меня и DLL, и EXE установлены на многопоточные DLL.

1 голос
/ 15 декабря 2010

У меня была такая проблема, которая была решена с помощью:

  • с использованием библиотеки времени выполнения в качестве библиотеки DLL как для приложения, так и для библиотеки DLL (так что распределение выполняется в уникальном месте)

  • убедившись, что все настройки проекта (компилятор, компоновщик) одинаковы для обоих проектов

1 голос
/ 15 декабря 2010

Обновление:

Я скомпилировал ваш пример как в VS2008, так и в VS2010, и мне удалось успешно скомпилировать и выполнить без проблем.Я скомпилировал библиотеку как статическую, так и динамическую.

Оригинал:

Следующее относится к моей дискуссии с bdk и imaaryboy.Я не удалил его, так как он может быть кому-то интересен.

Хорошо, этот вопрос меня действительно беспокоит, потому что похоже, что он передается по значению, а не по ссылке.Похоже, что в куче не создано никаких объектов, которые, по-видимому, полностью основаны на стеке.

Я провел быстрый тест, чтобы проверить, как объекты передаются в Visual Studios (скомпилированы в режиме выпуска без ссылкиОптимизация и оптимизация времени отключены).

Код:

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!";
}
...