13

我工作的公司用Delphi开发了一个系统,里面有几十个exe模块,每个exe模块在源代码上都有一定程度的相同。可悲的是,从来没有人关心使用库来放入共享代码。这意味着每次在所有这些模块共享的代码中需要修复错误时,程序员都必须单独对它们进行更正!总是要花很多时间...

我决定找到一种方法将共享代码放入库中。我考虑过 DLL 和 BPL。在这种情况下,BPL 似乎对程序员更友好,麻烦也更少,尤其是代码仅在我们的软件中使用,并且仅在 Delphi 中使用。

我将所有 exe 模块共享的所有代码放入 BPL 中,一切似乎都很好,但是有些事情我不明白,如果您向我解释,我将不胜感激。

  1. 在将代码划分为 BPL 之后,我所期望的是,使用我创建的 BPL 部署 exe 文件就足够了。但事实证明,他们也需要 rtl100.bpl 和 vcl100.bpl。为什么会这样?我只想部署 exe 和我的 BPL。我不想为最终用户提供由 Borland 和第三方公司提供的一大堆库:)。我希望它们像以前编译的那样在 exe 中编译。有可能这样做吗?

  2. 到目前为止我所做的是:

    • 我将所有共享的 pas 单元放到 BPL 中。每个 BPL 都包含属于同一类别的单元,因此程序员很清楚在给定的 BPL 中期望什么代码。
    • 每个 BPL 都是一个“运行时和设计时”库。
    • 每个 BPL 都是“显式重建”的。后两者是 BPL 的默认项目设置。
  3. 如果涉及到 exe 项目:

    • 我删除了之前放入 BPL 的所有单元。
    • 我从 BDS 2006 中的 Tools->Install package 菜单安装了我的 BPL。
    • 在我的 exe 项目设置中,我选中了“使用运行时包构建”选项,并在下面的编辑框中列出了我所有的 BPL 包(只有我的包,因为我清除了那里出现的所有其他包)。

这就是我所做的一切。exe 项目可以正确编译,但我无法访问 BPL 的源代码(我无法从我的 exe 项目中导航到该代码),即使所有 BPL 都与其源代码文件一起存储。为什么?我觉得很奇怪。

我总是倾向于写冗长的描述 - 对不起:)。我会感谢你的帮助。我只需要对我提到的几点进行几句解释:仅使用我的 BPL 部署 exe,我所做的整体工作的正确性,以及无法导航到 BPL 源代码。非常感谢您!


谢谢大家的讨论。有人说我选择的方法不是一个好主意。我们的软件由 100 多个模块组成(其中大部分是不同设备的驱动程序)。它们中的大多数共享相同的代码 - 在大多数情况下是类。问题是这些类并不总是放在单独的、独立的 pas 单元中。我的意思是共享代码通常被放入包含特定于模块的代码的单元中。这意味着当您修复共享类中的错误时,仅将其定义的 pas 单元复制到所有软件模块中并重新编译它们是不够的。不幸的是,您必须将固定的代码片段一个接一个地复制并粘贴到适当的单元和类中。这需要很多时间,这就是我想消除的,选择正确的方法 - 请帮助我。

我认为使用 BPL 将是一个很好的解决方案,但正如你们中的一些人所提到的,它有一些缺点。最糟糕的问题是,如果每个 EXE 需要多个 BPL,我们的技术支持人员必须知道哪个 EXE 需要哪些 BPL,然后为最终用户提供适当的文件。只要我们没有软件更新程序,这对我们的技术人员和最终用户来说都是一笔大买卖。他们肯定会迷路和生气:-/。

还可能发生兼容性问题 - 如果一个 BPL 由多个 EXE 共享,则对一个 BPL 的修改可能对一个 EXE 有利而对其他一些 EXE 不利 - @Warren P.

那么我应该怎么做才能在这么多项目中更快地修复错误?我想到了以下方法之一。如果您有更好的想法,请告诉我。

  • 将共享代码放入单独和独立的 pas 单元中,因此当其中一个有错误修复时,将其复制到所有项目(覆盖旧文件)并重新编译所有项目就足够了。

就后面修改的代码而言,这个解决方案似乎没问题。但是我们也有具有通用功能和程序的 pas 单元,这些单元通常不需要修改——我们会在必要时添加新功能,但在单个项目中。所以想象一下,你在 100 个模块中的一个模块中编写了一个新函数,并将其放入它的通用单元中。一两个月后,您修改了一个不同的模块,并且您认为您需要两个月前编写的相同功能。您必须找到该模块(如果您不记得它是哪个模块,这很困难)并将该函数复制到您的代码中。并且很明显 - 只要将它们分别存储在每个项目中,每个模块中的通用单位就会变得完全不同。然后,如果有一个错误修复要做......整个故事重复。

  • 为所有共享代码创建 BPL,但将它们链接到 EXE,以便 EXE 是独立的。

