Почему происходит сбой этой программы: передача std::string между DLL

У меня возникли проблемы с выяснением причины следующих сбоев (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?

Если так, то почему и каков был бы безопасный способ передачи строк между DLL (или исполняемыми файлами)? Без использования оберток, таких как shared_ptr с пользовательским удалителем.

5 ответов

Решение

На самом деле это не вызвано различными реализациями кучи - реализация 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 KB и этот пост.

Вам необходимо создать ссылку на одну и ту же библиотеку времени выполнения (DLL), либо отладочную, либо выпускную, для каждой библиотеки DLL в вашем приложении, где память выделена в одной и освобождена в другой. (Причина использования динамически связанной библиотеки времени выполнения состоит в том, что тогда будет одна куча для всего вашего процесса, в отличие от одной на dll/exe, которая ссылается на статическую.)

Это включает в себя возврат std::string и stl-контейнеров по значению, так как это то, что вы делаете.

Причины двоякие (обновленный раздел):

  • классы имеют разные макеты / размеры, поэтому по-разному скомпилированный код предполагает, что данные находятся в разных местах. Кто бы ни создал это первым, получает право, но другой рано или поздно вызовет сбой.
  • Реализации кучи msvc различны в каждой lib-среде выполнения, что означает, что если вы попытаетесь освободить указатель в куче, которая его не выделяла, он пойдет на убыль. (Это происходит, если макеты похожи, то есть там, где вы пережили первый случай.)

Таким образом, получите прямые библиотеки времени выполнения или прекратите освобождение / распределение в разных библиотеках (т. Е. Прекратите передавать вещи по значению).

В дополнение к тому, что было сказано выше, убедитесь, что набор инструментов платформы (в разделе Свойства-> Общие) идентичен в обоих проектах. В противном случае содержимое строки на принимающей стороне может быть поддельным.

Это произошло со мной, когда проект консольного приложения с версией набора инструментов v100 использовал библиотеку, которая была установлена ​​на v90.

Это может быть потому, что DLL и EXE скомпилированы с различными настройками CRT. Поэтому, когда вы передаете строку, возникает конфликт ресурсов. Проверьте настройки вашего проекта как для DLL, так и для исполняемого файла.

Убедитесь, что оба проекта (приложение и Dll) используют одну из библиотек времени выполнения " Многопоточная DLL", а не статическую версию.

Свойства-> C / C++-> Генерация кода-> (/MD или / MDd)

ПРИМЕЧАНИЕ. Если вы используете какие-либо сторонние библиотеки в своем приложении, вам также может потребоваться их перекомпилировать, компоновщик уведомит вас об этом с ошибками несоответствия / дублирования во время выполнения.

Другие вопросы по тегам