65.9K
CodeProject 正在变化。 阅读更多。
Home

吃你自己的狗粮(自用)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2021年10月7日

CPOL

4分钟阅读

viewsIcon

11868

downloadIcon

88

源代码组织建议

引言

任何大型项目都涉及来自不同来源的代码。在重用自己的代码或从 GitHub 下载东西时,我们必须应对不同的源代码布局、包含文件和库。

这个技巧想展示一种缓解这种痛苦的方法。它是多年实践中获得的许多伤痛的结果,其中许多是自残造成的。还有一点:这不是万能药;它适用于 C/C++ 项目,并且对于使用 Visual Studio 的项目效果更好。

我们将考虑一个常见的情况,即我们开发了两个库 cool_Acool_B,它们需要在应用程序 SuperApp 中重用。

库代码布局

对于每个库,我们都有包含文件、源文件,并且我们生成一个静态链接库。这里有一些 ASCII 艺术展示了通用的代码布局

DevTreeRoot
   |
   +-- cool_A
   |    |
   |    +-- include
   |    |      |
   |    |      +-- cool_A
   |    |            |
   |    |            +-- hdr1.h
   |    |            |
   |    |            +-- hdr2.h
   |    +-- src
   |    |    |
   |    |    +-- file1.cpp
   |    |    |
   |    |    +-- file2.cpp
   |    +-- project file (cool_A.vcxproj) and other stuff
   |
   +-- cool_B
        |
        +-- include
        |      |
        |      +-- cool_B
        |            |
        |            +-- hdr1.h
        |            |
        |            +-- hdr4.h
        +-- src
        |    |
        |    +-- file1.cpp
        |    |
        |    +-- file2.cpp
        |
        +-- project file (cool_B.vcxproj) and other stuff

到目前为止,与您已知的大部分内容没有太大区别,除了以下几点

规则 1 - 需要对用户可见的包含文件放在 include 文件夹的子文件夹中。该子文件夹与库同名。

如果 cool_A 的用户设法将 cool_A/include 放在包含路径中(稍后我们将看到如何做到),他们可以这样引用 hdr1.h 文件

#include <cool_A/hdr1.h>

优点是它避免了不同库之间的名称冲突。在我们的例子中,如果一个程序同时使用 cool_Acool_B,相应的包含指令将是

#include <cool_A/hdr1.h>
#include <cool_B/hdr1.h>

请注意,我没有提到输出库。我很快会讲到。

使用符号链接以获得乐趣和利润

Windows 用户不太习惯符号链接,因为它们在 Windows 世界中出现得相对较晚。然而,它们在管理多个项目方面可以带来显著的好处。

遵循前面所示的结构,使用 cool_Acool_B 的应用程序将有一个 include 文件夹,但在该文件夹中,我们将放置指向 cool_Acool_B 包含文件夹的符号链接。文件夹结构看起来会是这样的(尖括号表示符号链接)

DevTreeRoot
  |
  +-- SuperApp
  |      |
  |      +-- include
  |      |     |
  |      |     +-- <cool_A>
  |      |     |      |
  |      |     |      +-- hdr1.h
  |      |     |      |
  |      |     |      +-- hdr2.h
  |      |     |
  |      |     +-- <cool_B>
  |      |     |      |
  |      |     |      +-- hdr1.h
  |      |     |      |
  |      |     |      +-- hdr4.h
  |      |     other header files
  |      |
  |      +-- src
  |      |    |
  |      |    +-- source files
  |      other files
  ...

要创建这些符号链接,假设您当前的目录是 SuperApp\include,您需要发出以下命令

mklink /d cool_A \DevTreeRoot\cool_A\include\cool_A
mklink /d cool_B \DevTreeRoot\cool_B\include\cool_B

有了符号链接的神奇作用,SuperApp 只需要在其包含路径中包含其 include 文件夹,所有其他使用的库将自动可用。

