如何在C++中构建一个与运行时版本无关的DLL?

13
我的产品是一个C++库,在Windows上以dll的形式分发。它很少使用c-runtime(只使用了基本的iostream),因此我相信所有最近版本的CRT都可以正常运行。
由于我的客户应该使用我的dll构建他的应用程序,所以我不想强制他使用特定的运行时版本。我希望我的dll能够绑定到客户应用程序使用的任何运行时库版本上(我可以假设他将使用动态链接来连接他的CRT)。毕竟,这不就是动态链接的全部意义吗?这可能吗?
编辑:将dll与静态运行时库链接也行不通,因为那样静态运行时(来自dll)和动态运行时(来自客户应用程序)会混合在一起,这是不好的。
编辑:我主要要问的是如何告诉运行时加载器将我的dll与应用程序链接的任何CRT关联起来?也许与清单有关?
更普遍地说,我的问题是如何构建一个表现良好的dll,供客户构建自己的应用程序使用?
编辑:由于答案中的建议,我已经将所有对std类的引用转换为内联函数,并将我的dll与静态运行时库链接。现在它似乎可以在链接不同CRT版本的应用程序中工作了。

也许吧,但我的问题仍然存在。我肯定不会做任何可能在某个CRT版本中出现故障的花哨事情。 - pron
你不必做什么“花哨”的事情才能与特定的CRT耦合。 - John Dibling
3
C++和DLL混用会让人感到痛苦。我强烈建议在DLL之间暴露只有C语言接口。 - Billy ONeal
你的 DLL 没有改变过吗?如果有,无论你选择什么技术,你仍然需要处理版本问题。使用 COM 至少可以使版本非常清晰明了。 - AndersK
8个回答

12

没有确保你的DLL可与多个运行时一起使用的真正方法 - 它们之间任何更改的类型都可能导致不兼容性。例如,对象的大小或其中成员的位置可能会发生变化。在C++中很难处理这种情况。

最好的做法是静态链接到运行时,并确保导出的API仅限于严格受控制的类型 - 不要将std :: string传递给函数,不要使用stdlib类型作为成员,并且不要在一个DLL中使用new ,并在另一个DLL中使用delete。不要混合内联和导出函数(包括构造函数/析构函数),因为成员顺序和填充可能会在编译器之间发生变化。这里可以使用pimpl习惯用法。


1
你认为将产品作为dll交付是常见的做法吗? - pron
我不知道这是否是“标准做法”(如果真的存在这样的东西)。但这绝对是明智的建议。在我的DLL中,我更进一步地限制函数只能使用“C”调用。 - Jon Trauntvein
2
许多人因为这些复杂性而避免导出可运行时移植的C++函数,但在那些这样做的人中,这是常见的做法。 - Cory Nelson
2
在Windows中解决这个问题的常见方法是使用COM。当然,COM本身在C++中的生成和消费都很麻烦,版本控制也往往是一个大问题。我更喜欢的解决方案是不使用C++ :) - Qwertie

6
如果您在 DLL 边界上公开任何 C++ 对象,那么这是不可能的。但是,您可以使用第三方 DLL,构建多个配置(32 位 / 64 位,调试 / 发布,静态 / 动态运行时,静态 / 动态库),以尽可能满足更多人的需求。这一开始可能有点繁琐,但一旦您设置好所有配置,只需构建它们即可。当然,您还需要考虑您正在构建哪个运行时(vc8、vc9、vc10等),因此,如果您想覆盖所有情况,您可能会有很多配置。

1
为什么讨厌Windows?它在这个主题中没有犯任何罪。 - David Heffernan
嗯,也许不是这样。我只是感觉Windows CRT更改版本并且破坏兼容性的频率比Linux要高。也许我错了。 - pron
Windows C运行时库是不同的,由于每台机器上只有一个,因此不存在任何问题。您一直在讨论相当不同的MSVC运行时库。 - David Heffernan

4

将您的DLL链接到静态运行时库应该可以工作,但是您必须非常注意内存管理(例如,调用您的DLL的人不能释放或删除由您的DLL分配的任何内容),并且您不能交换标准C数据结构(例如,FILE*)。 (我有遗漏吗?)


