【问题标题】:How do I solve an unresolved external when using C++ Builder packages?使用 C++ Builder 包时如何解决未解决的外部问题?
【发布时间】:2011-02-13 04:41:53
【问题描述】:

我正在尝试重新配置我的应用程序以大量使用包。在使用几个不同的包进行链接时,我和另一个运行类似实验的开发人员都遇到了一些麻烦。我们可能都做错了什么,但天知道是什么:)

情况是这样的:

  • 第一个包PackageA.bpl 包含C++ 类FooA。该类使用PACKAGE 指令声明。
  • 第二个包PackageB.bpl 包含一个继承自FooA 的类,称为FooB。它包括FooB.h,并且该包是使用运行时包构建的,并通过添加对PackageA.bpi 的引用来链接到PackageA

  • 在构建PackageB 时,它可以正常编译,但链接失败并出现许多未解决的外部问题,其中前几个是:

    • [ILINK32 Error] Error: Unresolved external '__tpdsc__ FooA' referenced from C:\blah\FooB.OBJ
    • [ILINK32 Error] Error: Unresolved external 'FooA::' referenced from C:\blah\FooB.OBJ
    • [ILINK32 Error] Error: Unresolved external '__fastcall FooA::~FooA()' referenced from blah\FooB.OBJ

    等等

PackageA.bpl 上运行 TDump 显示:

Exports from PackageA.bpl
  14 exported name(s), 14 export addresse(s).  Ordinal base is 1.
  Sorted by Name:
    RVA      Ord. Hint Name
    -------- ---- ---- ----
    00002A0C    8 0000 __tpdsc__ FooA
    00002AD8   10 0001 __linkproc__ FooA::Finalize
    00002AC8    9 0002 __linkproc__ FooA::Initialize
    00002E4C   12 0003 __linkproc__ PackageA::Finalize
    00002E3C   11 0004 __linkproc__ PackageA::Initialize
    00006510   14 0007 FooA::
    00002860    5 0008 FooA::FooA(FooA&)
    000027E4    4 0009 FooA::FooA()
    00002770    3 000A __fastcall FooA::~FooA()
    000028DC    6 000B __fastcall FooA::Method1() const
    000028F4    7 000C __fastcall FooA::Method2() const
    00001375    2 000D Finalize
    00001368    1 000E Initialize
    0000610C   13 000F ___CPPdebugHook

因此,该类肯定似乎已导出并可用于链接。我可以看到 ILink32 说它正在寻找和未找到的特定内容的条目。在 BPI 文件上运行 TDump 会显示类似的条目。

其他信息

该类确实是 TObject 的后代,尽管最初在重构为包之前它是一个普通的 C++ 类。 (下面有更多详细信息。无论如何,在尝试用非常像 Delphi 的东西来解决问题时,使用 VCL 样式的类似乎“更安全”。改变它只会改变未解决的外部的顺序,首先找不到 Method1 和 @987654337 @,然后是其他人。)

FooA的声明:

class PACKAGE FooA: public TObject {
public:
   FooA();
   virtual __fastcall ~FooA();
   FooA(const FooA&);
   virtual __fastcall long Method1() const;
   virtual __fastcall long Method2() const;
};

FooB:

class FooB: public FooA {
public:
   FooB();
   virtual __fastcall ~FooB();
   ... other methods...
};

所有方法肯定都在 .cpp 文件中实现,所以不是因为它们不存在而找不到它们! .cpp 文件还包含靠近顶部的 #pragma package(smart_init),位于 include 之下。

可能有帮助的问题...

  • 使用 C++ 的包是否可靠,还是只能用于 Delphi 代码?
  • 通过添加对它的 BPI 的引用来链接到第一个包是否正确 - 你应该这样做吗?我可以使用 LIB,但它似乎使第二个包更大,我怀疑它在第一个包的内容中静态链接。
  • 我们能否仅在TObject 派生类上使用PACKAGE 指令?在标准 C++ 类上使用它没有编译器警告。
  • 将代码拆分成包是实现代码隔离和通过定义的层/接口进行通信的最佳方式吗?我一直在研究这条路径,因为它似乎是 C++Builder / Delphi 方式,如果它有效,它看起来很有吸引力。但是有更好的选择吗?
  • 我对使用包非常陌生,之前只通过使用组件了解它们。任何一般性的建议都会很棒!

我们使用的是 C++Builder 2010。我在上面的代码示例中捏造了类和方法名称,但除此之外的细节正是我们所看到的。

