Я испытываю некоторые затруднения при выяснении почему следующие катастрофические отказы (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
Очевидно(?), это происходит из-за различных моделей памяти для исполняемого файла и DLL. Могло случиться так что строка A::getString()
возвраты выделяются в A.dll и освобождаются в main.exe?
Если так, почему - и каков был бы безопасный способ передать строки между DLLs (или исполняемые файлы, в этом отношении)? Не используя обертки как shared_ptr с пользовательским средством удаления.
На самом деле это не вызвано разными реализациями кучи - реализация MSVC std :: string не использует динамически выделяемую память для таких маленьких строк (это использует оптимизацию малых строк). ЭЛТ действительно должны совпадать, но на этот раз вас укусило не это.
Происходит то, что вы вызываете неопределенное поведение, нарушая правило одного определения .
В сборках выпуска и отладки будут установлены разные флаги препроцессора, и вы обнаружите, что std :: string
имеет разные определения в каждом случае. Спросите у своего компилятора, что такое sizeof (std :: string)
- MSVC10 сообщает мне, что его 32 в отладочной сборке и 28 в сборке выпуска (это не заполнение - 28 и 32 - оба по 4 байта` границы).
Так что происходит? Переменная s
инициализируется с использованием отладочной версии конструктора копирования для копирования версии выпуска std :: string
. Смещения переменных-членов в разных версиях различаются, поэтому вы копируете мусор. Реализация MSVC эффективно хранит указатели начала и конца - вы скопировали в них мусор; поскольку они больше не равны нулю, деструктор пытается освободить их, и вы получаете нарушение прав доступа.
Даже если бы реализации кучи были такими же, это привело бы к сбою, поскольку вы освобождаете указатели мусора в память, которая никогда не была выделена изначально.
В итоге: версии CRT должны соответствовать, но должны соответствовать определениям, включая определения в стандартной библиотеке .
Может быть, строка, возвращаемая A :: getString (), выделяется в A.dll и освобождается в main.exe?
Да.
Если да, то почему - и что было бы безопасным способом передачи строк между библиотеками DLL (или исполняемыми файлами, если на то пошло)? Без использования оболочек, таких как shared_ptr, с пользовательским средством удаления .
Использование shared_ptr
кажется мне разумным. Помните, что, как показывает практика, выделения и освобождения должны выполняться одним и тем же модулем, чтобы избежать подобных сбоев.
Экспорт объектов STL в библиотеки DLL - в лучшем случае непростая задача. Я предлагаю вам сначала ознакомиться с этой статьей базы знаний MSDN и этой статьей .
Вам необходимо связать одну и ту же библиотеку времени выполнения (библиотеку DLL), отладочную или выпускаемую, для каждой библиотеки DLL в вашем приложении, где память выделяется в одной и освободили в другом. (Причина использования динамически подключаемой библиотеки времени выполнения заключается в том, что тогда будет одна куча для всего вашего процесса, а не одна куча для каждой dll / exe, которая ссылается на статическую.)
Это включает возврат std :: string и stl-контейнеры по значению, так как это то, что вы делаете.
Причины двоякие (обновленный раздел) :
Итак, получите ваши библиотеки времени выполнения прямо или прекратите освобождение / выделение в разных dll (т.е. прекратите передавать данные по значению).
Это может быть связано с тем, что DLL и EXE скомпилированы с разными настройками CRT. Поэтому, когда вы передаете строку, возникает конфликт ресурсов. Проверьте настройки вашего проекта как для библиотеки DLL, так и для исполняемого файла.