轻松地将许多重要的“静态库项目”重构为“ dll项目”

时间:2019-05-29 07:55:35

标签: c++ visual-studio dll visual-studio-2017 c++17

我有6个静态库项目:-

- Math
- ECS             : depends on Math
- Utility         : depends on ECS
- Physics         : depends on Utility         
- Graphics        : depends on Utility     
- BaseGame        : depends on Physics and Graphics         
- Some game (.exe): depends on BaseGame      
(The "depends" here is transitive e.g. BaseGame also depends on ECS.)    

我通过“静态库”技术成功使用了6个项目。

今天,我听说动态库可以减少编译时间(我们不讨论这是否成立),
因此,我阅读了以下链接,并成功创建了一个小型演示。

我的测试演示中有一些代码:-

#ifdef SomeName1_EXPORTS
#define SomeMacro1 __declspec(dllexport) 
#else
#define SomeMacro1 __declspec(dllimport) 
#endif
SomeMacro1 void someFunction(int someParam);

现在,将其应用于我的真实项目是令人兴奋的时刻。

第一步,我要导出6个库的所有函数和类。
我假设必须将SomeMacro1(每个项目不同)添加到所有6个项目(〜100K行)中的每个函数中,对吗?

那是一个巨大的重构。
有没有更简单的方法?我会错过一些非常重要的事情吗?

其他说明:-

  • 我想轻松地将库项目切换回静态库(以防出现问题)。
  • 我更喜欢跨平台解决方案。 (例如,如果我以后要在Linux中运行,则无需进行普遍的重构)
  • 我更喜欢这样一种解决方案:当我将源文件从一个项目剪切并粘贴到另一个项目时,重构的成本(以代码形式)不会比正常情况下增加。
    (因为SomeMacro1特定于项目)

类似的问题:How to convert a static library project into a dll project in VS2005

赏金原因

感谢 Andriy Tylychko 的回答,它提供了有用的警告,并暗示重构将不可避免地变得复杂,但是我仍然相信有一些简单的方法可以重构我的项目。

现在,我希望将库项目更改为动态库。 (更快的编译)
然后,当我发货时,我会将它们转换回静态库。 (性能更好)

4 个答案:

答案 0 :(得分:3)

要通过将所有自由函数标记为导出/导入来减少重构所需的方法,一种更简便的方法是将这些函数放在一个类中,并且只需要将该类标记为导出/导入,然后导出所有函数也。

https://docs.microsoft.com/en-us/cpp/cpp/using-dllimport-and-dllexport-in-cpp-classes?view=vs-2019


在Visual Studio中,将项目配置为根据目标生成类型的不同而有所不同,我建议使用“配置”。 默认情况下,准备的是:

  • 调试
  • 发布

可以创建新配置。

对于此示例,我们考虑一下

  • 调试版本应使用动态dll,
  • 发行版应使用静态dll

也可以将项目设置为静态lib或dll(我们称其为“混合lib”),然后在“属性”->“常规”->“配置类型”中选择:

  • 动态dll(.dll)
  • 静态库(.lib)
  • ...

因此,对于“调试”项目,请选择“动态库”。

对于“发布”项目,请选择“静态库”。

(请注意-通过更改项目类型,并非所有设置都被正确修改了!必须仔细检查所有内容。)

根据所选配置,可以有不同的设置。例如,在“预处理器”选项卡上,有不同的定义。

因此,如果选择“调试”,则定义“ _DEBUG”可用。在发行版中,“ NDEBUG”可用。这些定义可以在代码内部使用:

#ifdef _DEBUG

#ifdef SomeName1_EXPORTS
#define SomeMacro1 __declspec(dllexport) 
#else
#define SomeMacro1 __declspec(dllimport) 
#endif

#else

#define SomeMacro1

#endif

SomeMacro1 void someFunction(int someParam);

必须在混合dll和应用程序中正确配置这些配置。

答案 1 :(得分:2)

您不必将宏添加到函数定义中,只需将所有在头文件中进行的声明添加到其他编译单元即可。对于头文件中定义的函数,无需导出该函数,因为头中已经有原始源。

