如何在 C++ 中构建与运行时版本无关的 DLL?
Posted
技术标签:
【中文标题】如何在 C++ 中构建与运行时版本无关的 DLL?【英文标题】:How do I build a runtime version agnostic DLL in C++? 【发布时间】:2011-06-01 18:29:21 【问题描述】:我的产品是一个 C++ 库,它在 Windows 上以 dll 形式分发。它很少使用 c-runtime(基本的 iostream 就是这样),所以我确信所有最新版本的 CRT 都可以。
由于我的客户应该使用我的 dll 构建他的应用程序,我不想将任何特定的运行时版本强加给他。我希望我的 dll 绑定到我的客户端应用程序正在使用的任何运行时库版本(我可以假设他将为他的 CRT 使用动态链接)。毕竟,这不就是动态链接的全部意义吗?这可能吗?
编辑:将 dll 与静态运行时库链接也不起作用,因为静态运行时(来自 dll)和动态运行时(来自客户端应用程序)将混合在一起,这很糟糕。
编辑:我主要要问的是如何告诉运行时加载程序将我的 dll 与应用程序链接的任何 CRT 链接?也许与清单有关? 更一般地说,我的问题是如何构建一个行为良好的 dll,供构建自己的应用程序的客户使用?
编辑:感谢答案中的建议,我已将所有对 std 类的引用转移到我的标题中的内联函数中,并将我的 dll 与静态运行时库链接起来。它现在似乎甚至可以在与不同 CRT 版本链接的应用程序中工作。
【问题讨论】:
也许,但我的问题仍然存在。我当然不会做任何可能在某些版本的 CRT 中被破坏的花哨的事情。 您不必为了与特定的 CRT 耦合而做一些“花哨”的事情。 C++ + DLLs == 痛苦。我强烈建议在 DLL 之间公开 C-only 接口。 你的 DLL 不会改变吗?如果是,则无论您选择何种技术,您仍将处理版本。使用 COM 至少使版本非常清晰。 【参考方案1】:没有真正的方法可以确保您的 DLL 与多个运行时一起工作——它们之间的任何类型变化都可能导致不兼容。例如,对象的大小可以更改,或者对象中成员的位置。在 C++ 中这种事情几乎没有空间。
您可以做的最好的事情是静态链接到运行时并确保导出的 API 仅限于您严格控制的类型 - 不将 std::string
传递给函数,不将 stdlib 类型作为成员,并且不要 @ 987654322@ 在一个 DLL 中,delete
在另一个 DLL 中。不要为同一个对象混合内联函数和导出函数(包括构造函数/析构函数),因为编译器之间的成员顺序和填充可能会发生变化。 pimpl 成语在这里可能会有所帮助。
【讨论】:
您认为这是作为 dll 交付的产品的常见做法吗? 我不知道这是否是“标准实践”(如果真的存在这样的动物的话)。但这当然是合理的建议。我在我的 DLL 中更进一步,并将函数限制为可以使用“C”调用的东西。 由于这些复杂性,许多人避免导出运行时可移植的 C++ 函数,但这样做是常见做法。 解决这个问题的一种常用方法(在 Windows 中)是使用 COM。当然,COM 本身在 C++ 中生产和使用是一件很痛苦的事情,而且版本控制往往是一个大问题。我更喜欢的解决方案是不使用 C++ :)【参考方案2】:如果您跨 DLL 边界公开任何 C++ 对象,那么这是不可能的。您可以做的(我们使用 3rd-party DLL 执行此操作)以多种配置(32 位/64 位、调试/发布、静态/动态运行时、静态/动态库)构建您的库以满足尽可能多的人。刚开始设置可能有点乏味,但是一旦您设置了所有配置,只需构建所有配置即可。当然,您还需要考虑要针对哪个运行时构建(vc8、vc9、vc10 等),因此如果您想涵盖所有基础,您可以进行大量配置。
【讨论】:
为什么讨厌 Windows?此线程中的任何罪行都是无辜的。 呃,也许不是。我只是感觉 Windows CRT 更改版本和破坏兼容性比在 linux 上更多。也许我错了。 windows C 运行时不同,因为每台机器只有一个,所以没有问题。您一直在讨论完全不同的 MSVC 运行时。【参考方案3】:将您的 DLL 与静态运行时库链接应该可以工作,但您必须非常小心内存管理(例如,调用您的 DLL 的人不能释放()或删除 DLL 分配的任何内容)并且您不能交换标准 C 数据结构(例如 FILE*)。 (我错过了什么吗?)
【讨论】:
嗯,那会是个问题。从到目前为止答案的主旨来看,答案似乎是:“不,你不能那样做。dll和使用它们的应用程序应该使用相同的CRT” 而且,顺便说一句,为什么释放()由不同 CRT 分配的对象会有问题? Windows CRT 是否改变了它在版本之间存储大小信息的方式? 很可能,所有运行时库最终都会调用相同的基本 win32 函数来为进程分配内存。不同之处在于每个运行时库实例都将保留与分配的每个对象关联的“额外”簿记数据(使用 new 或 malloc())。如果一个 DLL 使用自己的运行时分配内存,而进程使用自己的运行时删除该内存,则其中之一或两者的堆可能会损坏。最终答案,不要混用。 @pron:不仅是版本之间,还有配置之间。考虑调试与发布版本。所以你也不能混合这些DLL。在我看来,最好的方法是,每个对象都会带来毁灭自己的手段,而不必依赖某些外部手段。最后,这将意味着与 COM 中的接口类似的机制,当它们的引用计数下降到零时,它就会死掉。 不同的配置可能是更大的问题。 CRT 调试堆做了一堆特殊的记账(参见nobugs.org/developer/win32/debug_crt_heap.html),我预计这会使其与发布版本不兼容。无论如何,不同的 CRT 版本可能使用相同的堆和相同的簿记方法,但您不能依赖它(因为 MS 可以随意更改版本之间的任何内容,而不必记录这些更改。)所以要找出两个版本是否兼容你必须做一些实验。我个人没有做过这样的实验。【参考方案4】:您可以通过对 I/O 使用 WinAPI 调用和任何其他可能依赖于运行时的方法来实现此目的。
最痛苦的部分是您可能必须覆盖全局new
和delete
才能独占使用WinAPI 函数,因为它们很可能在内部使用malloc/free。这还有许多其他痛苦的方面,我的意见是不值得麻烦。 Here 是一篇涵盖该主题的文章。
【讨论】:
关于这个的有趣是 HeapAlloc 和 HeapFree(来自 kernel32.lib)甚至比 MSVCRT dll 的 malloc/free 实现还要快。 是的,我认为这也不值得,所以假设我确实需要 CRT。 DLL 的 ABI 兼容性较差。我认为您无法确保它可以使用不同的运行时。例如,Qt 为 MSVC 和 Mingw 提供了不同的 DLL。【参考方案5】:如果您想以运行时中立的方式公开您的对象,那么除了 COM 之外我看不到任何解决方案。
【讨论】:
【参考方案6】:嗯,C 运行时和 C++ 运行时之间存在巨大差异。如果您在哪里使用 msvcrt.dll,它近年来已成为真正的系统 DLL,您可以依赖它在 XP 及更高版本上的存在(尽管对于 Windows 2000,您需要一些可再发行版本 6 的 msvcrt.dll )。您可以通过使用最新 WDK(Windows 驱动程序工具包)的编译器编译您的代码来使用 msvcrt.dll。尽管这是用户模式代码,但这是一种可行且良好的编译方法。
然而,IOStreams 需要 C++ 运行时。这让事情变得非常复杂。
编辑:将 dll 链接到 静态运行时库也不起作用, 因为那时静态运行时(来自 dll)和动态运行时(来自 客户的应用程序)将是 混合,这很糟糕。
好吧,如果你以这种方式混合代码,你的设计就有问题。在运行 DLL 的调试版本和其他代码的发布版本时,您会遇到类似的问题,反之亦然。
我只能建议您直接使用 COM,或者 - 如果太大 - 尝试模仿 COM 的一些想法。最重要的是你有一个工厂函数,并且在这两段代码(即 DLL 及其调用者)之间声明了一个(类)接口(并且永远不会改变)。工厂函数将返回该类的一个实例,该类将自行管理其生命周期(这意味着所有用于分配和释放的代码都将驻留在同一个实体中,即您的 DLL 中)。然后,生命周期管理将通过addref
和release
成员函数公开。 IUnknown
可以作为你的这个接口的基础,而不依赖于实际 COM 的其他部分。
编辑:我主要要问的是怎么做 我告诉运行时加载程序链接我的 dll 针对任何 CRT 应用程序与?某物 与清单,也许?更多的 一般来说,我的问题是如何构建 一个行为良好的 dll,那就是 客户使用他们自己的建筑 应用?
一点都不轻松。即使您安装了所有版本的 VS,您也必须编写脚本来摆脱这种困境以选择正确的版本。
【讨论】:
【参考方案7】:您的 dll 与编译它的 c-runtime 相关联。您的应用程序将始终使用此运行时。任何链接到您的 dll 的人都使用他们的 c-runtime。所以这不会有任何问题。
【讨论】:
那么,您的意思是最终的应用程序可能会链接到 2 个不同的运行时,这很好吗? 这可能不太“好”。取决于你的使用情况。 例如,如果您new
使用一个 CRT 而 delete
它使用另一个 CRT,这可能不是“很好”。
好的,那么如果我想给我的客户一个表现良好的 dll 应该怎么做呢?【参考方案8】:
如果您使用 C++,似乎不可能跨越运行时边界,除非您限制自己可以公开的内容。如前所述,std:: 对象不起作用(例如 std::string)。
这是一个会导致崩溃的小例子:
类基础 公开: 虚拟 ~Base() ;
类 ClassInDll:公共基础 上市: __declspec( dllexport ) ClassInDll( int arg ); __declspec(dllexport)~ClassInDll();
私人: int _arg; ;
如果这个类被编译成一个 VS2008 发布模式的 DLL,并且在 VS2008 调试模式下构建一个 .exe,执行以下操作:
ClassInDll* c = new ClassInDll( 1 ); 删除 c;
“删除 c”语句会导致崩溃。 这与 ClassInDll 有一个虚拟析构函数有关。
【讨论】:
@David Feurle:应用程序不会使用与 DLL 相同的运行时,这是问题的核心。以上是关于如何在 C++ 中构建与运行时版本无关的 DLL?的主要内容,如果未能解决你的问题,请参考以下文章
在 Visual Studio 2012 上构建 dll 版本时需要入口点
如何在 C++ 运行时组件中的 Windows Phone 8.1 XAML 应用程序中使用 C++ dll