如何正确地将Delphi项目划分为BPL? [英] How to divide a Delphi project into BPLs properly?

查看:975
本文介绍了如何正确地将Delphi项目划分为BPL?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我工作的公司在Delphi中开发一个包含数十个exe模块的系统,如果涉及到源代码,它们在一定程度上是相同的。可悲的是,没有人关心使用库来放置共享代码。这意味着每次在所有这些模块共享的代码中都有一个错误修复,程序员必须对所有这些模块进行更正。它总是需要这么多时间...



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



我把所有的代码所有的exe模块共享到BPL中,一切似乎都很好,但有一些我不明白的东西,如果你向我解释了,那将是很感激的。


  1. 将代码划分为BPL后,我预期的是,使用我创建的BPL来部署exe文件就足够了。但事实证明,他们还需要一个rtl100.bpl和vcl100.bpl。为什么会这样?我只想部署exe和我的BPL。我不想为最终用户提供Borland和第三方公司提供的一大堆图书:)。我希望他们在以前被编译的exes中被编译。是否可以这样做?


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




    • 我将所有共享的pas单位放到BPL中。每个BPL都包含属于同一类别的单元,所以对于程序员而言,在给定的BPL中可以看到什么代码。

    • 每个BPL都是一个运行时和设计时库。
    • 每个BPL都显式重建。
      后者是BPL的默认项目设置。


  3. 如果涉及到exe项目: / p>


    • 我删除了我之前提交给BPL的所有单位。

    • 我从工具 - >在BDS 2006中安装软件包菜单

    • 在我的exe项目设置中,我检查了使用运行时包构建选项,并在下面的编辑框中列出了所有的BPL软件包我的包裹,当我清除所有其他出现在那里的)。


是我所做的一切exe项目正确编译,但是我没有访问BPL的源代码(我无法从我的exe项目导入代码),即使所有的BPL都与源代码文件一起存储。为什么?对我来说似乎很奇怪。



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






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



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



如果一个BPL由许多EXE共享,那么兼容性问题可能会发生,一个BPL的修改对于一个EXE和其他一些不好 - @Warren P.



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




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



解决方案似乎是可以的,只要后修改的代码是精简的。但是我们也拥有一般使用功能和程序的pas单元,通常不会修改 - 我们在必要时添加新功能,但在单个项目中。所以想象你在100个模块之一中写一个新的功能,并把它放入它的一般使用单位。一两个月后,您修改了不同的模块,您认为您需要与2个月前所写的相同的功能。你必须找到这个模块(如果你不记得这个模块是很困难的),并将这个函数复制到你的代码中。显然,每个模块中的一般用途单元变得完全不同,只要它们分别存储在每个项目中即可。然后,如果有一个错误修复,...整个故事重复。




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



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




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



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



非常感谢你。 / p>

解决方案

即使这个问题有一个公认的答案,我会采取一个刺。



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



有几种方法可以这样做:




  • 共享单位

  • Dlls

  • BPLs



无论您走哪个方向,您都可能需要重组您的项目。从您的描述来看,每个项目都是相对孤立开发的。代码使用复制/粘贴进行共享,快速失去同步,并导致大量重复的工作。所以让我们检查分享代码的各种技巧。



共享单位






这是最直接的方法。您可以在项目中创建一个共享位置和地址代码,以便在此位置中重用。这些单元静态链接到您的项目中,因此您不必担心与主要可执行文件一起部署额外的依赖关系。静态链接单元是迄今为止最容易排除和调试的单元。



编译器需要能够找到您的共享单元。有四种方式告诉编译器在哪里查看。


  1. 将它们添加到项目中 - SHIFT + < kbd> F11 - 将单位的引用添加到项目文件(dpr,dproj)中。如果单元位于与项目文件相同的目录树下,则IDE将通常使用相对路径,否则将使用绝对路径,如果开发人员机器未配置相同,则可能会有问题。

  2. 项目的搜索路径 - CTRL + SHIFT + F11 Delphi编译器>搜索路径 - 添加目录并且编译器将看到那里找到项目中任何单元的uses子句中提到的单元。如果可以,最好使用相对路径。您还可以使用环境变量:$(MyPath)

  3. 全局搜索路径 - 工具>选项>环境选项> Delphi选项>库 - Win32>库路径 - 此处列出的任何路径都可用于所有项目都在机器上。 这是与机器相关的

  4. 命令行 - 如果您使用脚本或构建自动化工具构建,可以使用dcc32的 -U switch或者msbuild的 / property:UnitSearchPath = switch。

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