【问题讨论】:

    标签: delphi linker package c++builder


    【解决方案1】:

    也许是个愚蠢的问题,但是链接器是否可以在正确的路径中找到您的 BPI/BPL 文件? 我曾经在 BCB5 中创建了一个使用多个链接包的应用程序,但不记得制作它们是否有什么特别之处。

    【讨论】:

    • 是的,就是这样。有点“哦,当然,”的时刻就在那里!谢谢:)
    【解决方案2】:

    未解决的外部

    在您的情况下,未解析的外部似乎是因为编译器无法找到包数据的路径。您应该知道是否:

    • 该路径存在于编译器搜索路径列表中。
    • 包存在于默认包目录中。

    如果其中之一为真,则路径不是问题。然而,Riho 也提到这是问题的最可能原因。 Embarcadero documentation wiki 声明了以下关于 unresolved external 错误:

    在给定模块中引用了命名符号,但未在链接中包含的目标文件和库集中的任何位置定义。检查以确保符号拼写正确。

    如果发生以下任何情况,您通常会从 C 或 C++ 符号的链接器中看到此错误:

    • 您没有正确匹配不同源文件中__pascal__cdecl 类型的符号声明。
    • 您省略了程序所需的目标文件的名称。您需要手动将所有必需的包添加到 Requires 列表中。
    • 您没有在仿真库中进行链接。

    如果您将 C++ 代码与 C 模块链接,您可能忘记将 C 外部声明包装在 extern “C” 中。

    两个符号之间也可能大小写不匹配。

    来源:Unresolved external 'symbol' referenced from 'module'

    从 - 尽管类名改变 - 看来,这不是拼写错误的情况。您还声明您已将包添加到需求列表中,因此我们也排除了这一点。由于您没有链接到 C 模块,我们也可以省略该部分。所以它指向目录的问题。

    关于其他问题

    您的问题都很有趣,其中许多问题都是我自己在开始为 C++ Builder 开发包和组件时一直在寻找答案的问题。

    使用 C++ 的包是否可靠?

    包是用于 C++ Builder 的一个很好的解决方案,C++ Builder 都是为支持包和 Pascal 编写的 VCL 框架而构建的。这意味着 C++ Builder 中的某些实现与其他编译器不同。这是保持语言与其 Delphi 兄弟兼容的必要条件。由于这个原因,您可以像使用 Delphi 一样轻松几乎在 C++ Builder 中使用包。

    通过添加对其 BPI 的引用来链接到第一个包是否正确?

    从这里问题的第二部分开始,使用 lib 文件会使你的包更大,因为它使用静态链接 - 所以你的猜测是正确的。现在回到问题的第一部分,通过添加对包的 BPI 的引用来链接到包就可以了。但是您确实需要确保路径变量已按照Riho 在他的回答中的建议正确设置。

    就我个人而言,我总是确保将我的包放到你的用户文件夹中的正确目录中,这个位置取决于你的 Delphi 版本和操作系统版本。据我记得它在 Document and Settings\all users\shared documents\Rad studio(version number)\Packages 下,但我可能会弄错。

    我们能否仅在TObject 派生类上使用PACKAGE 指令?

    PACKAGE 宏被解析为__declspec(package),您可以将其与__declspec(dllexport) 进行比较。它们之间的区别在于 package 在包中声明时使用,而 dllexport 在 DLL 中声明时使用。在官方 embarcadero 论坛上有一个名为 __declspec(package) vs __declspec(dllexport) 的主题。原帖的作者也问了你关于这个的确切问题,但不幸的是,这部分问题没有得到回答。

    但是我有一个理论,我必须强调它只不过是一个理论。 Remy Lebeau 作为对论坛帖子中问题的回应写道:

    __declspec(dllexport) 可用于普通函数、数据变量和 非 VCL 类,并且可以在普通 DLL 中使用。 __declspec(package) 是 用于 VCL 组件,只能与包一起使用。

    因此,从阅读他的回复来看,在我看来,包只是在导出类,就像 dllexport 一样。而且,据我所知,由于 dllexport 只能在普通 DLL 中使用,因此您必须使用该包从包中导出(甚至)非 VCL 类。

    所有这一切的有趣之处在于,就我的记忆而言,一个包本质上是一个 DLL,但我必须承认我找不到或不记得该信息的来源,所以请谨慎对待。

    将代码拆分成包是实现代码隔离目标的最佳方式吗?

    在为 VCL 创建可重用组件时,包有一些非常突出的优势。显然,使用包限制了用户使用 C++Builder 或 Delphi,但对于编写以利用 VCL 框架的组件来说,这是一个很好的选择。正确编写的包可以减轻组件的重用性,我相信这是为 VCL 分发组件的首选方法。

    但是,如果您的代码没有以任何方式利用 VCL 框架,我会考虑使用一个普通的库,无论是静态的还是动态的,只是为了创建一个对交叉编译器更友好的方法。

    是否有更好的方法来隔离您的代码,实际上取决于您正在从事的项目。我喜欢保留通过在包中使用 VCL 类进行通信的代码,但不需要在常规库中使用任何 VCL 类的代码。请记住,尽管您可以轻松地在 DLL 中使用 VCL 类,但是如果您选择使用 VCL 字符串类作为参数或返回值导出函数,则需要处理特殊情况。

    有什么一般性的建议吗?

    我自己并不是最有经验的软件包开发人员,但我发现禁用运行时链接通常可以解决我的很多问题,虽然为自己的代码解决任何问题有些微不足道,但你经常会遇到无法处理此问题的第 3 方组件。话虽如此,我不喜欢按照这种情况下的要求将我的包与我的应用程序一起分发。但老实说,这是一个品味问题。

    当我开始创建组件和包时,我个人发现很难找到许多问题的正确答案。官方的帮助文件不是关于此事的最丰富的信息,但通过查看 VCL 源代码,通常会为您的问题提供最佳答案。除了有一些其他网站可以提供帮助之外,许多网站虽然都针对 Delphi,但您必须习惯这一点。

    Delphi Wikia 有一些关于创建组件的好文章,特别是 Creating ComponentsCreating Packages 还有 BCB Journal 这是少数 C++ Builder 特定站点之一,它有一些很好的文章和一个可接受的论坛。 Delphi pages at About.com 也是一个很好的信息来源,我在那里找到了很多很好的提示和很高兴知道,特别是:Creating Custom Delphi Components - Inside and Out

    【讨论】:

    • 这是一个非常有用的答案——感谢您写了这么久!
    【解决方案3】:

    对我来说,cpp 文件中的 #pragma package(smart_init,weak) 解决了这个问题。另见http://flylib.com/books/en/3.264.1.27/1/ cpp->obj 文件被静态链接而不影响其他任何东西。

    【讨论】: