Delphi引用C对象文件(转)
源:http://blog.csdn.net/henreash/article/details/7357618
C语言应用非常广泛,并在世界各地拥有大量的代码库.这些代码库与Delphi的可比性较小,因此如果我们无需转换为Delphi代码而可以直接使用这些库的部分代码就完美了.幸运的是,Delphi允许连接到C编译出来的对象文件.但这里有” unsatisfied externals”问题.
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".
C是一个简单但强大的语言,可从其运行时库中获取大量功能.几乎所有C代码都需要这个库的函数支持.但是Delphi的运行时不包括这些函数.因此简单的将C对象文件连接过来连接器会抱怨”unsatisfied external declarations”.,幸运的是C接受这些函数的实现,而不会在意是在哪个代码模块中定义的.如果连接器可以查找到一个具有期望名称的函数,其就可以被调用.可以利用这点在Delphi代码中自己提供一个不完整的运行时.
C is a simple but powerful language, that 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.
本文将阐述将一个对象文件编译并连接到Delphi单元,并提供一个满足需要的不完整的C运行时.为此,将使用有多伦多大学的Henry Spencer 发布的正则表达式搜索代码.只需要将其做少许修改使其可在Borland的C++编译器下可编译.正则表达式在Delphi帮助文件中有少量的描述,定义了一种灵活的搜索模式.
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 Borland's C++ compiler. Regular expressions are explained in short in the Delphi help files, and are a way of defining nifty search patterns.
对象文件
C通常产生对象文件,进一步连接成为可执行文件.在32位Windows下,通常以.obj为后缀.但是其具有不兼容的格式.微软的C++编译器和其他兼容编译器会生成做稍微修改的COFF格式.这不能用在Delphi中.Delphi需要OMF格式的对象文件.现在没有可行的办法将COFF格式转换为OMF,因此需要源码,将其编译为OMF文件.
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 these come in different, incompatible formats. Microsoft's C++ compiler, and some other compatible compilers, generate object files in a slightly modified COFF format. These can't be used in Delphi. Delphi requires OMF formatted object files. There is no practicable way of converting normal COFF object files to OMF, so you will need the source, and a compiler that generates OMF files.
注意C++Builder提供了很多版本的COFF2OMF工具对此也没什么帮助.其只能将导入的库从一种格式转换为另一种格式.导入的库文件只包括DLL导出函数的信息,可由DLL直接生成,或使用IMPLIB或其他类似工具.他们只包括C或C++库文件内容的一个有限的子集. COFF2OMF不能转换COFF格式的C或C++对象或实际的库文件.需要源代码以及一个Borland编译器产生可用于Delphi的OMF对象文件.
Note that the COFF2OMF utility which comes with many versions of C++Builder is no help at all for this problem. It is only meant to convert import libraries from one format to the other. Import library files only contain information about the exported functions 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 or C++ library files contain.COFF2OMF will not convert C or C++ object or real library files (see note below) in the COFF format. You really need source code and a Borland compiler to produce OMF object files usable with Delphi.
更新: Thaddy de Koning告诉我DigitalMars的COFF2OMF转换器可以完全进行转换.我没有去验证,但他说这个工具物有所值.
UPDATE: Thaddy de Koning told me that the COFF2OMF converter from DigitalMars can do a complete conversion. I didn't try it, but he said it was worth its money.
更新2: 著名的优化领域专家Agner Fog的开发的ObjConv工具,可将几种对象文件格式转换为其他格式.这样Delphi就可以使用C++生成的OMF格式对象文件或由非OMF对象文件转换的对象文件了.
UPDATE 2: Agner Fog, well known optimization guru, pointed me to hisObjConv tool, wich will convert several types of object files to several others. We are also working on making C++-generated OMF and non-OMF generated object files usable in Delphi.
Borland/CodeGear's C++ Builder可以生成OMF对象文件.但是并非所有的Delphi用户都安装了C++Builder.幸运的是,自C++Builder5以来Borland公司提供了免费的命令行编译器.可从这里进行下载this link,提供一些信息后就可下载.如果还没有环境请先下载.Borland已经发布了6.0版本,由于免费版5.0编译器仍然可用对此还没了解.
Borland/CodeGear's C++ Builder does generate such OMF object files. But not each Delphi user has C++Builder as well. Luckily, Borland made the command line compiler that comes with Borland C++ Builder version 5 freely available. it can be downloaded from this link, if you provide some information. If you don't have it yet, get it now. Borland already released version 6, so it is not clear until when the free version 5 compiler will be available.
更新: Borland/CodeGear 与IdE一起发布了新的免费编译器, Turbo C++ Explorer. 虽然名称相同但与上世纪的Borland/Turbo C++产品是不一样的,其或多或少相似于BDS2006,但只有一种语言.可以下载带IDE的Explorer版本,所带授权允许编译商业应用,但不能安装新控件(可在代码中使用控件类).下载地址:Turbo Explorer download page.
UPDATE: Borland/CodeGear released a new free compiler with IDE, Turbo C++ Explorer. Although it has the same name, it is not the same as the old Borland/Turbo C++ products of the last century, it is more or less the little brother ofBDS 2006, but with only one language. You can download the Explorer version, which is a full IDE with everything necessary, even a license that allows you to create commercial applications, except the possibility to install new components in the IDE (you can still use those components in code). It can be downloaded from the Turbo Explorer download page.
可使用的文件还有其他限制.只能使用编译为C文件的对象文件,而不能是C++文件.Delphi编译器处理包括C++的对象文件是有问题的.也就是说源码文件后缀必须是.c而不能是.cpp.但是由于无论如何都不能直接使用C++类,这也不会什么严格限制.
There is another limitation to what kind of files you can use. 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 contain C++. 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.
注意:C也经常使用.lib文件.其简单的包含多个对象文件, 一些C编译器同时提供一个字典程序来提取,插入,替换或简化其中的对象文件列表.在Delphi中不能直接连接.lib文件.但可以使用Delphi和C++Builder携带的TDUMP工具查看其中包含的内容.免费的C++编译器带了一个TLIB字典.
One note: C often uses 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 comes with theTLIB librarian.
代码
这里不会讨论正则表达式的机制和使用方法.网上书上有大量的资料可循.但要探索一下他们的代码实现,首先对正则表达式进行简单的编译,将文本表达式转换为便于交互的搜索代码.编译工作由regcompile()函数完成.要使用一个正则表达式来搜索字符串,需要向regexec()函数传递已编译的模式对象和一个字符串.将返回字符串与模式是否匹配以及位置等信息.
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 functionregcompile(). To search a string for a regular expression pattern, you pass the compiled pattern and the string to theregexec() function. It will return information about if, and where in the string, it found text matching the pattern.
正则表达式搜索的编译实现代码很复杂而且很多,这里不做详述.但其头文件对Delphi代码使用对象文件是很重要的.如下:
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.
以RE_开头的#define值作为函数返回值指示成功或错误. NSUBEXP是正则表达式实现中可能的子表达式的个数.叫做MAGIC的数值必须存在于每个编译过的正则表达式中.如果不存在则说明结构体中不包含合法的编译后正则表达式.注意0234不是一个十进制数.前导0报送C编译器这是一个八进制值.正如16进制以16作为基数,十进制为10,八进制为8.与十进制转换公式为:
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 calledMAGIC 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)
#pragma pack(push, 1) 设置对齐状态,设置其字节对齐.#pragma pack(pop) 恢复以前状态.这是很重要的,因为其使结构体与Delphi的packed record兼容.
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.
编译代码
如果有C++ Builder,或BDS2006,编译代码很简单.创建新项目,右击"Project",在弹出菜单中选择"Add to project"加入"regexp.c"文件,编译项目.将在目录中生成一个"regexp.obj"文件.
If you have C++ Builder, or BDS2006, it is a little easier 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, the directory will contain a file "regexp.obj"
如果有一个命令行编译器,并设置了正确的环境变量,打开命令行工具,,进入到"regexp.c"文件所在目录,输入:
If you have the command line compiler, and that is set up correctly, you open a command prompt, go to the directory that contains the file "regexp.c" and enter:
bcc32 -c regexp.c
可能会警告存在未使用的变量,或类型转换丢失有效位数,在此忽略这些信息,因为不必对此进行编码.使用这些代码多年来一直被使用,从没出现问题.编译后,在源码同目录中找到对象文件"regexp.obj".
Perhaps you'll get a warning about an unused variable, or about conversions losing significant digits, but you can ignore themin this case, since you didn't write the code anyway. I am using this code myself for years already, without any problems. After compilation, you'll find the object file "regexp.obj" in the same directory as the source file.
要在Delphi中导入对象文件,需要将对象文件拷贝到Delphi的源码路径中.
To import the object file in Delphi, you should now copy the object file to the directory with your Delphi source.
导入对象文件
要使用对象文件中的代码,必须要写一些声明.Delphi连接器对函数的参数,头文件中的regexp类型,regexp.h中定义的变量等一无所知.也不知道使用什么调用约定.为此需要写一个导入单元.
To use the code in the object file, you'll have to write some declarations. The Delphi linker doesn't know anything about the parameters of the functions, about theregexp type 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.
这里是一个Delphi单元的interface部分,用来导入C对象文件中的函数和变量.
Here is the interface part of the Delphi unit that is used to import the functions and values from the C object file into Delphi:
unit RegExpObj;
interface
const
NSUBEXP = 10;
// The first byte of the regexp internal "program" is actually this magic
// number; the start node begins in the second 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;
注意所有的函数名称前都有一个下划线.这是由于历史原因,大多数C编译器都会在C函数名称前加上一个下划线.要导入他们也必须使用下划线开头的函数名称.可以告诉C++Builder忽略下划线,但通常不会这样做.下划线表示我们使用的是C函数.也必须使用C的调用约定,及Delphi中的cdecl.忘记这点会导致很难定位的bug.
You'll notice that all the functions got an underscore in front of them. This is because, for historic reasons, most C compilers still generate C functions with names that start with an underscore. To import them, you'll have to use the "underscored" names. You could tell the C++Builder compiler to omit the underscores, but I normally don't do that. The underscores clearly show that we are using C functions. These must be declared with the C calling convention, which is calledcdecl in Delphi parlance. Forgetting this can produce bugs that are very hard to trace.
Henry Spencer提供的代码中不包括reggeterror() 和 regseterror()函数.这是后来引入的,因为不能在Delphi端直接使用对象文件中的变量,代码需要将错误值设置为0以及获取错误值的方式.但可以在C对象文件中直接使用Delphi的变量.有时对象文件需要外部变量存在.如果其不存在可以在Delphi代码中声明.
The original code of Henry Spencer didn't have the reggeterror() andregseterror() functions. I had to introduce them, because you can't use variables in the object files from the Delphi side directly, and the code requires access to reset the error value to 0, and to get the error value. But you can use Delphi variables from the C object file. Sometimes object files even require external variables to be present. If they don't exist, you can declare them somewhere in your Delphi code.
理想情况下,单元的implementation部分如下所示:
Ideally, the implementation part 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.
但编译后Delphi连接器抱怨不安全的外部变量(unsatisfied externals).Delphi单元必须提供定义.很多运行时函数很简单,在Delphi中可轻松实现.只是对于可变参数的函数如printf() 或scanf(),必须依赖汇编实现.可能在C++库中发现printf() 或scanf(),必须提取对应的对象文件并进行连接.这种情况没有做过尝试.
But if you compile that, the Delphi linker will complain about unsatisfied externals. The Delphi unit will have to provide them. Most runtime functions are simple, and can easily be coded in Delphi. Only functions that take a variable number of arguments, like printf() or scanf(), are impossible to do without resorting to assembler. Perhaps, if you could find the code ofprintf() or scanf() in the C++ libraries, you could extract the object file and link that file in as well. I have never tried this.
正则表达式代码需要C库函数malloc()申请内存, strlen()计算字符串长度, strchr()在字符串中查找单个字节, strncmp()比较两个字符串, strcspn()从另一个字符串中查找一个字符串的第一个字符.
The regular expression code needs the C library functions malloc() to allocate memory,strlen() to calculate the length of a string, strchr() to find a single character in a string,strncmp() to compare two strings, and strcspn() to find the first character from one string in another string.
这四个函数很简单,在Delphi中可一行代码搞定,因为Delphi中也有类似的代码.但是strcspn()在Delphi的运行时库中没有相应的函数,必须手都实现.幸运的是,可以将C代码翻译为Delphi代码.否则必须仔细阅读参数规格,并尝试实现出来.
The first four functions are simple, and can be coded in one line of Delphi code, since Delphi has similar functions as well. But forstrcspn() there is no equivalent function in the 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 to translate that to Delphi. Otherwise I'd have had to read the specifications really carefully, and try to implement it myself.
单元的部分代码如下所示:
The missing part of the implementation section of the unit looks like this:
// since this unit provides the code for _malloc, it can use FreeMem to free
// the PRegExp it gets. 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;
while SrchS2^ <> #0 do
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;
可见,函数必须以dcecl关键字声明,并名称前加下划线.函数名称大小写敏感,必须正确的拼写.
As you can see, these functions must also be declared cdecl and have a leading underscore. The function names are also case sensitive, so their correct spelling is important.
中这个项目中,不会直接使用代码. _RegExp结构体包含的信息不必进行转换,也是字节对齐的.所以将其传递个几个简单的函数,并提供RegFree函数,其简单的调用FreeMem,因为_malloc()在中调用了GetMem.当然,正则表达式代码也要提供一个regfree()函数.
In my project, I don't use this code directly. The _RegExp structure contains information that should not be changed from outside, and is a bit awkward to use. So I wrapped it up in a few simple functions, and provided aRegFree function as well, which simply calls FreeMem, since the_malloc() I provided uses GetMem. Ideally, the regular expression code should have provided aregfree() function.
导入代码和包装单元的C源码,是简单的grep程序,可从Downloads下载.
The entire C source code, the code for the import unit and the wrapper unit, as well as avery simple grep program can be found on my Downloads page.
使用msvcrt.dll
除了写出所有的函数,也可以使用Microsoft Visual C++的运行时库函数.这个DLL也是Windows使用的,因此其出现在各个Windows版本中.
Instead of writing all these functions yourself, you could also use functions from the Microsoft Visual C++ runtime library. This is a DLL which Windows also uses, and that is why it should be present on all versions of Windows.
FWIW不是我的目标,我在Borland新闻组中向Rob Kennedy提过建议.在JEDI项目中也使用了这种技术.
FWIW, this is not my idea, I got it as a suggestion from Rob Kennedy in the Borland newsgroups. It seems theJEDI project also uses this technique in some of their sources.
使用msvcrt.dll替换上面的代码,只需简单的声明例程问external.确保名称使用external来声明,因为这些例程在DLL中没有下划线:
Using msvcrt.dll, instead of the code above, you could simply declare most of the routinesexternal. Be sure to use the name clause in the external declaration, because these routines do not have an underscore in the DLL:
// Note that you don't want to use the C memory manager,
// so you must still rewrite routines like _malloc() in Delphi.
function _malloc(Size: Cardinal): Pointer; cdecl;
begin
GetMem(Result, Size);
end;
// The rest can be imported 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';
这也可以处理复杂的例程如sprintf() 或 scanf().对于C中需要的文件句柄,可以简单的声明为一个指针.效果是一样的.例如:
This will even work for complicated routines like sprintf() orscanf(). Where C requires a file handle, you simply declare a pointer. The effect is the same. 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';
可从Downloads下载对msvcrt.dll简单的测试接口单元.以后会对其完善.
I put a slightly tested version of an interface unit for msvcrt.dll on myDownloads page. I will change this page as soon as it is well tested.
问题
同时也解决了几个读者感兴趣的问题.按约定写了一个简单的测试C程序,其中调用了很多从msvcrt.dll导入的例程.但是其中的一些例程打破了惯例.它们以直接操作结构体的宏的形式实现,在BCB C,Delphi,Microsoft C的并不总存在或可用.
In the meantime, I have encountered a few problems which might be interesting for the reader. For the conversion, I wrote a simple test program in C, which uses many of the routines imported frommsvcrt.dll. But it turned out that some of the routines are not routines at all. They are implemented asmacros, which directly access structures, and these don't always exist or are accessible in this mix of BCB C, Delphi and Microsoft C.
getchar() 和 putchar()
例如,对于getchar()例程.在stdio.h中其声明为宏,存取stdin->level,而stdin也是一个宏&_streams[0].如果level变量是正数,例程从缓冲中读取到一个字符,否则使用_fgetc()(IOW, Delphi端为__fgetc()).因此无论如何声明例程,都不会被调用.
For instance, take the getchar() routine. In stdio.h, this is declared as a macro, which accessesstdin->level, and stdin is again a macro for &_streams[0]. If thislevel variable is positive, the routine uses a character 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.
这意味着必须声明__streams,初始化level域为负数.问题是msvcrt.dll例程还有其他版本的简单结构体(这里不能担保FILE结构相同),而且不能设置或读取BCB的变量_streams.因此需要写一个Delphi版本的__fgetc()函数检查参数stream是否等于@__streams[0],表明其与标准输入流stdin一同调用.如果成立,意味着调用_fgetch(stdin);,相当于getchar()宏.如果就是这样,其调用Delphi的Read,否则调用msvcrt.dll中的_fgetc()例程.
This meant I had to declare __streams, and initialize thelevel fields to something negative. The problem is that the msvcrt.dll routines will have their own versions of similar structs (there is no guarantee that theFILE 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 what thegetchar() macro amounts to. If this is the case, it calls Delphi's Read, otherwise it uses the_fgetc() routine in msvcrt.dll.
我希望如此,但是我更希望不是这样的.请将遇到的问题发送邮件给我.
I hope that this will do, but I'd like to know it if it doesn't. Pleaseemail me about any problems you encounter.
FWIW,我意识到一个事实,一个ntdll中的例程(例如fwrite())被调试器的int3断点停止执行.如果按F9或Run按钮,程序会继续执行.
FWIW, I am aware of the fact that one of the routines (fwrite(), I think) stops at anint 3 breakpoint in ntdll.DbgBreakPoint, if it is run in the debugger. If you do anF9 or Run, the program will continue.
但是putchar()例程也是一个宏,而且将会再次增加level值.因此可能与这个例程会有相同的问题.但我并没有遇到.但是我对getchar() 的修改可能会导致ungetc() 不能正常工作.(其也是一个宏).如果必要,可能必须在Delphi中要模拟全部的系统.因为C中使用了一些宏.
But the putchar() routine is a macro too, and this could increment thelevel again. So there may be similar problems with that routine. I did not encounter any, yet. But the changes I made forgetchar() mean that probably ungetc() might not work properly (AFAICS, this is a macro too). If necessary, I might have to emulate the entire system in Delphi. Just because C uses a few macros.
这些情况表明有些情况下的调用如msvcrt.dll 并不是简单而直接的.宏干扰了这个观点.
This goes to show that it is not always a case of simply redirecting calls to something likemsvcrt.dll after all. Macros ruin this idea.
宏是很不幸的东西.
FWIW, macros are evil evil evil.
fgetpos() 和fsetpos()
看起来中msvcrt.dll 的这两个例程在pos参数中存储了附件的数据.在BCB中,pos是简单的长整形.使用BCB的stdio.h中声明的fpos_t类型做参数调用_fgetpos()将引起访问冲突.因此需要使用_fseek()可_ftell()对这两个例程重新实现.
It seems that in msvcrt.dll these two routines store additional data in thepos parameter. In BCB, pos is a simple long integer. Using _fgetpos() with the declaration of fpos_t in BCB's stdio.h caused an access violation. So I wrote my own versions of these routines, using_fseek() and _ftell().
理论上,fpos_t是一个不透明的类型,而且两个例程都指向同一个实例.但是在BCB中其声明为长整形,因此分配的空间不能超过4字节.因此如果msvcrt.dll 试图存储更多数据,会导致覆盖其他数据,程序可能会退出,或引起访问冲突.这是使用不同编译器需要解决的任务.
In theory, fpos_t is an opaque type, and both routines only use pointers to one. But in BCB it is declared as along, so it won't be larger than 4 bytes, and that is what is allocated. So ifmsvcrt.dll tries to store more than that into it, some kind of data will be overwritten, and your program will not work properly, or cause an access violation. That is the risk of using code that is written with a different compiler.
结论
假设对C有所了解,并且愿意重新实现一些C运行时函数(如果使用msvcrtl.dll只需重写有限的函数),将C的对象文件连接到Delphi是很简单的.这样创建的程序不需要DLL,只需部署一个文件.
Provided you have a little knowledge of C, and are not afraid to write a replacement for a few missing C runtime library functions yourself (if you usemsvcrtl.dll, that number 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 deployed in one piece.
如果在使用C++ Builder命令行编译器时需要查看帮助,可以查看Borland的新闻组
If you need help with using the free C++ Builder command line compiler (compiler version 5.5), you will find excellent help in the Borland newsgroup
news://newsgroups.borland.com/borland.public.cppbuilder.commandlinetools
新闻组 news://newsgroups.borland.com/borland.public.cppbuilder.language
中可查看语言中遇到的问题.祝在验证中愉快.
is available for questions about the language. I wish you a nice time experimenting.
Rudy Velthuis