就SVN存储库而言,您可以选择组织项目和共享单元的几个选项。最简单的方法是将所有项目放在单个中继线和共享单元之间:

 项目
trunk
ProjectA
ProjectB
ProjectC
图书馆(共享单位)

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

  ProjectA 
trunk
图书馆(主图书馆的分支)
ProjectB
中继
图书馆(主图书馆的分支)
ProjectC
trunk
图书馆(主图书馆的分支)

树干(主库)

在此配置中,项目的库文件夹将不会立即提供给其他项目。每个项目都需要定期将更改与主库项目进行同步。这样做的一个副作用是破坏其他项目的更改将被延迟,直到其他项目同步。无论你认为这是好还是坏。一方面,在涉及的代码在开发人员的头脑中仍然是新鲜的时候,错误更容易和更便宜。另一方面,如果您不执行单元测试(我强烈建议您执行)或代码非常脆弱,或者您只是让开发人员容易做出鲁莽的更改,您可能需要控制这些更改被推入其他项目的频率



Dlls






Dll允许您通过在运行时链接到它。它们暴露了可以从主可执行文件或另一个DLL中调用的函数。



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



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



您可以安全地使用dll内的对象,但如果要在dll和应用程序之间传递对象,则需要提取对象的数据并将其作为原始类型传递,并将其重新组合到另一个对象中结束。这被称为(de)序列化或编组,并且有更简单的方法来做这个比滚动自己的。



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



Bpls






Bpls(也称为简单的包)是专门的格式化的dll,使得使用对象更容易。像标准的dll一样,它们在运行时链接,可以静态或动态加载。它们比COM dll更容易学习和使用,并且比COM提供更多的seamles集成到项目中。软件包由两部分组成:bpl和dcp。 dcp就像编译一个普通单元文件时生成的dcu文件,除了它包含一大堆单元。使用在bpl中编译的类很简单,将dcp添加到项目的包列表中,然后将该单元添加到项目单位之一的uses子句。



部署应用程序时,您还需要安装bpl。正如其他人所说,你必须将rtl包放在最低限度,最有可能的是vcl包,如果你使用任何表单。有一种方法可以在您的项目中部署Borland提供的bpls。您可以创建一个仅包含项目所需单元的迷你rtl包。



总结






从您给出的创建一个共享单元文件库到静态链接的描述可能是最方便的路线。我还建议您尝试一个名为 Simian 的程序。它将帮助您跟踪代码库中的重复代码,以便包含在共享库中。它不直接支持pascal,但它使用纯文本解析器做了一个体面的工作,稍微调整其配置。



我也不能强调单位测试值。特别是如果你正在转向共享库。一套精心编写的单元测试经常运行,当开发人员改变课程并打破一个无关的项目时,可以立即反馈。


The company I work for develops a system in Delphi, that contains dozens of exe modules, and each of them is identical to a certain degree if it comes to source code. Sadly, nobody has ever cared about using libraries to put the shared code in. This means that each time there is a bug fix to do in the code all these modules share, a programmer has to make corrections in all of them separately! It always takes so much time...

I decided to find a method to put the shared code into libraries. I considered DLLs and BPLs. In this case BPLs seemed much more programmer-friendly and much less troublesome, especially that the code is used only in our software and only in Delphi.

I put all the code shared by all the exe modules into BPLs and everything seems fine, but there are certain things I don't understand and would be grateful if you explained them to me.

  1. What I expected after dividing the code into BPLs was that it would be enough to deploy exe files with the BPLs I created. But it turned out that they need an rtl100.bpl and vcl100.bpl as well. Why is it so? I want to deploy exes and my BPLs only. I don't want to provide end users with a whole bunch of libraries supplied by Borland and third party companies :). I want them to be compiled within exes as they used to be compiled before. Is it possible to do that?

  2. What I did so far was:

    • I put all shared pas units to BPLs. Each BPL contains units belonging to the same category so it is clear for programmers what code to expect in a given BPL.
    • Each BPL is a "runtime and designtime" library.
    • Each BPL is "rebuilt explicitly". The two latter are default project settings for BPLs.
  3. And if it comes to the exe projects:

    • I deleted all units that I had earlier put to BPLs.
    • I installed my BPLs from the Tools->Install package menu in BDS 2006.
    • In my exe project settings I checked the option "build with runtime packages" and I listed all my BPL packages in the edit box below (only my packages, as I cleared all other ones that appeared there).

