由于Microsoft如何在非DLL版本的运行时实现堆,从DLL返回C ++对象可能会导致问题:
// dll.h
DLL_EXPORT std::string somefunc();
和
// app.c - not part of DLL but in the main executable
void doit()
{
std::string str(somefunc());
}
如果DLL和EXE都是使用多线程DLL运行时库构建的,上面的代码运行正常。
但是如果DLL和EXE是在没有DLL运行时库(单线程或多线程版本)的情况下构建的,则上面的代码失败(使用调试运行时,由于断言_CrtIsValidHeapPointer(pUserData)
,代码会立即中止失败;使用非调试运行时,堆被破坏,程序最终在其他地方失败。
两个问题:
答案 0 :(得分:1)
有没有办法解决这个问题,然后要求所有代码都使用DLL运行时?
不是我知道的。
对于将图书馆分发给第三方的人,您如何处理?你不在API中使用C ++对象吗?您是否要求库的用户使用DLL运行时?还有别的吗?
过去我分发了一个带有dll的SDK,但它是基于COM的。使用COM,所有参数和IPC的编组都会自动完成。用户还可以使用任何语言进行集成。
答案 1 :(得分:1)
您的代码有两个潜在的问题:您解决了第一个问题 - CRT运行时。这里有另一个问题:std :: string可能会在VC ++版本中发生变化。事实上,它确实在过去发生了变化。
安全的处理方法是只导出C基本类型。并导出DLL中的创建和释放函数。而不是导出std :: string,导出指针。
__declspec(export) void* createObject()
{
std::string* p = __impl_createObject();
return (void*)p;
}
__declspec(export) void releasePSTRING(void* pObj)
{
delete ((std::string*)(pObj));
}
答案 2 :(得分:0)
有一种方法可以解决这个问题,但这有点不重要。与大多数库的其余部分一样,std::string
不会直接使用new
分配内存 - 相反,它默认使用分配器(std::allocator<char>
)。
您可以提供自己的分配器,它使用您自己的DLL和可执行文件共有的堆分配例程,例如使用HeapAlloc
获取内存,并从那里子分配块。
答案 3 :(得分:0)
如果您要分发DLL并且不想将调用者绑定到C-Runtime的特定版本,请执行以下任一操作:
予。将DLL链接到C-Runtime库的静态版本。从Visual Studio项目属性页面中,选择配置属性 - &gt;的选项卡。 C / C ++ - &gt;代码生成。这些选项用于选择“运行时库”。选择“多线程”或“多线程调试”而不是DLL版本。 (命令行等价是/ MT或/ MTd)
这种方法有几个不同的缺点:
一个。如果Microsoft发布了CRT的安全补丁,那么在重新编译和重新编写二进制文件之前,您发送的组件可能容易受到攻击。
湾由DLL中的“malloc”或“new”分配的堆指针不能由EXE或其他二进制文件“释放”或“删除”。否则你会崩溃。对于fopen创建的FILE句柄也是如此。您不能在DLL中调用fopen并期望EXE能够在其上进行fclose。如果你这样做,再次崩溃。您需要构建DLL的接口以适应所有这些问题。对于初学者来说,将实例返回给std :: string的函数可能会成为一个问题。提供DLL导出的函数,以根据需要处理资源的释放。
其他选择:
II。没有c运行时依赖性。这有点难。首先必须从代码中删除对CRT的所有调用,提供一些存根函数以使DLL链接,并指定“无默认库”链接选项。可以做到。
III。可以使用COM接口指针从DLL中干净地导出C ++类。你仍然需要解决上面1a中的问题,但是ATL类是一种很好的方法来消除COM的开销。
答案 4 :(得分:0)
这里的一个简单事实是,除了微软实现外,C ++不是ABI。您无法在任何平台上从动态模块导出C ++对象,并期望它们使用不同的编译器或语言。
从Dll导出c ++类是一个毫无意义的练习 - 由于名称错误,并且c ++中缺乏动态加载类的支持 - dll必须静态加载 - 因此你放弃了将项目分成的最大好处dlls - 只根据需要加载功能的能力。