#include <string>
extern "C" __declspec(dllexport) std::string hello_world()
{
return std::string("Hello, World!"); // or just: return "Hello, World!";
}
我们还有一个可执行文件 ("LoadLibraryExample.exe"),它使用 LoadLibrary WINAPI 函数动态加载此 DLL:
#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上找到了this page,其中提到以下内容:
因此,库和可执行文件具有各自的CRT副本,这些副本具有各自的状态。在库中构造了一个std :: string实例(库的CRT进行了一些内部内存分配),然后将其返回到可执行文件中。可执行文件显示它,然后调用其析构函数(导致可执行文件的CRT释放内部内存)。据我所知,这就是出现错误的地方:尝试使用一个CRT分配的底层std :: string内存来释放另一个CRT。使用静态链接的CRT意味着C运行时库保存的任何状态信息都将局限于CRT的那个实例。
因为通过链接到静态CRT构建的DLL将具有自己的CRT状态,所以不建议在DLL中静态链接到CRT,除非明确需要并理解其后果。
如果从DLL返回一个原始类型(int、char、float等)或指针,则不会出现问题,因为在这些情况下没有内存分配或释放。但是,尝试在可执行文件中删除返回的指针会导致相同的错误(而不删除指针显然会导致内存泄漏)。
因此,问题是:有没有可能解决这个问题?
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")
(function name gets decorated by the C++ compiler, the actual name can be retrieved with dumpbin.exe utility)。警告将消失,但问题仍然存在。
P.P.P.S:我认为提供库中每个这种情况的一对函数可能是一个解决方案:一个返回指向某些数据的指针,另一个删除这些数据的指针。在这种情况下,内存是由相同的CRT分配和释放的。但这种解决方案似乎非常丑陋和不友好,因为我们必须始终使用指针,并且程序员必须始终记住调用特殊的库函数来删除指针,而不是简单地使用delete关键字。