This is all I did. The exe projects compile properly, but I have no access to the source code of BPLs (I can't navigate into that code from my exe projects), even though all BPLs are stored together with their source code files. Why? It seems strange to me.

I always tend to write lengthy descriptions - sorry for that :). I will appreciate your help. I just need a few words of explanation to the points I mentioned: deploying exe with my BPLs only, the correctness of what I did as a whole, and the inability to navigate into BPL source codes. Thank you very much in advance!


Thank you all for the discussion. Some said the approach I chose was not a good idea. Our software consists of more than 100 modules (most of them being something like drivers for different devices). Most of them share the same code - in most cases classes. The problem is that those classes are not always put into separate, standalone pas units. I mean that the shared code is often put into units containing code specific to a module. This means that when you fix a bug in a shared class, it is not enough to copy the pas unit it is defined in into all software modules and recompile them. Unfortunately, you have to copy and paste the fixed pieces of code into each module, one by one, into a proper unit and class. This takes a lot of time and this is what I would like to eliminate, choosing a correct approach - please help me.

I thought that using BPLs would be a good solution, but it has some downsides, as some of you mentioned. The worst problem is that if each EXE needs several BPLs, our technical support people will have to know which EXE needs which BPLs and then provide end users with proper files. As long as we don't have a software updater, this will be a great deal for both our technicians and end user. They will certainly get lost and angry :-/.

Also compatibility issues may happen - if one BPL is shared by many EXEs, a modification of one BPL can bee good for one EXE and bad for some other ones - @Warren P.

What should I do then to make bug fixes quicker to make in so many projects? I think of one of the following approaches. If you have better ideas, please let me know.

  • Put shared code into separate and standalone pas units, so when there is a bug fix in one of them, it is enough to copy it to all projects (overwrite the old files) and recompile all of them.

This solution seems to be OK as far as a rearly modified code is concrened. But we also have pas units with general use functions and procedures, which often undrego modifications - we add new functions there whenever necessary, but in single projects. So imagine that you write a new function in one of the 100 modules and put it into its general use unit. After a month or two you modify a different module and you think you need the same function you wrote 2 months ago. You have to find the module (it's difficult if you don't remember which one it was) and copy the function to your code. And obviously - the general use units become completely different in each module as long as they are stored in each project separately. And then, if there is a bug fix to do... the whole story repeats.

  • Create BPLs for all the shared code, but link them into EXEs, so that EXEs are standalone.

For me it seems the best solution now, but there are several cons. If I do a bug fix in a BPL, each programmer will have to update the BPLs on their computer. What if they forget? But still, I think it is a minor problem. If we take care of informing each other about changes, everything should be fine.

  • @CodeInChaos: I don't know if I understood you properly. Do you mean sharing pas files between projects? How to do that? We store source codes in SVN. This means that we would have to store shared code in a separate folder and make all projects search for that code there, right? And download from the SVN a project and all folders it is dependent on...

Please, help me choose a good solution. I just don't want the company to lose much more time and money than necessary on bugfixes just because of a stupid approach to software development.

Thank you very much.

解决方案

Even though this question has an accepted answer I'm going to take a stab at it.

The title asks how to divide a project into bpls but the real question appears to be: "What's the best way to share code between projects?"

There are a few ways to do this:

  • Shared units
  • Dlls
  • BPLs

Regardless of which direction you go you will likely need to restructure your projects. From your description it sounds like each project is developed in relative isolation. Code is shared using copy/paste, which quickly gets out of sync and result in a lot of duplicated effort. So lets examine each of the techniques for sharing code.

Shared units


This is the most straightforward approach. You create a shared location and place code you would like to reuse among your projects into this location. The units are statically linked into your projects so you don't need to worry about deploying extra dependencies along with the main executables. Statically linked units are by far the easiest to troubleshoot and debug.

