Почему происходит сбой этой программы: передача std :: string между DLL - PullRequest
24 голосов
/ 24 февраля 2010

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

50 голосов
/ 24 февраля 2010

Это на самом деле не вызвано различными реализациями кучи - реализация MSVC std :: string не использует динамически выделенную память для маленьких строк (она использует небольшую строковую оптимизацию). ЭЛТ должны совпадать, но на этот раз это не то, что вам нужно.

То, что происходит, заключается в том, что вы вызываете неопределенное поведение, нарушая Правило одного определения .

В сборках выпуска и отладки будут установлены разные флаги препроцессора, и вы обнаружите, что std::string имеет разные определения в каждом случае. Спросите у своего компилятора, что такое sizeof(std::string) - MSVC10 говорит мне, что это 32 в отладочной сборке и 28 в сборке выпуска (это не заполнение - 28 и 32 являются границами 4 байтов).

Так что же происходит? Переменная s инициализируется с использованием отладочной версии конструктора копирования для копирования версии выпуска std::string. Смещения переменных-членов в разных версиях различны, поэтому вы копируете мусор. Реализация MSVC эффективно хранит указатели начала и конца - вы скопировали в них мусор; поскольку они больше не равны нулю, деструктор пытается их освободить, и вы получаете нарушение прав доступа.

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


В итоге: версии CRT должны совпадать, но должны соответствовать определениям, включая определения в стандартной библиотеке .

3 голосов
/ 24 февраля 2010

Может быть, что строка Возвращает A :: getString () размещены в A.dll и освобождены в main.exe

Да.

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

Использование shared_ptr звучит для меня как разумная вещь. Помните, что, как правило, распределение и освобождение должны выполняться одним и тем же модулем, чтобы избежать подобных сбоев.

Экспорт объектов STL через dll - в лучшем случае непростая задача. Я предлагаю вам сначала прочитать эту статью MSDN KB и эту запись.

2 голосов
/ 05 февраля 2015

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

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

2 голосов
/ 24 февраля 2010

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

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

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

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

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

1 голос
/ 24 февраля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...