最高只能300分了,求教Delphi中如何调用函数?(300分)

  • 主题发起人 主题发起人 TK128
  • 开始时间 开始时间
T

TK128

Unregistered / Unconfirmed
GUEST, unregistred user!
如果我有这样一个数据结构,我应该如何构造一个通用程序调用方法?
数据结构如下:
TProcType=(ptProc,ptFunc,ptUser,ptSystem);
TFunctionAttribute=Packed Record
ProcType: Set of TProcType; //过程类型 系统表示是外部的过程 用户表示内部过程
ParamCount: Integer; //参数个数
ParamType: Array [0..MaxParamNumber-1] of Integer; //参数类型
ParamName: Array [0..MaxParamNumber-1] of String[MaxIdentLen]; //参数名
ProcAddr: Pointer; //函数地址
ResultType: Integer; //返回值类型
End;

TAttribute = Packed Record //属性结构
Name: String[MaxIdentLen]; //名称
DataType: Integer; //数据类型
BaseType: Integer; //基本类型
Size: Integer; //大小
Level: Integer; //所处层次
Address: DWord; //变量地址
Sub: TTypeDescription; //下推式符号表,包含一些必要的操作和数据结构,和本问题无关
Case Integer of
ID_ARRAY:(ArrayParam: TArrayAttribute); //数组描述
ID_PROCEDURE,ID_FUNCTION:( ProcParam: TFunctionAttribute); //过程描述
ID_CONST: (Value: Pointer); //常量值
End;
上面这个数据结构是我在构造一个解释器时符号表用的数据结构,抛开结构的其他部分不
论,就函数调用而言,现碰到了一些问题:当我根据外部程序的描述,如:

Function Trim(S: String): String;

此时数据结构值为:

Attribute.Name:='Trim';
Attribute.DataType:=ID_FUNCTION;
Attribute.BaseType:=ID_FUNCTION;
Attribute.Size:=0;
Attribute.Level:=0; //最高级别,全局函数,谁都可以访问
Attribute.Address:=Index; //该函数在函数库中的索引
Attribute.Sub:=Nil;
Attribute.ProcType:=[ptFunc,ptSystem];
Attribute.ParamCount:=1;
Attribute.ParamType[0]:=ID_STRING;
Attribute.ParamName[0]:='S';
Attribute.ResultType:=ID_STRING;

当然这个数据结构是由专门的程序分析而得,不是由自己用手输入的!

有了这个数据结构,这时候产生了两个问题:(假定该函数是DELPHI内部的函数或是WINDOWS API,不然问题更多)
1. 如何取得定义函数的地址?(DELPHI中可没有DBASE中宏替换功能)
2. 如何构造一个通用的函数调用函数?(能根据这个数据结构的描述去调用对应的函数)

本人尝试过如下方法:
1. 建立一个形如: Function CallUserFunction(Index: Integer; S: Array of Variant): Variant;
的函数,在该函数中用第一个参数去查找某个数据表,得到要调用函数的地址然后调用,这种方法要求
所有函数都必须有同样参数,同样的申明,申明必须如下:

Function (S: Array of Variant): Variant;

通过定义该函数去调用系统和API函数,具体应用如下:

Function Own_Trim(S: Array of Variant): Variant; //替代Trim函数
Begin
if VarType=varString Then Result:=Trim(S[0])
Else Result:='';
End;

这样也能解决这个问题,但这种方法本人总认为较为不好

2. 由于上面的方法需要对每个系统函数编制另一个替代函数,因此本人又做了如下尝试:
(下面的程序不是本人过的程序仅是方法相同)
Function CallUserFunction(Index: Integer; S: Array of Variant): Variant;
Var
I: Integer;
Begin
For I:=0 To Attribute.ProcParam.ParamCount-1 Do
Begin
Case Attribute.ProcParam.ParamType of
ID_STRING:
Begin
Asm
Push String(S)
End;
End;
ID_INTEGER:
Begin
Asm
Push String(S)
End;
End;
End;
Asm
Call Attribute.ProcParam.ProcAddr
End;
End;
这种通过形成汇编指令间接调用的方式对WINDOWS API有效,对Delphi内部函数就无效了,
原因是Window API因为是操作系统函数,在汇编这个级别上严格的定义了输入、输出结果,
而Delphi内部函数由于有字符串操作,因此当用这样的调用方式去调用一些内部函数就出现
访问无效内存的错误,这种错误不是由于PASCAL调用规则和WINAPI调用规则引起的。

以上就是我的问题描述,请众高手指教!


 
哈哈,老兄,您的上面这段代码已经可以让很多人获益了!谢谢!

>>对Delphi内部函数就无效了
默认情况下,delphi使用“register”方式,若参数在3个已内,将分别使用eax、edx和
ecx,超过3个参数部分将使用堆栈。返回参数的存放视长度而定,例如8位用al返回,16位
用ax,32位用eax,64位用用两个32位寄存器edx:eax,其中eax是低位。
因此,对Delphi函数的调用,应该先判断参数个数:若大于3,则先将第4到n号压入堆栈,
然后做法和等于3的一样;如果等于3,则应该将它们分别放入eax,edx,ecx;如果是两个...
 
我没时间进行仔细研究,不知道以下会不会对你有帮助或启发:

procedure TForm1.Button1Click(Sender: TObject);
var
CallTrim:function(const s:string):string;
begin
CallTrim:=trim;
showmessage('['+CallTrim(' ABCD A!@# ')+']');
end;
 
老兄莫不是想自己做Pascal 的脚本解释/执行器?
函数底层的调用是比较复杂的工作,各种类型的转换,堆栈操作都要十分小心。
我原来也想自己做一套,现在在用一套第三方带源码(上万行,写得不错)的然后自己改造过的 Pascal 的脚本解释/执行器,感觉轻松多了
可以挂接系统函数,标准Delphi函数和自编函数,以及外部 DLL 调用,支持伪编译(运行速度大大提高)
另外还支持各种流程控制,数组,对象等等。
原先的注册函数很麻烦也不直观,经我改造注册函数使用如下所示例:

MyRegFunc(MyScripter, @ShowMessage, 'procedure ShowMessage(const Msg: string);');

其中第二个参数是欲注册的 Delphi 实际函数地址,第3个函数形式说明。很方便吧?:)
关于汇编如何调用我就不详细解释了,因为我用的这套东西已经很好的实现了,支持 Object Pascal 中绝大部分数据类型
我也碰到些 bug (比如某些浮点数类型堆栈操作)我都自己改好了
如果你需要可以进一步交流。
 
To creation-zy:
不是由于这个原因,我指定调用方式为 Stdcall,也就是后面我所采用的方法
To jsxjd: Sorry

To 轻松虎:
我是在做一个PASCAL解释器,基本差不多了,就差函数调用
我的QQ:25644550 你的呢?
 
确实是高手云集
 
to all:
我不明白写这个东东有什么用吗?
 
如果没用那还写干嘛?
 
to all:

到底有什么用,能不能详细解释一下。
 
有些数据处理方面的问题,无法用常规的方法来解决,也无法归纳一种通用的处理模式,
因此只能通过用户自己编写一段小程序来解决数据处理问题
 
to TK128,我的QQ:3197541
 
我前一段时间写了一篇关于函数调用的心得,也许对你有帮助,
不过在我自己的机器里,在学校,我过几天贴上来。

Delphi 内部函数的参数规则一般都是默认的 register 规则,这好办。
 
多人接受答案了。
 
后退
顶部