The compiler needs to be able to find your shared units. There are 4 ways to tell the compiler where to look.

  1. Add them to the project - SHIFT+F11 - Adds a reference to the unit into the project files (dpr, dproj). The IDE will normally use relative paths if the unit is located under the same directory tree as the project files, otherwise it will use absolute paths, which can be problematic if developer machines aren't configured identically.
  2. The project's Search Path - CTRL+SHIFT+F11 Delphi Compiler > Search path - Add a directory and the compiler will look there to find units mentioned in the uses clause of any unit in the project. Its best to use relative paths if you can. You can also use environment variables: $(MyPath)
  3. Global Search Path - Tools > Options > Environment Options > Delphi Options > Library - Win32 > Library Path - Any paths listed here are available to all projects on a machine. This is machine dependant
  4. Command line - If you build from a script or build automation tool you can set the search path using the dcc32's -U switch or msbuild's /property:UnitSearchPath= switch.

Options 1 and 2 will be the most useful.

As far as your SVN repository goes you have a few options for organizing the projects and shared units. The simplest would be to place all projects under single trunk along with the shared units:

Projects
    trunk
        ProjectA
        ProjectB
        ProjectC
        Library (shared units)

If for some reason the above structure isn't possible you could try this alternative:

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)

In this configuration changes made to each project's library folder would not be immediately available to the other projects. Each project would need to synchronize changes with the main Library project on a regular basis. A side effect of this is that changes that break other projects will be delayed until the other projects are synchronized. Whether you consider this a good or bad thing depends. On the one hand bugs are easier and cheaper to fix when the code they involve is still fresh in the developer's mind. On the other hand if you don't practice unit testing (which I highly recommend you do) or the code is very fragile or you just have developers prone to making reckless changes you may want to control how frequently those changes get pushed into other projects.

Dlls


Dlls allow you to share code by linking to it at runtime. They expose functions that can be called from a main executable or another dll.

While dlls are always linked at runtime you decide whether they are loaded at application startup or only when needed. Loading at startup is called static loading and in Delphi is accomplished using the external directive. The vast majority of the rtl/vcl classes that wrap system api calls use static loading. Dynamic loading lets to delay the loading of a dll until it is required. This uses the WinAPI functions LoadLibrary and GetProcAddress. A corresponding call to FreeLibrary will unload a dll.

Unfortunately standard dlls limit what kind of datatypes can be passed. If you need to access a dll from non-Delphi projects you will need to limit yourself to using c style data types. If you will only be using a dll with Delphi projects you can safely use Delphi strings and dynamic arrays as well if you use the SharedMem unit in the dll and any projects that use it.

You can safely use object's within the dll without problems but if you want to pass objects between the dll and the application you'll need to extract the object's data and pass it as primitive types and reassemble it into an object on the other end. This is called (de)serialization or marshalling and there are much easier ways to do this than rolling your own.

COM (Component Object Model) is well supported in Delphi but it has a bit of a learning curve. Consuming COM objects is pretty straightforward but designing one will take time if you're not familiar with COM. COM has the advantage that it is language neutral and is supported in the majority of languages targeting the Windows platform (including languages targeting the .NET framework).

Bpls


Bpls (also called simply "packages") are specially formatted dlls that make working with objects a lot easier. Like standard dlls they are linked at runtime and can be statically or dynamically loaded. They are easier to learn and use than COM dlls and provide more seamles integration into your projects than COM. Packages are composed of two parts: the bpl and the dcp. The dcp is like the dcu files generated when you compile a normal unit file except it contains a whole bunch of units in it. Using a class that is compiled in a bpl is as simple as adding the dcp to the project's package list then adding a unit to a uses clause of one of the project's units.

When you deploy the app you'll need to install the bpl as well. As other's have noted you have to include the rtl package at a minimum and most likely the vcl package if you use any forms. There is a way around deploying Borland supplied bpls with your projects. You can create a "mini" rtl package that contains only the units your project need. The difficultly is in determining which units to include.

Summary


From the description you've given creating a library of shared unit files to statically link against may be the most expedient route. I would also suggest trying out a program called Simian. It will help you track down duplicate code in your code base for inclusion in your shared library. It doesn't directly support pascal but it does a decent enough job using the plain text parser with a little tweaking of its configuration.

Also I can't stress enough the value of unit testing. Especially if you're moving toward shared libraries. A suite of well written unit tests run on a frequent basis will give you instant feedback when a developer changes a class and it breaks an unrelated project.

这篇关于如何正确地将Delphi项目划分为BPL?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