C is a very widely used language, and this has made theworldwide code library for C huge. The code library for Delphi is comparablysmall, 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 allowsyou to link compiled C object files. But there is this problem with"unsatisfied externals".
C is a simple but powerful language, that gets most ofits functionality from its runtime library. Almost all non-trivial C code needssome of the functions in this library. But Delphi's runtime doesn't containthese functions. So simply linking the C object file will make the linkercomplain about "unsatisfied external declarations". Luckily, Caccepts any implementation of such a function, no matter in which code moduleit is defined. If the linker can find a function with the desired name, it canbe used. You can use this to provide missing parts of the runtime yourself, inyour Delphi code.
In this article I will demonstrate how to compile andlink an object file into a Delphi unit, and provide the missing parts of the Cruntime that it needs. For this, I will use the well known public domainregular expression search code that Henry Spencer of the University of Torontowrote. I only slightly modified it to make it compile with C++Builder. Regularexpressions are explained in short in the Delphi help files, and are a way ofdefining nifty search patterns.
Object files
C normally generates object files, that are to be linkedto an executable. On 32 bit Windows, these usually have the file extension".obj". But these come in different, incompatible formats.Microsoft's C++ compiler, and some other compatible compilers, generate objectfiles in a slightly modifiedCOFF format. These can't be used in Delphi. DelphirequiresOMF formatted object files. There is no practicable way of convertingnormal COFF object files to OMF, so you will need the source, and a compilerthat generates OMF files.
Note that theCOFF2OMF utility which comes with many versions of C++Builder is no help at all forthis problem. It is only meant to convert import libraries from one format tothe other. Import library files only contain information about the exportedfunctions of a DLL, and can be generated from the DLL directly, usingIMPLIB or a similar utility. They contain a very limited subset of what real C orC++ library files contain.COFF2OMF will not convert C or C++object or real library files (see note below) in the COFF format. You reallyneed source code and a C++Builder compiler to produce OMF object files usablewith Delphi.
UPDATE: Thaddy de Koning told me that theCOFF2OMF converter from DigitalMarscan do a complete conversion. I didn't try it, but he said it is worth itsmoney.
UPDATE 2: Agner Fog, well known optimization guru, pointed me to hisObjConvtool, wich willconvert several types of object files to several others. We are also working onmaking C++-generated OMF and non-OMF generated object files usable in Delphi.
Borland/CodeGear/Embarcadero's C++ Builder does generatesuch OMF object files. But not each Delphi user has C++Builder as well.Luckily, Embarcadero still makes the command line compiler that came withBorland C++ Builder 5 freely available. It can be downloaded from the lastentry in the list on this page,if you provide some information. If you don't have it yet, get it now.
There is another limitation to what kind of files you canuse. You can only use object files that are compiled as C files, not C++ files.For some reason, the Delphi linker has problems with object files that containC++. This means that your source files must have the extension ".c"and not ".cpp". But since you can't use C++ classes directly anyway,that is not a severe limitation.
One note: C often uses library (".lib") files as well. Thesesimply contain multiple object files, and some C compilers come with alibrarian program to extract, insert, replace or simply list object files inthem. In Delphi, you can't link .lib files directly. But you can use theTDUMP utility that comes with Delphi and C++Builder to see what is stored in them.The free C++ compiler comes with the TLIBlibrarian to get at the singleobject files.
The code
I will not discuss the mechanism or use of regularexpressions here. There is enough material available in books and on theInternet. But to exploit them with this code, you first pass a regularexpression pattern to a kind of very simple compiler, that turns the textualrepresentation into a version that can easily be interpreted by the searchcode. The compilation is done by the functionregcompile().To search a string for a regular expression pattern, you pass the compiledpattern and the string to the regexec()function. It will return information about if, and where in the string, itfound text matching the pattern.
The complete implementation code for the regularexpression search is rather complicated and long, so I will not show that. Butthe header file is of course important for the Delphi code using the objectfile. Here it is.
/***************************************************************************/
/* */
/* regexp.h */
/* */
/* Copyright (c)1986 by Univerisity of Toronto */
/* */
/* This publicdomain file was originally written by Henry Spencer for the */
/* University ofToronto and was modified and reformatted by Rudy Velthuis */
/* for use withBorland C++ Builder 5. */
/* */
/***************************************************************************/
#ifndef REGEXP_H
#define REGEXP_H
#define RE_OK 0
#defineRE_NOTFOUND 1
#defineRE_INVALIDPARAMETER 2
#defineRE_EXPRESSIONTOOBIG 3
#defineRE_OUTOFMEMORY 4
#defineRE_TOOMANYSUBEXPS 5
#defineRE_UNMATCHEDPARENS 6
#defineRE_INVALIDREPEAT 7
#defineRE_NESTEDREPEAT 8
#defineRE_INVALIDRANGE 9
#defineRE_UNMATCHEDBRACKET 10
#define RE_TRAILINGBACKSLASH 11
#defineRE_INTERNAL 20
#defineRE_NOPROG 30
#defineRE_NOSTRING 31
#defineRE_NOMAGIC 32
#defineRE_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 secondbyte.
*/
#define MAGIC 0234
#pragma pack(push, 1)
typedef struct regexp
{
char *startp[NSUBEXP];
char*endp[NSUBEXP];
charregstart; /* Internal use only. */
charreganch; /* Internal use only. */
char*regmust; /* Internal use only. */
intregmlen; /* Internal use only. */
charprogram[1]; /* Internal use only. */
} regexp;
#ifdef __cplusplus
extern"C" {
#endif
extern intregerror;
extern regexp*regcomp(char *exp);
extern int regexec(register regexp* prog,registerchar
*string);
extern intreggeterror(void);
extern voidregseterror(int err);
extern voidregdump(regexp *exp);
#ifdef __cplusplus
}
#endif
#pragma pack(pop)
#endif// REGEXP_H
The header above defines a few constant values, astructure to pass information between the regular expression code and thecaller, and also between the different functions of the code, and the functionsthat the user can call.
The #definevalues that start withRE_ areconstants that are returned from the functions to indicate success or an error.NSUBEXP is the number ofsubexpressions a regular expression may have in this implementation. The numbercalled MAGIC is a value that must bepresent in each compiled regular expression. If it is missing, the structureobviously doesn't contain a valid compiled regular expression. Note that0234 is not adecimal value. The leading zero tells the C compiler that this is an octalvalue. Like hexadecimal uses 16 as number base, and decimal uses10, octal uses8. Thedecimal 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'spacked record.
Compiling the code
If you have C++ Builder, or BDS2006, it is a littleeasier to compile the code. You create a new project, and add the file"regexp.c" to it via the menu selections "Project","Add to project", and compile the project. As a result of this, thedirectory will contain a file "regexp.obj"
If you have the command line compiler, and that is set upcorrectly, you open a command prompt, go to the directory that contains thefile "regexp.c" and enter:
bcc32 -c regexp.c
Perhaps you'll get a warning about an unused variable, orabout conversions losing significant digits, but you can ignore themin thiscase, since you didn't write the code anyway. I am using this code myselffor years already, without any problems. After compilation, you'll find theobject file "regexp.obj" in the same directory as the source file.
To import the object file in Delphi, you should now copythe object file to the directory with your Delphi source.
Importing the object file
To use the code in the object file, you'll have to writesome declarations. The Delphi linker doesn't know anything about the parametersof the functions, about theregexptype in the header, and about the values that were defined in the file"regexp.h". It doesn't know what calling convention was used, either.To do this, you write an import unit.
Here is the interfacepart of the Delphi unit that is used to import the functions and values fromthe C object file into Delphi:
unitRegExpObj;
interface
const
NSUBEXP = 10;
// The first byte of the regexp internal"program" is actually this magic
// number; the start node begins in thesecond byte.
MAGIC = 156;
type
PRegExp = ^_RegExp;
_RegExp =packed record
StartP:array[0..NSUBEXP- 1] of PChar;
EndP:array[0..NSUBEXP- 1] of PChar;
RegStart: Char; //Internal use only.
RegAnch: Char; //Internal use only.
RegMust: PChar; //Internal use only.
RegMLen: Integer; //Internal use only.
Prog:array[0..0]of Char; // Internal use only.
end;
function_regcomp(exp: PChar): PRegExp;cdecl;
function_regexec(prog: PRegExp; str: PChar): LongBool;cdecl;
function_reggeterror: Integer;cdecl;
procedure _regseterror(Err:Integer);cdecl;
You'll notice that all the functions got an underscore infront of them. This is because, for historic reasons, most C compilers stillgenerate C functions with names that start with an underscore. To import them,you'll have to use the "underscored" names. You could tell theC++Builder compiler to omit the underscores, but I normally don't do that. Theunderscores clearly show that we are using C functions. These must be declaredwith the C calling convention, which is calledcdeclin Delphi parlance. Forgetting this can produce bugs that are very hard totrace.
The original code of Henry Spencer didn't have thereggeterror() andregseterror()functions. I had to introduce them,because you can't use variables in theobject files from the Delphi side directly, and the code requires access toreset the error value to 0, and to get the error value. But you can use Delphivariables from the C object file. Sometimes object files even require externalvariables to be present. If they don't exist, you can declare them somewhere inyour Delphi code.
Ideally, the implementationpart of the unit would look like this:
implementation
uses
SysUtils;
{$LINK 'regexp.obj'}
function_regcomp(exp: PChar): PRegExp;cdecl; external;
function_regexec(prog: PRegExp; str: PChar): LongBool;cdecl;
external;
function_reggeterror: Integer;cdecl; external;
procedure_regseterror(Err: Integer);cdecl; external;
end.
But if you compile that, the Delphi linker will complainabout unsatisfied externals. The Delphi unit will have to provide them. Mostruntime functions are simple, and can easily be coded in Delphi. Only functionsthat take a variable number of arguments, like printf()orscanf(), are impossible to dowithout resorting to assembler. Perhaps, if you could find the code ofprintf() orscanf()in the C++ libraries, you could extract the object file and link that file inas well. I have never tried this.
The regular expression code needs the C library functionsmalloc()to allocate memory,strlen() to calculate the length of a string,strchr() to find a single character in astring,strncmp() to compare twostrings, and strcspn() to find thefirst character from one string in another string.
The first four functions are simple, and can be coded inone line of Delphi code, since Delphi has similar functions as well. But forstrcspn()there is no equivalent function inthe Delphi runtime library, so it must be coded by hand. Fortunately, I had(admittedly, rather ugly) C code for such a function, and I only had totranslate that to Delphi. Otherwise I'd have had to read the specificationsreally carefully, and try to implement it myself.
The missing part of the implementation section of theunit looks like this:
// since this unitprovides the code for _malloc, it can use FreeMem to free
// the PRegExp itgets. But normally, a _regfree() would be nice.
function_malloc(Size: Cardinal): Pointer;cdecl;
begin
GetMem(Result, Size);
end;
function _strlen(const Str: PChar): Cardinal;cdecl;
begin
Result := StrLen(Str);
end;
function_strcspn(s1, s2: PChar): Cardinal;cdecl;
var
SrchS2: PChar;
begin
Result := 0;
while S1^ <>#0 do
begin
SrchS2 := S2;
whileSrchS2^ <> #0do
begin
if S1^= SrchS2^ then
Exit;
Inc(SrchS2);
end;
Inc(S1);
Inc(Result);
end;
end;
function _strchr(const S: PChar; C: Integer): PChar;cdecl;
begin
Result := StrScan(S, Chr(C));
end;
function_strncmp(S1, S2: PChar; MaxLen: Cardinal): Integer;
cdecl;
begin
Result := StrLComp(S1, S2, MaxLen);
end;
As you can see, these functions must also be declaredcdecl and have a leading underscore. Thefunction names are also case sensitive, so their correct spelling is important.
In my project, I don't use this code directly. The_RegExpstructure contains information thatshould not be changed from outside, and is a bit awkward to use. So I wrappedit up in a few simple functions, and provided aRegFreefunction as well, which simply calls FreeMem,since the_malloc() I provided uses GetMem. Ideally, the regular expression codeshould have provided aregfree()function.
The entire C source code, the code for the import unitand the wrapper unit, as well as avery simple grep program can be foundon my Downloadspage.
Using msvcrt.dll
Instead of writing all these functions yourself, youcould also use functions from the Microsoft Visual C++ runtime library. This isa DLL which Windows also uses, and that is why it should be present on allversions of Windows.
FWIW, this is not my idea, I got it as a suggestion fromRobKennedyin the former Borland newsgroups. It seems the JEDI project also uses this technique insome of their sources.
Using msvcrt.dll,instead of the code above, you could simply declare most of the routinesexternal. Be sure to use thename clause in theexternaldeclaration, because these routines do not have an underscore in the DLL:
// Note that youdon't want to use the C memory manager,
// so you muststill rewrite routines like _malloc() in Delphi.
function_malloc(Size: Cardinal): Pointer;cdecl;
begin
GetMem(Result, Size);
end;
// The rest can beimported from msvcrt.dll directly.
function _strlen(const Str: PChar): Cardinal;cdecl;
external'msvcrt.dll'name 'strlen';
function_strcspn(s1, s2: PChar): Cardinal;cdecl;
external'msvcrt.dll'name 'strcspn';
function _strchr(const S: PChar; C: Integer): PChar;cdecl;
external'msvcrt.dll'name 'strchr';
function_strncmp(S1, S2: PChar; MaxLen: Cardinal): Integer;
cdecl;external'msvcrt.dll' name'strncmp';
This will even work for complicated routines likesprintf() orscanf().Where C requires a file handle, you simply declare a pointer. The effect is thesame. Examples:
function_sprintf(S: PChar;const Format: PChar):Integer;
cdecl;varargs;external'msvcrt.dll'name 'sprintf';
function_fscanf(Stream: Pointer;const Format:PChar): Integer;
cdecl;varargs;external'msvcrt.dll'name 'fscanf';
I put a slightly tested version of an interface unit formsvcrt.dllon my Downloads page. I willchange this page as soon as it is well tested.
Problems
In the meantime, I have encountered a few problems whichmight be interesting for the reader. For the conversion, I wrote a simple testprogram in C, which uses many of the routines imported frommsvcrt.dll. But it turned out that some of theroutines are not routines at all. They are implemented asmacros, whichdirectly access structures, and these don't always exist or are accessible inthis mix of BCB C, Delphi and Microsoft C.
getchar() and putchar()
For instance, take the getchar()routine. Instdio.h, this is declaredas a macro, which accessesstdin->level,andstdin is again a macro for&_streams[0]. If thislevel variable is positive, the routine uses acharacter from the buffer, otherwise it will use_fgetc()(IOW, __fgetc() on the Delphi side).So no matter how you declare your own routine, it will simply not get called.
This meant I had to declare __streams,and initialize thelevel fields tosomething negative. The problem is that themsvcrt.dllroutines will have their own versions of similar structs (there is no guaranteethat the FILE struct is the same,there), and these do not set or read from the BCB variable_streams. So I wrote my own Delphi version of__fgetc() which checks if thestream parameter passed is the same as@__streams[0], indicating it is called withstdin, the standard input stream. If it is,this means it is called as_fgetch(stdin); which is whatthegetchar() macro amounts to. Ifthis is the case, it calls Delphi'sRead,otherwise it uses the _fgetc()routine inmsvcrt.dll.
I hope that this will do, but I'd like to know it ifit doesn't. Pleaseemail meabout any problems you encounter.
FWIW, I am aware of the fact that one of the routines(fwrite(), I think) stops at anint 3 breakpoint inntdll.DbgBreakPoint, if it is run in the debugger. If you do anF9 or Run, the program will continue.
But the putchar()routine is a macro too, and this could increment thelevel again. So there may be similar problems with thatroutine. I did not encounter any, yet. But the changes I made forgetchar()mean that probably ungetc() might not work properly (AFAICS, thisis a macro too). If necessary, I might have to emulate the entire system inDelphi. Just because C uses a few macros.
This goes to show that it is not always a case of simplyredirecting calls to something likemsvcrt.dllafter all. Macros ruin this idea.
FWIW, macros are evil evil evil.
fgetpos() and fsetpos()
It seems that in msvcrt.dllthese two routines store additional data in theposparameter. In BCB, pos is a simplelong integer. Using_fgetpos() withthe declaration of fpos_t in BCB'sstdio.h caused an access violation. So I wrotemy own versions of these routines, using_fseek()and _ftell().
In theory, fpos_tis an opaque type, and both routines only use pointers to one. But in BCB it isdeclared as along, so it won't belarger than 4 bytes, and that is what is allocated. So ifmsvcrt.dlltries to store more than that intoit, some kind of data will be overwritten, and your program will not workproperly, or cause an access violation. That is the risk of using code that iswritten with a different compiler.
Conclusion
Provided you have a little knowledge of C, and are notafraid to write a replacement for a few missing C runtime library functionsyourself (if you usemsvcrtl.dll, thatnumber will be very limited), linking C object files to a Delphi unit is easy.It allows you to create a program that does not need a DLL, and can be deployedin one piece.
If you need help with using the free C++ Builder commandline compiler (compiler version 5.5), you will find excellent help in theBorland newsgroups or forums, such as:
Or you can use the equivalent NNTP newsgroups and anewsreader. I wish you a nice time experimenting.
Rudy Velthuis
转自:http://rvelthuis.de/articles/articles-cobjs.html