Using C object files in Delphi
Rudy Velthuis
Rudy Velthuis
[SHOWTOGROUPS=4,20]
C is a very widely used language, and this has made the worldwide code library for C huge. The code library for Delphi is comparably small, so it would be nice if we could use parts of that huge library directly, without a translation of the entire code in Delphi. Fortunately, Delphi allows you to link compiled C object files. But there is this problem with "unsatisfied externals".
Being a simple but powerful language, C gets most of its functionality from its runtime library. Almost all non-trivial C code needs some of the functions in this library. But Delphi’s runtime doesn’t contain these functions. So simply linking the C object file will make the linker complain about "unsatisfied external declarations". Luckily, C accepts any implementation of such a function, no matter in which code module it is defined. If the linker can find a function with the desired name, it can be used. You can use this to provide missing parts of the runtime yourself, in your Delphi code.
In this article I will demonstrate how to compile and link an object file into a Delphi unit, and provide the missing parts of the C runtime that it needs. For this, I will use the well known public domain regular expression search code that Henry Spencer of the University of Toronto wrote. I only slightly modified it to make it compile with C++Builder. Regular expressions are explained in short in the Delphi help files, and are a way of defining nifty search patterns.
The same principles that apply to DLLs, as described in my article "DLL dos and don’ts, apply to the object files Delphi can use too. This also means that you can’t use C++-specific types like C++ objects or templates from Delphi. Even though I show that you can link to object files generated from some .cpp files, these should only contain C code. To avoid any mangling problems, you should also declare functions as extern "C", e.g.
If you want to use C++ objects, please read my article about how you can do that. It is not possible by linking to object files containing C++-specific code, though.
Object files
Since this article was originally written, many things have changed with respect to which kind of object files can be used and which kind of object files can be generated by which compiler. There are two Delphi compilers for Windows, and three C++Builder compilers (well, actually four, if you count bcc32x.exe, but that only has a different command line interface and is otherwise the same as bcc32c.exe). And then there are the very popular Microsoft and GNU compilers. I rewrote this section to account for the changes.
C normally generates object files, that are to be linked to an executable. On 32 bit Windows, these usually have the file extension .obj, but sometimes also the extension .o. But these can have different, incompatible formats. Microsoft’s C++ compiler, and some other compatible compilers, generate object files in a slightly modified COFF format (as far as I know, it is called COFF/PE). C++Builder’s Win32 compilers (bcc32 and bcc32c) generate a 32 bit OMF file format, which is also the kind older Delphi compilers need.
Since XE2, the Delphi compiler can also use the 32 bit COFF format object files generated by many other 32 bit C and C++ compilers for Windows.
Tested object files
To find out which kind of object files modern Delphi versions can link to, I made a simple program that generates test .c and .cpp files with different names and differently named functions that all do the same, to be compiled with . For example the file cbc.c (cb for C++Builder, c for C) contains the following function:
Then I wrote a little batch file that compiles these with three different compilers: Embarcadero’s bcc32c.exe or bcc64.exe, the different versions of Microsoft’s cl.exe, and MinGW’s gcc.exe, explicitly setting the kind of file (C or C++) to be compiled and the bitness (32 or 64 bit). The resulting object files names reflect these differences. E.g. the object file generated for gccpp.cpp, compiled with the 64 bit gcc.exe, is gccpp64.o and contains the function int gc_add_cpp(int a, int b). The relevant parts are:
Then I wrote a little test console app in Delphi, that links to all the generated object files and calls each of the generated functions, both compiled for WIN32 and WIN64.
It turned out that Delphi 10.3 Rio could link to and call the functions in all these object files, except the one compiled as C++ file by Embarcadero’s own bcc32c.exe. Even a conversion from OMF32 to COFF32, with the following did not help:
Older Delphi versions
Older Delphi versions can only use OMF files. So if you have COFF files, you will have to use one of the existing converters, or if you have the source code, recompile with C++Builder or the free command line compiler (see below). One good converter is Agner Fog’s Для просмотра ссылки Войдиили Зарегистрируйся converter. I was told that the Для просмотра ссылки Войди или Зарегистрируйся should work too.
Note that the COFF2OMF utility that comes with C++Builder or RAD Studio is only meant to convert import libraries for DLLs from one format to the other. It can not convert full code library files, as it only knows a limited subset of the record types you can find in a full code library file.
If you don’t have a C compiler, you can use any compiler that generates Visual C++ compatible COFF files (see above). For versions older than Delphi XE2, you will however need a compiler that generates OMF object files. There are a number of free compilers from Embarcadero:
C and C++ often use library (.lib) files as well. These simply contain multiple object files, and some C compilers come with a librarian program to extract, insert, replace or simply list object files in them. In Delphi, you can’t link .lib files directly. But you can use the TDUMP utility that comes with Delphi and C++Builder to see what is stored in them. The free C++ compiler and C++Builder come with the TLIB librarian to get at the single object files.
Object file formats for Win64 are different. See the Win64 section below.
The code
I will not discuss the mechanism or use of regular expressions here. There is enough material available in books and on the Internet. But to exploit them with this code, you first pass a regular expression pattern to a kind of very simple compiler, that turns the textual representation into a version that can easily be interpreted by the search code. The compilation is done by the function regcompile(). To search a string for a regular expression pattern, you pass the compiled pattern and the string to the regexec() function. It will return information about if, and where in the string, it found text matching the pattern.
The complete implementation code for the regular expression search is rather complicated and long, so I will not show that. But the header file is of course important for the Delphi code using the object file. Here it is.
Для просмотра ссылки Войдиили Зарегистрируйся
The header above defines a few constant values, a structure to pass information between the regular expression code and the caller, and also between the different functions of the code, and the functions that the user can call.
The #define values that start with RE_ are constants that are returned from the functions to indicate success or an error. NSUBEXP is the number of subexpressions a regular expression may have in this implementation. The number called MAGIC is a value that must be present in each compiled regular expression. If it is missing, the structure obviously doesn’t contain a valid compiled regular expression. Note that 0234 is not a decimal value. The leading zero tells the C compiler that this is an octal value. Like hexadecimal uses 16 as number base, and decimal uses 10, octal uses 8. The decimal value is calculated this way:
The #pragma pack(push, 1) pushes the current alignment state, and sets it to bytewise alignment. #pragma pack(pop) restores the previous state. This is important, because it makes the structure compatible with Delphi’s packed record.
[/SHOWTOGROUPS]
C is a very widely used language, and this has made the worldwide code library for C huge. The code library for Delphi is comparably small, so it would be nice if we could use parts of that huge library directly, without a translation of the entire code in Delphi. Fortunately, Delphi allows you to link compiled C object files. But there is this problem with "unsatisfied externals".
Being a simple but powerful language, C gets most of its functionality from its runtime library. Almost all non-trivial C code needs some of the functions in this library. But Delphi’s runtime doesn’t contain these functions. So simply linking the C object file will make the linker complain about "unsatisfied external declarations". Luckily, C accepts any implementation of such a function, no matter in which code module it is defined. If the linker can find a function with the desired name, it can be used. You can use this to provide missing parts of the runtime yourself, in your Delphi code.
In this article I will demonstrate how to compile and link an object file into a Delphi unit, and provide the missing parts of the C runtime that it needs. For this, I will use the well known public domain regular expression search code that Henry Spencer of the University of Toronto wrote. I only slightly modified it to make it compile with C++Builder. Regular expressions are explained in short in the Delphi help files, and are a way of defining nifty search patterns.
The same principles that apply to DLLs, as described in my article "DLL dos and don’ts, apply to the object files Delphi can use too. This also means that you can’t use C++-specific types like C++ objects or templates from Delphi. Even though I show that you can link to object files generated from some .cpp files, these should only contain C code. To avoid any mangling problems, you should also declare functions as extern "C", e.g.
Код:
extern "C" int vc_add_cpp(int a, int b);
If you want to use C++ objects, please read my article about how you can do that. It is not possible by linking to object files containing C++-specific code, though.
Object files
Since this article was originally written, many things have changed with respect to which kind of object files can be used and which kind of object files can be generated by which compiler. There are two Delphi compilers for Windows, and three C++Builder compilers (well, actually four, if you count bcc32x.exe, but that only has a different command line interface and is otherwise the same as bcc32c.exe). And then there are the very popular Microsoft and GNU compilers. I rewrote this section to account for the changes.
C normally generates object files, that are to be linked to an executable. On 32 bit Windows, these usually have the file extension .obj, but sometimes also the extension .o. But these can have different, incompatible formats. Microsoft’s C++ compiler, and some other compatible compilers, generate object files in a slightly modified COFF format (as far as I know, it is called COFF/PE). C++Builder’s Win32 compilers (bcc32 and bcc32c) generate a 32 bit OMF file format, which is also the kind older Delphi compilers need.
Since XE2, the Delphi compiler can also use the 32 bit COFF format object files generated by many other 32 bit C and C++ compilers for Windows.
Tested object files
To find out which kind of object files modern Delphi versions can link to, I made a simple program that generates test .c and .cpp files with different names and differently named functions that all do the same, to be compiled with . For example the file cbc.c (cb for C++Builder, c for C) contains the following function:
Код:
#include "cbc.h"
int cb_add_c(int a, int b)
{
return a + b;
}
Then I wrote a little batch file that compiles these with three different compilers: Embarcadero’s bcc32c.exe or bcc64.exe, the different versions of Microsoft’s cl.exe, and MinGW’s gcc.exe, explicitly setting the kind of file (C or C++) to be compiled and the bitness (32 or 64 bit). The resulting object files names reflect these differences. E.g. the object file generated for gccpp.cpp, compiled with the 64 bit gcc.exe, is gccpp64.o and contains the function int gc_add_cpp(int a, int b). The relevant parts are:
Код:
bcc32c -c cbc.c -o cbc32.obj
bcc32c -c cbcpp.cpp -o cbcpp32.obj
bcc64 -c cbc.c -o cbc64.o
bcc64 -c cbcpp.cpp -o cbcpp64.o
set clpath="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.12.25827\bin\Hostx64\"
%clpath%"x86\cl.exe" /Fovcc32.obj /Tc vcc.c /c
%clpath%"x86\cl.exe" /Fovccpp32.obj /Tp vccpp.cpp /c
%clpath%"x64\cl.exe" /Fovcc64.obj /Tc vcc.c /c
%clpath%"x64\cl.exe" /Fovccpp64.obj /Tp vccpp.cpp /c
gcc -m32 -c gcc.c -o gcc32.o
gcc -m32 -c gccpp.cpp -o gccpp32.o
gcc -m64 -c gcc.c -o gcc64.o
gcc -m64 -c gccpp.cpp -o gccpp64.o
Then I wrote a little test console app in Delphi, that links to all the generated object files and calls each of the generated functions, both compiled for WIN32 and WIN64.
It turned out that Delphi 10.3 Rio could link to and call the functions in all these object files, except the one compiled as C++ file by Embarcadero’s own bcc32c.exe. Even a conversion from OMF32 to COFF32, with the following did not help:
Код:
objconv -fpe cbcpp32.obj cbcpp32-coff.obj
Older Delphi versions
Older Delphi versions can only use OMF files. So if you have COFF files, you will have to use one of the existing converters, or if you have the source code, recompile with C++Builder or the free command line compiler (see below). One good converter is Agner Fog’s Для просмотра ссылки Войди
Note that the COFF2OMF utility that comes with C++Builder or RAD Studio is only meant to convert import libraries for DLLs from one format to the other. It can not convert full code library files, as it only knows a limited subset of the record types you can find in a full code library file.
If you don’t have a C compiler, you can use any compiler that generates Visual C++ compatible COFF files (see above). For versions older than Delphi XE2, you will however need a compiler that generates OMF object files. There are a number of free compilers from Embarcadero:
- Since 13 July 2016 there is a new free C++ command line compiler for 32 bit Windows from Embarcadero. It can be found on the Для просмотра ссылки Войди
или Зарегистрируйся page. It is the Clang-based C++11 supporting C++ compiler that comes with C++Builder 10.1 Berlin. It generates OMF object files. It comes with a number of Windows headers and command line tools. It is described in Для просмотра ссылки Войдиили Зарегистрируйся by the new product manager, David Millington. - Since 1 May 2018, there is yet another new free C++ command line compiler from Embarcadero, the Для просмотра ссылки Войди
или Зарегистрируйся. It features the latest bcc32c compiler as well as a slightly different bcc32x compiler, which has the Clang and not the Borland command line interface. It comes with the Dinkumware STL, the RTL and various command line tools. - Since 18 July 2018, you can even download the free and fully equipped (but limited by license) Для просмотра ссылки Войди
или Зарегистрируйся. Note that it can’t be installed on the same machine (or virtual machine) as Delphi Community Edition or one of the paid versions of Delphi, C++Builder or RAD Studio.
C and C++ often use library (.lib) files as well. These simply contain multiple object files, and some C compilers come with a librarian program to extract, insert, replace or simply list object files in them. In Delphi, you can’t link .lib files directly. But you can use the TDUMP utility that comes with Delphi and C++Builder to see what is stored in them. The free C++ compiler and C++Builder come with the TLIB librarian to get at the single object files.
Object file formats for Win64 are different. See the Win64 section below.
The code
I will not discuss the mechanism or use of regular expressions here. There is enough material available in books and on the Internet. But to exploit them with this code, you first pass a regular expression pattern to a kind of very simple compiler, that turns the textual representation into a version that can easily be interpreted by the search code. The compilation is done by the function regcompile(). To search a string for a regular expression pattern, you pass the compiled pattern and the string to the regexec() function. It will return information about if, and where in the string, it found text matching the pattern.
The complete implementation code for the regular expression search is rather complicated and long, so I will not show that. But the header file is of course important for the Delphi code using the object file. Here it is.
Для просмотра ссылки Войди
/***************************************************************************/ /* */ /* regexp.h */ /* */ /* Copyright (c) 1986 by Univerisity of Toronto */ /* */ /* This public domain file was originally written by Henry Spencer for the */ /* University of Toronto and was modified and reformatted by Rudy Velthuis */ /* for use with Borland C++Builder 5. */ /* */ /***************************************************************************/ #ifndef REGEXP_H #define REGEXP_H #define RE_OK 0 #define RE_NOTFOUND 1 #define RE_INVALIDPARAMETER 2 #define RE_EXPRESSIONTOOBIG 3 #define RE_OUTOFMEMORY 4 #define RE_TOOMANYSUBEXPS 5 #define RE_UNMATCHEDPARENS 6 #define RE_INVALIDREPEAT 7 #define RE_NESTEDREPEAT 8 #define RE_INVALIDRANGE 9 #define RE_UNMATCHEDBRACKET 10 #define RE_TRAILINGBACKSLASH 11 #define RE_INTERNAL 20 #define RE_NOPROG 30 #define RE_NOSTRING 31 #define RE_NOMAGIC 32 #define RE_NOMATCH 33 #define RE_NOEND 34 #define RE_INVALIDHANDLE 99 #define NSUBEXP 10 /* * The first byte of the regexp internal "program" is actually this magic * number; the start node begins in the second byte. */ #define MAGIC 0234 #pragma pack(push, 1) typedef struct regexp { char *startp[NSUBEXP]; char *endp[NSUBEXP]; char regstart; /* Internal use only. */ char reganch; /* Internal use only. */ char *regmust; /* Internal use only. */ int regmlen; /* Internal use only. */ char program[1]; /* Internal use only. */ } regexp; #ifdef __cplusplus extern "C" { #endif extern int regerror; extern regexp *regcomp(char *exp); extern int regexec(register regexp* prog, register char *string); extern int reggeterror(void); extern void regseterror(int err); extern void regdump(regexp *exp); #ifdef __cplusplus } #endif #pragma pack(pop) #endif // REGEXP_H |
The header above defines a few constant values, a structure to pass information between the regular expression code and the caller, and also between the different functions of the code, and the functions that the user can call.
The #define values that start with RE_ are constants that are returned from the functions to indicate success or an error. NSUBEXP is the number of subexpressions a regular expression may have in this implementation. The number called MAGIC is a value that must be present in each compiled regular expression. If it is missing, the structure obviously doesn’t contain a valid compiled regular expression. Note that 0234 is not a decimal value. The leading zero tells the C compiler that this is an octal value. Like hexadecimal uses 16 as number base, and decimal uses 10, octal uses 8. The decimal value is calculated this way:
Код:
0234(oct) = 2 * 82 + 3 * 81 + 4 * 80 = 128 + 24 + 4 = 156(dec)
The #pragma pack(push, 1) pushes the current alignment state, and sets it to bytewise alignment. #pragma pack(pop) restores the previous state. This is important, because it makes the structure compatible with Delphi’s packed record.
[/SHOWTOGROUPS]