对我来说,这似乎是现在最好的解决方案,但有几个缺点。如果我在 BPL 中修复错误,每个程序员都必须更新他们计算机上的 BPL。如果他们忘记了怎么办?但是,我仍然认为这是一个小问题。如果我们注意互相通知变化,一切都应该没问题。

  • @CodeInChaos:我不知道我是否理解正确。你的意思是在项目之间共享pas文件吗?怎么做?我们将源代码存储在 SVN 中。这意味着我们必须将共享代码存储在一个单独的文件夹中,并让所有项目都在那里搜索该代码,对吧?并从 SVN 下载一个项目及其依赖的所有文件夹......

请帮我选择一个好的解决方案。我只是不希望公司仅仅因为软件开发的愚蠢方法而在错误修复上浪费更多的时间和金钱。

非常感谢。

4

4 回答 4

11

即使这个问题有一个公认的答案,我也会尝试一下。

标题询问如何将项目划分为 bpls,但真正的问题似乎是:“在项目之间共享代码的最佳方式是什么?”

有几种方法可以做到这一点:

  • 共享单元
  • DLL
  • BPL

无论您走向哪个方向,您都可能需要重组您的项目。根据您的描述,听起来每个项目都是在相对孤立的情况下开发的。代码是使用复制/粘贴共享的,这很快就会失去同步并导致大量重复工作。因此,让我们检查每种共享代码的技术。

共享单元


这是最直接的方法。您创建一个共享位置并将您希望在项目中重用的代码放置到此位置。这些单元静态链接到您的项目中,因此您无需担心与主要可执行文件一起部署额外的依赖项。到目前为止,静态链接单元是最容易排除故障和调试的。

编译器需要能够找到您的共享单元。有 4 种方法可以告诉编译器在哪里查找。

  1. 将它们添加到项目中 - SHIFT+ F11- 将对单元的引用添加到项目文件(dpr、dproj)中。如果单元位于与项目文件相同的目录树下,IDE 通常会使用相对路径,否则它将使用绝对路径,如果开发人员机器的配置不同,这可能会出现问题。
  2. 项目的搜索路径- CTRL+ SHIFT+ F11Delphi Compiler > Search path - 添加一个目录,编译器会在那里查找项目中任何单元的uses子句中提到的单元。如果可以的话,最好使用相对路径。您还可以使用环境变量:$(MyPath)
  3. 全局搜索路径 - 工具 > 选项 > 环境选项 > Delphi 选项 > 库 - Win32 > 库路径 - 此处列出的任何路径都可用于机器上的所有项目。这取决于机器
  4. 命令行 - 如果您从脚本或构建自动化工具构建,您可以使用 dcc32 的-U开关或 msbuild 的/property:UnitSearchPath=开关设置搜索路径。

选项 1 和 2 将是最有用的。

就您的 SVN 存储库而言,您有一些用于组织项目和共享单元的选项。最简单的方法是将所有项目与共享单元一起放在单个主干下:

Projects
    trunk
        ProjectA
        ProjectB
        ProjectC
        Library (shared units)

如果由于某种原因上述结构是不可能的,你可以试试这个替代方案:

ProjectA
    trunk
        Library (branch of main library)
ProjectB
    trunk
        Library (branch of main library)
ProjectC
    trunk
        Library (branch of main library)
Library
    trunk (main library)

在此配置中,对每个项目的库文件夹所做的更改不会立即用于其他项目。每个项目都需要定期与主库项目同步更改。这样做的副作用是破坏其他项目的更改将被延迟,直到其他项目同步。您认为这是好事还是坏事取决于。一方面,当开发人员对它们所涉及的代码还记忆犹新时,它们更容易修复且成本更低。另一方面,如果您不进行单元测试(我强烈建议您这样做)或者代码非常脆弱,或者您只是让开发人员容易做出鲁莽的更改,您可能希望控制这些更改被推送到其他项目的频率.

DLL


Dll 允许您通过在运行时链接到代码来共享代码。它们公开可以从主可执行文件或另一个 dll 调用的函数。

虽然 dll 始终在运行时链接,但您可以决定它们是在应用程序启动时加载还是仅在需要时加载。在启动时加载称为静态加载,在 Delphi 中是使用external指令完成的。包装系统 api 调用的绝大多数 rtl/vcl 类都使用静态加载。动态加载允许延迟加载 dll 直到需要它。这使用 WinAPI 函数 LoadLibrary 和 GetProcAddress。对 FreeLibrary 的相应调用将卸载 dll。