这是最简单,最独立于平台的方式。如前所述,.def文件也可以在Visual Studio上使用,但是您必须列出每个函数名称两次,因此它并不是非常干燥。

老实说,十万行代码还不错。您也许可以摆脱正则表达式的烦恼,为您完成大部分工作。

我看到有一种方法可以使用dumpbin为您生成一个.def文件。 This comment mentions a way to generate the .def file for all function definitions in your .o files

您只需要将宏添加到函数声明中,而不仅是定义。您还必须决定如何导出课程。

如前所述,只要每个项目有单独的编译单元,并且不仅将所有源代码都包含在一个编译单元中(* .o文件是每个编译单元创建的),dll都不会提高编译时的性能。全部。

答案 2 :(得分:1)

严格来说,转换为DLL将主要减少链接的时间,但是您会注意到(在大多数情况下微妙的)性能下降,因为现在“整个程序优化”的优化空间要小得多,例如内联跨不同二进制文件的函数。

动态链接库和静态链接库是完全不同的野兽,DLL需要更多的关注。您需要仔细设计他们的公共API并相应地定义它(通常由您所呈现的宏)。例如。不建议在DLL公共API中使用依赖于特定类型的C运行时(例如调试和发布)的标准库类型(以及许多其他类型)-主要是要避免在一个二进制文件中分配内存,而在另一个二进制文件中释放内存。但这并不总是一个问题。

在静态库和动态库之间来回切换没有多大意义。

静态库通常更简单,并且不关心公共API,因为一切都可以从外部自动访问。例如。数学库通常是静态的,因为几乎所有功能都应该导出,并且它们(通常)没有任何复杂的内部“业务”逻辑。

更大的库具有您要隐藏的“业务”逻辑(例如,具有更大的修改自由),并且定义良好的公共API可以带来长期的好处。例如。如果未更改API,则只能更新一个小的DLL而不是一个庞大的EXE。

由于很难进行周密的计划,因此随着项目的成熟,经常需要将静态库转换为DLL(一次)。在这种情况下,您不需要导出每个类和函数,而是要仔细选择应该导出的内容,并在理想情况下重构现有的API以更好地适应新的现实。幸运的是,您似乎没有任何循环依赖,因为它们可能会增加很多麻烦。

在DLL中,重构不可避免地变得更加复杂,但这不是一个大问题。如果您的lib看起来应该是DLL-将其设为DLL,则优点将大于不那么容易复制/粘贴的缺点。

在您的示例中,由于知识有限,所以纯粹是猜测,看起来只有物理,也许图形应该是DLL,也许还有BaseGame。

答案 3 :(得分:1)

  

现在,我希望将库项目更改为动态库。   (更快的编译)然后,当我发货时,我会转换它们   回到静态库。 (更好的表现)

动态链接库与静态库不同,在链接时间的潜在收益之外,它并没有真正为“更快的编译”提供好处,因为您基本上是在链接较小的模块(n#dll + 1 exe) ),而不是1个大exe。如果您打算将.lib静态链接到您的exe,则它在很大程度上违反了将事物转换为dll的目的,因为它是您始终不打算使用的dll。

如果要分解项目以便可以替换项目中的各个模块,则这是转到dll的参数。例如,您要修改图形而不必重新链接整个可执行文件,可以将“ Graphics”包装到Graphics.dll中,使.exe依赖Graphics.dll,然后在对Graphics进行更改时,只需构建并在本地替换Graphics.dll而不是整个可执行文件,以缩短更改/测试所需的时间。如果exe永远需要链接,则可以大大提高生产率。

专门针对Windows:

鉴于您的链接,我做出以下假设:

  • 您正在为Windows开发
  • 您正在使用某些版本的Visual Studio和Microsoft C编译器。

如果您在exe上使用GLLTCG标志,则有一个小参数不构建dll,因为gl / ltcg应用于模块并具有所有静态libs链接在一起可以使您在GL / LTCG上获得更好的收益,尽管链接时间更长(不建议用于调试类型的构建)。

如果要将库包装为dll,可以考虑使用C++/WinRT构建dll,该dll在Visual Studio的现代版本中具有支持,或者由于某种原因而无法启动Windows Runtime,则可以可以查看COM dlls