如何在 C++Builder 项目的 Delphi 单元中使用 Crtl? (或链接到 C++Builder C 运行时库)
我有一个 Delphi 单元,它使用 {$L xxx}
指令静态链接 C .obj 文件。 C 文件是用 C++Builder 的命令行编译器编译的。为了满足 C 文件的运行时库依赖项(_assert、memmove 等),我包含 Allen Bauer 提到的 crtl
单元 此处。
unit FooWrapper;
interface
implementation
uses
Crtl; // Part of the Delphi RTL
{$L FooLib.obj} // Compiled with "bcc32 -q -c foolib.c"
procedure Foo; cdecl; external;
end.
如果我在 Delphi 项目 (.dproj) 中编译该单元,则一切正常。
如果我在 C++Builder 项目 (.cbproj) 中编译该单元,则会失败并显示以下错误:
[ILINK32 Error] Fatal: Unable to open file 'CRTL.OBJ'
事实上,RAD Studio 安装文件夹中没有 crtl.obj
文件。有 .dcu,但没有 .pas。尝试将 crtdbg
添加到 use 子句(定义 _assert 的 C 标头)中会出现找不到 crtdbg.dcu
的错误。
如果我删除 uses 子句,它会失败,并出现找不到 __assert
和 _memmove
的错误。
那么,在 C++Builder 项目的 Delphi 单元中,如何从 C 运行时库导出函数以便它们可用于链接?
我已经知道 Rudy Velthuis 的文章。如果可能的话,我想避免手动编写 Delphi 包装器,因为我在 Delphi 中不需要它们,并且 C++Builder 必须已经包含必要的函数。
编辑
对于任何想要在家玩的人,可以在 Abbrvia 的 Subversion 存储库中找到该代码,网址为 https://tpabbrevia.svn.sourceforge.net/svnroot/tpabbrevia/trunk。我采纳了 David Heffernan 的建议,添加了一个“AbCrtl.pas”单元,在 C++Builder 中编译时模仿 crtl.dcu。这使得 PPMd 支持正常工作,但是 Lzma 和 WavPack 库都因链接错误而失败:
[ILINK32 Error] Error: Unresolved external '_beginthreadex' referenced from ABLZMA.OBJ
[ILINK32 Error] Error: Unresolved external 'sprintf' referenced from ABWAVPACK.OBJ
[ILINK32 Error] Error: Unresolved external 'strncmp' referenced from ABWAVPACK.OBJ
[ILINK32 Error] Error: Unresolved external '_ftol' referenced from ABWAVPACK.OBJ
AFAICT,所有这些库都已正确声明,并且 _beginthreadex 实际上是在 AbLzma.pas 中声明的,因此纯 Delphi 编译也使用它。
要亲自查看,只需下载主干(或仅“源”和“包”目录),禁用 AbDefine.inc 底部的 {$IFDEF BCB} 块,然后尝试编译 C++Builder “Abbrvia .cbproj”项目。
I have a Delphi unit that is statically linking a C .obj file using the {$L xxx}
directive. The C file is compiled with C++Builder's command line compiler. To satisfy the C file's runtime library dependencies (_assert, memmove, etc), I'm including the crtl
unit Allen Bauer mentioned here.
unit FooWrapper;
interface
implementation
uses
Crtl; // Part of the Delphi RTL
{$L FooLib.obj} // Compiled with "bcc32 -q -c foolib.c"
procedure Foo; cdecl; external;
end.
If I compile that unit in a Delphi project (.dproj) everthing works correctly.
If I compile that unit in a C++Builder project (.cbproj) it fails with the error:
[ILINK32 Error] Fatal: Unable to open file 'CRTL.OBJ'
And indeed, there isn't a crtl.obj
file in the RAD Studio install folder. There is a .dcu, but no .pas. Trying to add crtdbg
to the uses clause (the C header where _assert is defined) gives an error that it can't find crtdbg.dcu
.
If I remove the uses clause, it instead fails with errors that __assert
and _memmove
aren't found.
So, in a Delphi unit in a C++Builder project, how can I export functions from the C runtime library so they're available for linking?
I'm already aware of Rudy Velthuis's article. I'd like to avoid manually writing Delphi wrappers if possible, since I don't need them in Delphi, and C++Builder must already include the necessary functions.
Edit
For anyone who wants to play along at home, the code is available in Abbrevia's Subversion repository at https://tpabbrevia.svn.sourceforge.net/svnroot/tpabbrevia/trunk. I've taken David Heffernan's advice and added a "AbCrtl.pas" unit that mimics crtl.dcu when compiled in C++Builder. That got the PPMd support working, but the Lzma and WavPack libraries both fail with link errors:
[ILINK32 Error] Error: Unresolved external '_beginthreadex' referenced from ABLZMA.OBJ
[ILINK32 Error] Error: Unresolved external 'sprintf' referenced from ABWAVPACK.OBJ
[ILINK32 Error] Error: Unresolved external 'strncmp' referenced from ABWAVPACK.OBJ
[ILINK32 Error] Error: Unresolved external '_ftol' referenced from ABWAVPACK.OBJ
AFAICT, all of them are declared correctly, and the _beginthreadex one is actually declared in AbLzma.pas, so it's used by the pure Delphi compile as well.
To see it yourself, just download the trunk (or just the "source" and "packages" directories), disable the {$IFDEF BCB} block at the bottom of AbDefine.inc, and try to compile the C++Builder "Abbrevia.cbproj" project.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我对此的看法是,您只需要项目的 Delphi 版本中的 Delphi 单元。
在 C++ 构建器版本中,您只需编译并链接foolib.c,就好像它是一个 C 文件一样(确实如此!)。在该程序的 Delphi 版本中,您可以使用 bcc32 创建 .obj,按照所述使用 ctrl 等。
为什么要将它包装在 Delphi 包装器中的 C 库中以便在 C++ 中使用?
编辑 1
您已在评论中添加了说明。
另一个需要考虑的选择是避免 crtl 并实现 FooWrapper 中缺少的函数。我这样做而不是使用 crtl 因为这给了我更多的控制权并且我理解正在调用的内容。例如,我不希望对
printf()
的任何调用泄漏到我的 GUI 应用程序或 DLL 中。如果您只缺少少数功能,这可能是一个有吸引力的选择。通常,获取它们的最巧妙方法是将它们从 msvcrt.dll 链接到其中,msvcrt.dll 是当今的标准系统组件。当然,仅仅为了获取
memset()
、memcpy()
等而链接到 msvcrt.dll 似乎有点重量级。当你编译时,有多少个缺失的函数Delphi 单元没有crtl 吗?
编辑2
我将其添加到答案中以显示一些代码。从我自己的代码库中,我提供了以下内容:
对于 ftol,我链接到 ftol.obj,我认为它是从我使用的 BCC55 编译器中的 lib 文件之一中提取的。
我认为 strncmp 应该是用普通 Pascal 实现的非常常规的方法。
sprintf 总体来说更困难,但您可能会发现它仅用于一些琐碎的事情,例如整数到字符串。在这种情况下,您可以伪造 C 代码来调用专用于此目的的例程并简单地实现它。
老实说,我认为“msvcrt.dll”看起来很有吸引力!
编辑 3
我很快就通话了吗?您可以从几乎所有进程都已加载的 user32.dll 中提取一个完全可用的 sprintf。确保选择
wsprintfA
如果您需要的是 ANSI 版本。编辑4
我注意到
_beginthreadex
。你说这是在不同的 Delphi 单元中定义的。为了让编译器看到它,您需要在 AbCtrl.pas 中重新声明它,并从那里调用 AbLzma.pas 中的真实版本。当您在 Delphi .pas 文件中包含 .obj 时,编译器必须能够从链接到 .obj 的 Delphi 单元内解析 .obj 文件中的所有引用。整个游戏是由编译器而不是链接器处理的。
有时,您会因包含 .obj 文件的顺序而陷入困境,解决方案是使用前向声明,但那是另一个故事了。
My take on this is that you only need the Delphi unit in the Delphi version of the project.
In the C++ builder version you just compile and link foolib.c as if it was a C file (it is!) In the Delphi version of the program you create the .obj with bcc32, use ctrl etc. as described.
Why do you want to wrap it up a C library up in a Delphi wrapper to be consumed in C++?
EDIT 1
You've added clarifications in the comments.
Another option to consider would be to avoid crtl and implement the missing functions in FooWrapper. I do it that way rather than using crtl because that gives me more control and I understand what is being called. For example, I don't want any calls to
printf()
leaking into my GUI app or my DLL.This might be an attractive option if you are only missing a handful of functions. Often the neatest way to get them is to link them in from msvcrt.dll which is a standard system component these days. Of course it seems a bit heavyweight to link in msvcrt.dll just to get at
memset()
,memcpy()
etc.How many missing functions are there when you compile the Delphi unit without crtl?
EDIT 2
I'm adding this to the answer to show some code. From my own code base I offer this:
For
ftol
I link in ftol.obj which I presume I extracted from one of the lib files in the BCC55 compiler that I use.I think
strncmp
should be pretty routine to implement in plain Pascal.sprintf
is more difficult in full generality, but you might find that it is only used for something trivial like integer to string. In which case you could fudge the C code to call a routine dedicated for that and implement it trivially.To be honest with you, I think 'msvcrt.dll' looks pretty attractive!
EDIT 3
Did I speak to soon? You can pull a perfectly serviceable
sprintf
out of user32.dll which almost all processes have loaded anyway. Make sure you pick outwsprintfA
if it's an ANSI version you need.EDIT 4
I notice
_beginthreadex
. You say this is defined in a different Delphi unit. In order to get the compiler to see it you need to redeclare it in AbCtrl.pas and from there call the real version in AbLzma.pas.When you include a .obj in a Delphi .pas file the compiler has to be able to resolve all the references in the .obj file from within the Delphi unit which links to the .obj. This whole game is dealt with by the compiler rather than the linker.
Sometimes you get tangled in knots with the order in which you include the .obj files and the solution is to use forward declarations, but that's another story.
在这种情况下,假设您感兴趣的函数可以直接从 C RTL 中获得,因此使用虚拟(空)obj 文件伪造链接器应该可以工作,因为它将满足链接器寻找 Delphi 告诉它的 obj 文件的要求你需要,但仍然可以在 RTL 中找到这些函数。
In this case, the functions you are interested are assumed to be available directly from the C RTL, so faking out the linker with a dummy (empty) obj file should work as it will satisfy the linker looking for the obj file which Delphi told it you need, but still find the functions in the RTL.
晚了,但更完整:
crtl.dcu 从 D2005 一直到 XE2 都可以正常工作。
对于 D6 和 D7,存在对 midaslib.dcu 的依赖性。
嗯,不是真的,dcu 是用一个脏的使用条款来分发的。
对于 D6 和 D7,您应该创建一个空的 midaslib.pas 代理,例如:
现在您可以使用 crtl.dcu 而不会出现内部错误!
Late, but more complete:
crtl.dcu works without problems from D2005 up until XE2.
For D6 and D7 there is a dependency on midaslib.dcu.
Well, not really, the dcu is distributed with a dirty uses clause.
For D6 and D7 you should create an EMPTY midaslib.pas surrogate, like:
Now you can use crtl.dcu without the internal errors!