我们现在可以使用相同的技巧来处理静态链接库。按照惯例,我们将它们放在 lib 文件夹中。但是,这次我们将把 lib 文件夹放在开发树的根目录下,并在每个使用它的项目中放置符号链接。在不重复前面已显示的文件布局部分的情况下,这里是与 lib 文件夹相关的部分(同样,尖括号表示符号链接)

DevTreeRoot
  |
  +-- cool_A
  |     |
  |    ...
  |     +-- <lib>
  |           |
  |           all link libraries are here
  +-- cool_B
  |     |
  |    ...
  |     +-- <lib>
  |           |
  |           all link libraries are here
  +-- SuperApp
  |      |
  |     ...
  |      +-- <lib>
  |            |
  |            all link libraries are here
  +-- lib
       |
       all link libraries are here

规则 2:静态链接库文件夹 lib 存在于开发树的根目录下,并通过符号链接使其对使用它的每个项目可见。

如果存在不同类型的链接库(debug、release、32 位、64 位),它们可以作为 lib 文件夹的子文件夹。

自动化

通过一个简单的批处理文件可以自动创建项目所需的符号链接。我在所有项目中都使用 mklinks.bat 这个名字,对于 SuperApp 来说,它看起来是这样的

rem Make sure DEV_TREE_ROOT environment variable is defined

:MAKELINKS
if not exist lib\nul mklink /d lib %DEV_TREE_ROOT%\lib

pushd "%~dp0include"

if not exist cool_A\nul     mklink /d cool_A %DEV_TREE_ROOT%\cool_A\include\cool_A
if not exist cool_B\nul     mklink /d cool_B %DEV_TREE_ROOT%\cool_B\include\cool_B

popd

与 Visual Studio 集成

这个方案可以轻松应用于任何 C/C++ Visual Studio 项目。只需对项目应用一些配置

  1. 将包含路径设置为(或添加到)$(SolutionDir)include
  2. 对于任何库,将输出路径设置为 $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)\
  3. 对于任何应用程序(或 DLL),将库路径设置为 $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)

最终注释

  • 一些库可能依赖于其他库。在我们的例子中, cool_B 可能使用 cool_A 库。在这种情况下,它只需要一个指向 cool_B 包含文件夹的符号链接。

  • 另外两个关于在 Visual Studio 项目中放置各种文件的建议。它们已被证明能够很好地扩展,即使面对复杂的项目层次结构

    1. 将中间文件目录设置为 $(SolutionDir)o\$(ProjectName)\$(PlatformTarget)\$(Configuration)\
    2. 对于任何应用程序(或 DLL),将输出路径设置为 $(SolutionDir)app\$(ProjectName)\$(PlatformTarget)\$(Configuration)\

使用示例代码

首先,代码既不“酷”也不“超级”。它只是一些演示代码,旨在展示遵循这些规则的好处。下载它并按照以下步骤操作

  • 在所有项目(cool_Acool_BSupperApp)中运行 mklinks.bat 脚本
  • 构建库 cool_Acool_B
  • 构建应用程序 SuperApp

结论

使用符号链接和一些简单的规则可以大大简化代码重用。Visual Studio 中使用的不同设置是多年精心调整的结果,直到我一切都弄对为止。例如,将中间文件目录设置为 $(SolutionDir)o\$(ProjectName)\$(PlatformTarget)\$(Configuration)\ 的建议基于几个目标

  • 将所有对象文件放在一个地方以便于清理项目是很方便的,因此有通用的 o 文件夹。
  • 您可能在一个解决方案中有多个项目,并且需要将它们的中间文件分开,因此需要 $(ProjectName) 部分。
  • 当然,每个“版本”的构建(x86、x64、debug、release 等)都需要分开,因此需要 $(PlatformTarget)\$(Configuration) 部分。

这个系统对于不同的 Git 仓库效果非常好,您只需从每个仓库获取代码并使用符号链接将它们组合起来。

历史

  • 2021 年 10 月 7 日 - 初始版本
© . All rights reserved.