不幸的是,标准 dll 限制了可以传递的数据类型。如果您需要从非 Delphi 项目访问 dll,您将需要限制自己使用 c 样式数据类型。如果您只在 Delphi 项目中使用 dll,那么如果您在 dll 和任何使用它的项目中使用 SharedMem 单元,您也可以安全地使用 Delphi 字符串和动态数组。

您可以安全地在 dll 中使用对象而不会出现问题,但如果您想在 dll 和应用程序之间传递对象,则需要提取对象的数据并将其作为原始类型传递,然后在另一端将其重新组合成一个对象。这称为(反)序列化或编组,有比滚动自己更简单的方法来做到这一点。

COM(组件对象模型)在 Delphi 中得到很好的支持,但它有一点学习曲线。使用 COM 对象非常简单,但如果您不熟悉 COM,那么设计一个对象需要时间。COM 的优势在于它是语言中立的,并且支持大多数面向 Windows 平台的语言(包括面向 .NET 框架的语言)。

BPLS


Bpls(也简称为“包”)是经过特殊格式化的 dll,可以更轻松地处理对象。与标准 dll 一样,它们在运行时链接,可以静态或动态加载。它们比 COM dll 更容易学习和使用,并且提供比 COM 更多的无缝集成到您的项目中。包由两部分组成:bpl 和 dcp。dcp 类似于编译普通单元文件时生成的 dcu 文件,不同之处在于它包含一大堆单元。使用在 bpl 中编译的类就像将 dcp 添加到项目的包列表中,然后将一个单元添加到项目单元之一的使用子句中一样简单。

部署应用程序时,您还需要安装 bpl。正如其他人所指出的,如果您使用任何形式,您必须至少包含 rtl 包,并且最有可能包含 vcl 包。有一种方法可以在您的项目中部署 Borland 提供的 bpls。您可以创建一个仅包含项目所需单元的“迷你”rtl 包。困难在于确定要包括哪些单元。

概括


根据您给出的描述,创建一个共享单元文件库以静态链接可能是最方便的途径。我还建议尝试一个名为Simian的程序。它将帮助您跟踪代码库中的重复代码以包含在您的共享库中。它不直接支持pascal,但使用纯文本解析器并对其配置进行了一些调整,它做得足够好。

我也不能足够强调单元测试的价值。特别是如果您正在转向共享库。当开发人员更改一个类并且它破坏了一个不相关的项目时,一套频繁运行的编写良好的单元测试将为您提供即时反馈。

于 2011-09-08T17:24:29.420 回答
8

想象一下,您有一个项目,其中包含一个 EXE 和两个不同的 BPL 模块,并且在该代码库的某个地方,有一行写着if MyObject is TStringList then DoSomething;. 操作员通过检查存储在 VMT 中的对象的类元数据来工作,然后通过指针is跟踪一系列 VMT,查看它们中是否有任何与. 为了确保它能够正常工作,无论它被分成多少个 BPL,都需要一个用于 TStringList 的单一 VMT,它在整个程序中都是相同的,这意味着它必须在自己的包中。这就是为什么需要像 rtl*.bpl 和 vcl*.bpl 这样的系统运行时,而您对此无能为力。这是使用 BPL 价格的一部分。ClassParentTStringList

至于无法调试,您需要确保 BPL 是在启用调试信息的情况下构建的,并且调试器知道如何找到 DCP(包含 BPL 调试信息的文件)所在的文件夹。而且您将无法追踪到系统 BPL,因为您的版本未附带启用调试的 DCP。它们是最近才添加的,我认为是在 XE 中,但可能是在 D2010 中。

于 2011-08-12T16:53:24.123 回答
2

为什么我不能浏览我的源代码?有没有办法来解决这个问题?

您无法浏览包中包含的单元的源代码,因为它们不在您的项目、库或搜索路径中。

配置您的搜索路径

我解决这个问题的方法是将目录添加到项目搜索路径中。这样编译器不知道这些文件(并且不会尝试重新编译它们),但 IDE 让您浏览它们的内容并调试它们。

于 2011-08-12T17:29:31.417 回答
1

“在我的 exe 项目设置中,我选中了“使用运行时包构建”选项

这就是为什么没有 BPL 等就无法部署的原因——这个选项让很多开发人员感到困惑—— “使用运行时包构建”意味着你需要在运行时存在 bpl。取消选中该选项,包将在编译时链接到您的 exe。(你的 exe 会变大。)“使用运行时包构建”背后的想法是保持 exe 的大小并允许多个应用程序共享公共 bpl,因为它们没有链接到 exe @ compileTime - 这是好的一面。您现在遇到的缺点 - 您必须将 bpl 与您的 exe 一起分发。

于 2011-08-13T04:02:40.253 回答