3

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.

Community
  • 1
  • 1
Zoë Peterson
  • 13,094
  • 2
  • 44
  • 64

3 Answers3

4

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:

const
  __turboFloat: Longint=0;
  (* We don't actually know the type but it is 4 bytes long and initialised to zero.  This can be determined
     using tdump initcvt.obj.  It doesn't actually matter how we define this since it is ultimately not
     referred to and is stripped from the executable by the linker. *)

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 out wsprintfA 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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I'm adding Lzma support to TurboPower Abbrevia (a Delphi zip/tar/gz library). All of the code that interacts with the Lzma (C) library is Delphi. I'm just trying to make Abbrevia support C++Builder too. – Zoë Peterson Mar 14 '11 at 20:02
  • I'm a little out of my depth. Can a Delphi unit access .c/.h files in a C++Builder project? That is, if I create a duplicate "LzmaDecompress()" function in a .c file that accessed the original project header directly, how would I refer to it in my Delphi unit? – Zoë Peterson Mar 14 '11 at 20:04
  • The only way to statically link C code into Delphi is with $L. – David Heffernan Mar 14 '11 at 20:11
  • @Craig you're doing this over the sf project? is it a open fork? where can one get your contributions? I'm pretty interested in LZMA support and I like TP Abbrevia also, so I think it worths a look :) – jachguate Mar 14 '11 at 21:36
  • @jachguate Yes, the code is all committed to the Abbrevia Subversion repository. I'm hoping to do a release tonight if I can get this straightened out. – Zoë Peterson Mar 14 '11 at 23:34
  • @David I've updated the OP with the download location and the new errors I'm receving. I need 7 functions I haven't implemented in Pascal, including _sprintf and _turboFloat, so I don't think the msvcrt.dll requirement is *too* heavyweight. – Zoë Peterson Mar 14 '11 at 23:38
  • @David, thanks for the updates. AbLzma.pas is the unit that actually includes the $L directives. My comment was referring to the fact that I had to declare it there explicitly, since it isn't provided by crtl.dcu. I have it in the correct unit because I'm getting link errors, and if I comment out the declaration I get DCC errors instead. – Zoë Peterson Mar 15 '11 at 01:00
  • No, I'm still getting the link errors I mentioned in my edit. When I tried switching to user32.dll/wsprintfA, it just changed the error message to refer to that instead of sprintf. Do I need to tell CB to link user32.lib somewhere? – Zoë Peterson Mar 15 '11 at 01:43
  • I don't think wsprintfA will work since it uses stdcall rather than cdecl. Or at least, I don't know enough about varargs to create a proper wrapper function. – Zoë Peterson Mar 15 '11 at 02:08
  • 1
    I think I got it. I was using the "name" directive with the "external" directive to match "_memcmp" to "memcmp". Removing the "name" portion solved the linker errors. Still using msvcrt instead of user32 for sprintf, but that's ok. If it bothers a C++ dev they can figure it out. :) – Zoë Peterson Mar 15 '11 at 02:21
  • As one final followup, Delphi does need the 'name' directives, so they have to be wrapped in {$IFNDEF BCB} blocks. – Zoë Peterson Mar 15 '11 at 12:56
0

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.

David Dean
  • 2,682
  • 23
  • 34
  • I'm not sure. If that was the case then deleting crtl would also work. – David Heffernan Mar 14 '11 at 22:26
  • DavidH is right, that fails with the same "Unresolved external 'Crtl::_memmove' type errors. – Zoë Peterson Mar 14 '11 at 23:27
  • "unresolved external" is actually helpful. I almost mentioned it in my original reply. It looks like the delphi unit is placing the methods in a different namespace. It can be resolved, but it looks like you've already found a workable solution. – David Dean Mar 15 '11 at 04:49
  • If there's a way to resolve it I'd love to hear it. The msvcrt.dll dependency works, but it's a bit clunky. – Zoë Peterson Mar 15 '11 at 12:44
  • @David The thing that I believe you are missing is that the resolving of these names is done by the Delphi compiler and not by the linker. If there was something similar to `extern` in Delphi then that would get the job done. But there isn't. – David Heffernan Mar 15 '11 at 13:05
  • @Craig, I was thinking a #pragma alias – David Dean Mar 16 '11 at 18:11
0

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:

unit midaslib;
interface
implementation
end.

Now you can use crtl.dcu without the internal errors!

Thaddy
  • 1
  • 1
    This question is about using Crtl.dcu with C++Builder. Specifically using a .c file from a .pas file compiled with C++Builder. I already had it working when using just Delphi. – Zoë Peterson Jan 08 '12 at 17:35