嗯,那会是个问题。从迄今为止的答案来看,似乎答案是:“不,你不能这样做。dll和使用它们的应用程序应该使用相同的CRT”。 - pron
顺便问一下,为什么释放由不同CRT分配的对象会有问题?Windows CRT在不同版本之间更改了存储大小信息的方式吗? - pron
1
可能所有的运行时库最终都会调用相同的基本win32函数为进程分配内存。区别在于每个运行时库实例将保留与分配的每个对象(使用new或malloc())相关联的“额外”簿记数据。如果一个DLL使用它自己的运行时分配内存,而进程使用它自己的运行时删除该内存,则其中一个或两者的堆可能会变得损坏。最后的答案是不要混合它们。 - Jon Trauntvein
@pron: 不仅版本之间,还包括配置之间。考虑调试版与发布版的构建。因此,您也不能混合使用这些DLL。在我看来,最好的方法是每个对象都带有销毁自身的手段,而不必依赖于某些外部手段。最终,这将意味着与COM中接口类似的机制,当它们的引用计数降至零时就会消失。 - 0xC0000022L
1
不同的配置可能是更大的问题。CRT调试堆执行一些特殊的簿记(请参见http://www.nobugs.org/developer/win32/debug_crt_heap.html),我预计它与发布版本不兼容。无论如何,不同的CRT版本可能使用相同的堆和相同的簿记方法,但您不能依赖它(因为MS可以在版本之间自由更改任何内容,而不必记录这些更改)。因此,要找出两个版本是否兼容,您需要进行一些实验。我个人没有进行过这样的实验。 - Qwertie

3
您可以通过使用WinAPI调用来实现此目的,包括I/O和其他可能依赖于运行时的内容。
最痛苦的部分是您可能需要覆盖全局的newdelete,以完全使用WinAPI函数,因为它们很可能在内部使用malloc/free。还有许多其他令人痛苦的方面,我的观点是这不值得麻烦。这篇文章涵盖了此主题。

有趣的是,kernel32.lib中的HeapAlloc和HeapFree甚至比MSVCRT dlls的malloc/free实现略微更快。 - x4u
是的,我也认为这不值得麻烦,所以让我们假设我确实需要CRT。 - pron
1
DLL的ABI兼容性较差。我认为您无法确保它可以使用不同的运行时。例如,Qt针对MSVC和Mingw有不同的DLL。 - Tamás Szelei

3

如果您希望以运行时中立的方式公开您的对象,那么我认为除了使用COM之外,没有其他解决方案。


2
好的,C-runtime和C++-runtime之间有很大的区别。如果你使用msvcrt.dll,最近几年被冠以真正的系统DLL,你可以依靠它在XP及其以上版本中存在 (但是对于Windows 2000,你需要一些分发版本6的msvcrt.dll)。你可以通过使用最新的WDK(Windows Driver Kits)编译器来编译代码来利用msvcrt.dll。即使这是用户模式代码,这也是一种可行且良好的编译方法。
然而,IOStreams需要C++-runtime,这让事情变得复杂了很多。
引用: 编辑:将dll链接到静态运行时库也不起作用,因为静态运行时库(来自dll)和动态运行时库(来自客户端应用程序)将混合在一起,这是不好的。
如果你以这种方式混合代码,则设计存在问题。当您在使用Debug版本的DLL与另一个代码的Release版本或反之互相运行时,您会遇到类似的问题。
我只能建议您直接使用COM,或者如果太大,请尝试模拟一些COM的思想。其中最重要的是,您必须有一个工厂函数,并且在这两个代码片段(即DLL和其调用者)之间声明并永远不改变(类)接口。工厂函数将返回类的实例,并且类将自行管理其生命周期(这意味着所有分配和释放的代码将驻留在同一实体中,即您的DLL)。然后通过addref和release成员函数公开生命周期管理。IUnknown可以是您的接口的基础,而不依赖于实际COM的其他部分。
引用: 编辑:我主要问的是如何告诉运行时加载器将我的dll链接到应用程序链接的任何CRT?也许与清单有关?更一般地说,我的问题是如何构建一个良好的dll,供客户端构建自己的应用程序使用?
根本不容易。 即使您安装了所有版本的VS,您仍然必须通过脚本来选择正确的版本以解决这个困境。

0

你的dll是与它编译时链接的c-runtime相关联的。你的应用程序将始终使用此运行时。任何链接到你的dll的人都会使用他们自己的c-runtime。因此,这不会有任何问题。


所以,你的意思是最终应用程序可能与2个不同的运行时链接,这没问题? - pron
这可能不是“好的”。这取决于您的使用方式。 - John Dibling
如果例如您使用一个CRT(C运行时库)来创建一个对象,然后使用另一个CRT来删除它,那可能会非常不好。 - John Dibling
好的,那么如果我想给我的客户一个表现良好的dll,我该怎么做? - pron

0

如果您使用C ++,似乎不可能跨运行时边界,除非您限制可以公开的内容。如前所述,std::对象不起作用(例如std :: string)。

这是一个会导致崩溃的小例子:

类Base
{
public:
virtual ~Base()
{
}
};

类ClassInDll:public Base
{ public: __declspec(dllexport)ClassInDll(int arg);
__declspec(dllexport)~ClassInDll();

private: int _arg;
};

如果将此类编译为VS2008发布模式DLL,并在VS2008调试模式下构建以下内容的.exe:

ClassInDll * c = new ClassInDll(1); delete c;

“delete c”语句会导致崩溃。 这与ClassInDll具有虚拟析构函数有关。


@David Feurle:应用程序将不会使用与DLL相同的运行时,这就是问题的核心。 - Marius Matioc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接