Предположим, у нас есть динамическая библиотека ( "HelloWorld.dll" ), которая скомпилирована с Microsoft Visual Studio 2010 из следующего исходного кода:
#include <string>
extern "C" __declspec(dllexport) std::string hello_world()
{
return std::string("Hello, World!"); // or just: return "Hello, World!";
}
И у нас также есть исполняемый файл ( "LoadLibraryExample.exe" ), который динамически загружает эту DLL с помощью LoadLibrary Функция WINAPI:
#include <iostream>
#include <string>
#include <Windows.h>
typedef std::string (*HelloWorldFunc)();
int main(int argc, char* argv[])
{
if (HMODULE library = LoadLibrary("HelloWorld.dll"))
{
if (HelloWorldFunc hello_world = (HelloWorldFunc)GetProcAddress(library, "hello_world"))
std::cout << hello_world() << std::endl;
else
std::cout << "GetProcAddress failed!" << std::endl;
FreeLibrary(library);
}
else
std::cout << "LoadLibrary failed!" << std::endl;
std::cin.get();
}
Это прекрасно работает при подключении к динамической библиотеке времени выполнения (переключатели / MD или / MDd ).
Проблема возникает, когда я связываю их (библиотека и исполняемый файл) с отладочной версией статической библиотеки времени выполнения (переключатель / MTd ). Программа, кажется, работает ( «Hello, World!» отображается в окне консоли), но затем вылетает со следующим выводом:
HEAP[LoadLibraryExample.exe]: Invalid address specified to RtlValidateHeap( 00680000, 00413F60 )
Windows has triggered a breakpoint in LoadLibraryExample.exe.
This may be due to a corruption of the heap, which indicates a bug in LoadLibraryExample.exe or any of the DLLs it has loaded.
This may also be due to the user pressing F12 while LoadLibraryExample.exe has focus.
The output window may have more diagnostic information.
Проблема волшебным образом не возникает с выпуском версии статической библиотеки времени выполнения (переключатель / MT ). Я предполагаю, что версия выпуска просто не видит ошибку, но она все еще там.
После небольшого исследования я нашел эту страницу в MSDN, в которой говорится следующее:
Использование статически связанного CRT подразумевает, что любая информация о состоянии, сохраненная библиотекой времени выполнения C, будет локальной для этого экземпляра CRT.
Поскольку библиотека DLL, созданная путем связывания со статическим ЭЛТ, будет иметь свое собственное состояние ЭЛТ, не рекомендуется статически ссылаться на ЭЛТ в библиотеке DLL, если только последствия этого специально не желательны и понятны.
Таким образом, библиотека и исполняемый файл имеют свои собственные копии CRT, которые имеют свои собственные состояния. Экземпляр std :: string создается в библиотеке (с некоторыми выделениями внутренней памяти, выполняемыми CRT библиотеки) и затем возвращается в исполняемый файл. Исполняемый файл отображает его и затем вызывает его деструктор (что приводит к освобождению внутренней памяти с помощью CRT исполняемого файла). Как я понимаю, именно здесь возникает ошибка: базовая память std :: string выделяется одним CRT и пытается быть освобождена другим.
Проблема не появляется, если мы возвращаем примитивный тип (int, char, float и т. Д.) Или указатель из DLL, потому что в этих случаях нет выделения памяти или освобождения. Однако попытка удалить возвращенного указателя в исполняемом файле приводит к той же ошибке (а не удаление указателя, очевидно, приводит к утечке памяти).
Итак, вопрос: возможно ли обойти эту проблему?
P.S .: Я действительно не хочу зависеть от MSVCR100.dll и заставлять пользователей моего приложения устанавливать любые распространяемые пакеты.
P.P.S: Приведенный выше код выдает следующее предупреждение:
warning C4190: 'hello_world' has C-linkage specified, but returns UDT 'std::basic_string<_Elem,_Traits,_Ax>' which is incompatible with C
, который можно устранить, удалив extern "C" из объявления библиотечной функции:
__declspec(dllexport) std::string hello_world()
и изменение вызова GetProcAddress следующим образом:
GetProcAddress(library, "?hello_world@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ")
(имя функции оформляется компилятором C ++, фактическое имя можно получить с помощью утилиты dumpbin.exe ). Затем предупреждение исчезает, но проблема остается.
P.P.P.S: я вижу возможное решение в предоставлении пары функций в библиотеке для каждой такой ситуации: одна возвращает указатель на некоторые данные, а другая - удаляет указатель на эти данные. В этом случае память выделяется и освобождается с помощью одного и того же ЭЛТ. Но это решение кажется очень уродливым и не дружественным, так как мы всегда должны работать с указателями, и, кроме того, программист должен всегда не забывать вызывать специальную библиотечную функцию для удаления указателя, а не просто использовать ключевое слово delete .