【问题标题】:Dynamically loaded BPL's sharing code / passing objects动态加载 BPL 的共享代码/传递对象
【发布时间】:2011-03-09 16:08:25
【问题描述】:

我正在玩弄使用动态加载 BPL 并将对象实例从主应用程序传递到 BPL 中的方法的想法。这在应用程序和 BPL 使用的单元之间造成了问题。

我编写了一个小型原型来执行此操作,并且很好奇 Delphi 如何在内部管理应用程序中定义的类与 BPL 中定义的类之间的差异。

例如,说一个基本的 Widget 类,如:

TmyWidget = class
private
  fId:Integer;
  fDescription:String;
public
  procedure DoSomething1();
end;

现在应用程序和 BPL 是使用包含 TmyWidget 类的单元构建的。后来,TMyWidget 发生了一些变化,应用程序被重建,但 BPL 没有(反之亦然)。我添加了另一个方法 DoSomething2() 并在应用程序中创建了一个 TmyWidget 实例并将其传递给 BPL 进行处理并输入基本的例子,它奏效了。但它显然充满了潜在的问题。

如果另一个动态加载的 BPL 也使用 TmyWidget,那么事情会变得更加有趣。它似乎有效,但绝对感觉不理想。

主要问题是 - 通常如何在主应用程序和 DLL 或 BPL 之间传递对象?我以前从未尝试过,可能是有充分理由的,但我有一个适合这种方法的想法......

我想最好的方法是序列化对象并将这些字节传递过来并在 DLL/BPL 中反序列化它,这个过程要注意主机和动态加载的模块之间的潜在版本差异,但我希望新的 SimpleSharedMem 选项可能会带来这个新功能而无需序列化开销,但它似乎不是很有用,除非您严格保持应用程序和 dll 在任何共享代码更改上重建......但在这个原型中,应用程序将保持相当稳定,动态加载的模块会随着功能添加到 TmyWidget 而频繁更改。 (服务器应用程序充当基于客户端请求构建 TmyWidget 的工厂,应用程序会将实例传递给各个模块进行处理。)

【问题讨论】:

  • 当然正确的解决方案是每个单元只有一个实例。为什么你有两次相同的单位?你不能像大自然一样组织代码来避免这种情况吗?
  • 是的,它可以(应该)被重新组织——它开始于一个想法,变成了加载 BPL,而这些 BPL 开始是带有一些共享代码的 DLL,没有分段并且出于某种原因,它起作用了,我有点想知道为什么。

标签: delphi bpl


【解决方案1】:

...很好奇 Delphi 如何在内部管理应用程序中定义的类与 BPL 之间的差异

Delphi 通过不允许它来管理它。您不能同时在多个包中拥有一个具有相同名称的单元:如果这样做,您会收到一条类似于Package XYZ already contains ABC 的错误消息(有一段时间没看到...)。由于类型名称包括单元名称,因此您不能在两个不同的包中具有相同的类型。除非它是由它的 GUID 定义的接口,但那是另一回事。

...通常如何在主应用程序和 DLL 或 BPL 之间传递对象?

您不将对象传递给 DLL,这不是一个好主意。当您需要将对象传递给 BPL 时,请确保将该 BPL 的基类定义为第三个 BPL。

示例。 TmyWidget 的多态行为可能是使用一些虚拟方法定义的。确保您有一个定义所有这些虚拟方法的TmyWidgetBase 类,从该基类派生所有TmyWidget 并传递TmyWidgetBase 类型的对象。确保 TmyWidgetBase 类在它自己的包中。

当我尝试这样做时,我最终得到了一个很小的“引导”exe 和许多 BPL。基本上所有的逻辑都在 BPL 中,以便于传递对象。

【讨论】:

  • 注意 - 我没有收到“包已包含 xx”错误,其中两个动态加载的 BPL 包含相同的 TmyWidget 单元(在运行时使用 LoadPackage 动态加载)没有错误..帮助说它不可能发生。
  • @Darian,不幸的是我现在不能测试这个。如果包之间没有“require”条件,Delphi 可能不会对重复的单元名称进行测试。
  • 如果我使用运行时包构建应用程序,则会收到错误消息,否则不会出现错误。然后它就像一个普通的 DLL,调用了初始化/终结。
  • @Darian,当您希望能够在模块(exe/dll)之间传递对象时,包工作得很好。但重要的是,您只能跨模块边界传递类型,这些类型已在两个模块都需要的包中定义(对于作为 requires 子句的包,对于 exe 和 dll 是您在编译器选项中列出的运行时包)。
  • 如果 AValidatePackage 函数返回“True”,则会绕过重复的单元检查。请注意,如果加载了两个包含相同单元和类型的包,这可能会导致奇怪和不可预测的行为。 - 来自 SysUtils.pas。在文档中的任何地方都没有看到这个。
【解决方案2】:

我从事的一个项目已经成功使用了大量运行时包十多年了,所以我将分享一些我处理包的经验。

正如 Cosmin 指出的那样,不同的包装不能包含相同的单位。如果您使用隐式链接,通过将包添加到另一个包的 requires 子句或通过将包添加到项目选项中的 运行时包 列表中,编译器将执行为您工作并报告以下错误消息之一:

E2199: Packages '%s' and '%s' both contain unit '%s'(如果你的编译项目依赖于两个包含相同单元的包)

E2200: Package '%s' already contains unit '%s'(如果您编译的包包含一个单元,该单元已经包含在它所依赖的包之一中)

如果您使用显式链接,使用 LoadPackage,通常会在运行时尝试检查(尽管它可以被规避)并引发:

EPackageError: 无法加载包 “%s。”它包含单元“%s”,即 也包含在包“%s”中

解决这些错误并不是那么困难。

如果您有两个包都需要使用一个单元,只需让其中一个包含该单元,另一个需要第一个。

如果您有两个需要使用彼此包含的单元的包,您必须将这些单元移动到一个新包中,这两个包都可以依赖。

隐式链接包的优点是您可以直接访问类定义,就好像它们是静态链接的一样。只需将一个单元添加到您需要使用它的单元的 uses 子句中。编译器和运行时环境会负责解决所有问题。

显式链接的包将需要依赖初始化部分中的类注册。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-09
    相关资源
    最近更新 更多