Возврат непримитивного типа C ++ из функции DLL, связанной со статической средой выполнения (/ MT или / MTd)

Предположим, у нас есть динамическая библиотека ( «HelloWorld.dll» ), скомпилированная с помощью Microsoft Visual Studio 2010 из следующего исходного кода:

#include 

extern "C" __declspec(dllexport) std::string hello_world()
{
    return std::string("Hello, World!"); // or just: return "Hello, World!";
}

И у нас также есть исполняемый файл ( «LoadLibraryExample.exe» ), который динамически загружает эту DLL с помощью функции LoadLibrary WINAPI:

#include 
#include 

#include 

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, будет локальной для этого экземпляра ЭЛТ.
Поскольку DLL, созданная путем связывания со статической CRT, будет иметь собственное состояние CRT, не рекомендуется статически связываться с CRT в DLL, если только последствия этого не желательны и не поняты.

Таким образом, библиотека и исполняемый файл имеют свои собственные копии CRT, которые имеют свои собственные состояния. Экземпляр std :: string создается в библиотеке (с некоторыми выделениями внутренней памяти, выполняемыми библиотекой CRT), а затем возвращается исполняемому файлу. Исполняемый файл отображает его, а затем вызывает его деструктор (что приводит к освобождению внутренней памяти CRT исполняемого файла). Насколько я понимаю, здесь возникает ошибка: основная память std :: string выделяется одной CRT и пытается быть освобождена другой.

Проблема не возникает, если мы возвращаем примитивный тип (int, char, float и т. Д.) Или указатель из DLL, потому что в этих случаях не происходит выделения или освобождения памяти. Однако попытка удалить возвращенный указатель в исполняемом файле приводит к той же ошибке (и отсутствие удаления указателя, очевидно, приводит к утечке памяти).

Итак, вопрос: можно ли обойти эту проблему?

P.S .: Я действительно не хочу иметь зависимость от MSVCR100.dll и заставлять пользователей моего приложения устанавливать какие-либо распространяемые пакеты.

П.П.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: Я вижу возможное решение в предоставлении пары функций в библиотеке для каждой такой ситуации: одна возвращает указатель на некоторые данные, а другая удаляет указатель на эти данные. В этом случае память выделяется и освобождается с помощью той же CRT. Но это решение кажется очень уродливым и недружелюбным, поскольку мы всегда должны работать с указателями, и, более того, программист должен всегда помнить о вызове специальной библиотечной функции для удаления указателя вместо простого использования ключевого слова delete .

8
задан Mat 15 October 2011 в 16:06
поделиться