当使用C++ Builder包时,如何解决未解析的外部引用?

9
我正在尝试重新配置我的应用程序,以大量使用软件包。我和另一个运行类似实验的开发人员在使用几个不同的软件包时遇到了一些麻烦。我们可能都做错了什么,但是谁知道呢:)
情况是这样的:
- 第一个软件包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也显示了类似的条目。

其他信息

虽然在重构成包之前它是一个普通的C ++类,但这个类确实是从TObject继承而来。(更多细节见下文) 无论如何,当尝试解决这种非常Delphi风格的问题时,使用VCL-style类似乎更加“安全”。更改这一点只会将未解决的外部引用顺序更改为首先不找到 Method1 Method2 ,然后才是其他部分。

FooA的声明:

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

今日免费次数已满, 请开通会员/明日再来
class FooB: public FooA {
public:
   FooB();
   virtual __fastcall ~FooB();
   ... other methods...
};

所有方法都在.cpp文件中实现,因此它们不是因为不存在而无法找到!.cpp文件还包含在包含项下方靠近顶部的#pragma package(smart_init)

可能有用的问题...

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

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

3个回答

9

未解决的外部问题

在您的情况中,未解决的外部问题似乎是因为编译器无法找到软件包数据的路径。您应该找出以下信息:

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

如果其中一个为真,则路径不是问题所在。但是正如Riho所提到的那样,这很可能是问题的最常见原因。Embarcadero文档wiki关于“未解决的外部”错误的说明如下:

给定模块中引用了指定符号,但在链接的对象文件和库集合中未定义该符号。请检查符号拼写是否正确。 如果出现以下任何情况,您通常会从C或C++符号链接器看到此错误: - 您未正确匹配不同源文件中符号的`__pascal`和`__cdecl`声明。 - 您已省略程序所需的某个目标文件名称。您需要手动将所有必需的软件包添加到Requires列表中。 - 您未链接仿真库。
如果您正在链接C++代码与C模块,则可能忘记在extern“C”中包装C外部声明。 您还可能存在两个符号之间的大小写不匹配问题。 来源:模块中引用了未解析的外部'symbol'。

由于类名已更改,因此不是拼写错误的情况。您还声明已将包添加到要求列表中,因此我们也排除了这个问题。由于您没有链接到C模块,因此我们可以省略该部分。因此,它指向目录存在问题。

关于其他问题

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

C++使用包是否可靠?

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

通过将引用添加到其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,在DLL中声明时使用dllexport。在官方Embarcadero论坛上有一个关于此主题的帖子,标题为 __declspec(package) vs __declspec(dllexport)。原帖的作者也问了您这个问题,但不幸的是,该问题的那部分没有得到回答。
然而,我有一个理论,必须强调它只是一个理论。Remy Lebeau在论坛帖子的回复中写道:

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

从他的回答中可以看出,这个包只是像dllexport一样导出类。由于根据他的回答,dllexport仅用于普通DLL,因此您必须使用该包(甚至)从包中导出非VCL类。
有趣的是,所有这些都是关于将代码拆分成包的最佳方法来实现隔离代码目标。
在创建可重用组件时,包具有一些非常突出的优点。显然,使用包限制了用户只能使用C ++ Builder或Delphi,但对于编写以利用VCL框架的组件来说,这是一个绝佳选择。正确编写的包可以简化组件的可重用性,并且我认为这是分发VCL组件的首选方法。
但是,如果您的代码在任何方面都不利用VCL框架,则应考虑使用普通库,无论是静态还是动态,只是为了创建更跨编译器友好的方法。
无论是否有更好的方法来隔离你的代码,这实际上取决于你正在开发的项目。我喜欢将通过使用VCL类进行通信的代码放在包中,但是不需要使用任何VCL类的代码则放在常规库中。但请记住,您可以轻松地在DLL中使用VCL类,但如果选择导出带有VCL字符串类作为参数或返回值的函数,则需要处理特殊情况。

有什么一般性建议吗?

我本人并不是最有经验的包开发者,但我发现禁用运行时链接通常可以解决我的很多问题。虽然对于自己的代码解决任何问题有点琐碎,但你经常会遇到第三方组件无法处理此问题。话虽如此,我不喜欢像这种情况所要求的与我的应用程序一起分发我的包。但老实说,这是个人口味的问题。
就我个人而言,当我开始创建组件和包时,我发现很难找到一些合适的答案。官方帮助文件在这方面并不是最具信息量的,但查看VCL源代码通常会给您提供最佳答案。此外还有一些其他网站可以提供帮助,许多网站针对的是Delphi,但是你必须习惯这一点。

Delphi Wikia有一些关于创建组件的好文章,特别是Creating ComponentsCreating Packages。还有BCB Journal,这是为数不多的C++ Builder专用网站之一,它有一些优秀的文章和一个可以接受的论坛。About.com上的Delphi页面也是一个很好的信息来源,我在那里找到了很多好的提示和有用的知识,特别是:Creating Custom Delphi Components - Inside and Out


这是一个非常有用的答案 - 感谢您写了这么长的东西! - David

2
也许是一个愚蠢的问题,但你的BPI/BPL文件是否在正确的路径下被链接器找到了? 我曾经在BCB5中创建过一个应用程序,使用了几个链接的包,但我不记得有什么特别的制作方法。

是的,就是这样。刚才有一点“哦,当然”的时刻!谢谢 :) - David

2
对我来说,在cpp文件中使用#pragma package(smart_init,weak)解决了问题。参见http://flylib.com/books/en/3.264.1.27/1/。cpp->obj文件会被静态链接,而不会影响其他内容。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接