转贴,字符串执行函数:
来自: ghc_x, 时间: 2004-07-22 17:26:12, ID: 2725092
昨天晚上回家走在路上的时候,觉得这个问题比较有意思,就试着实现了一下,还是比较有意思的。为了简单起见,我在这里把要转换的代码改成
showmessage('c');
而不是楼主的
str:=inttostr(12345);
但原理都是一样的。
在这里说明一下,showmessage('c');为运行时要转换的代码,将该代码写在一个名为a.txt的文本文件的第一行,并将该文件和应用程序放在同一目录下。’c’为要显示的字符串,可以随时更改该字符串的内容(比如说可以改成 ’a’ 或 ‘z’ 什么的,如果改成大于1个字符的字符串那么只能显示该字符串的第一个字符,这个限制只是因为我比较懒:)而且代码看上去会简单一些,所以没有实现整个字符串的拷贝,和动态编译没有任何关系)以显示动态编译的效果。现在我们就可以把这个文本文件看作是一个procedure的实现代码,只不过该procedure只有一行代码showmessage('c');而已。新建一个工程,在form1上放一个按钮,点击该按钮则动态编译a.txt中的代码。好了,代码伺候:
//在单元的interface部分声明一个过程类型,该过程类型的变量就代表整个a.txt所代表
//的那个procedure,该变量其实是一个指针,指向procedure的代码存放地址。然后执行该
//变量程序流程就转到该地址了。其实还有一种实现方法就是在delphi中写嵌入式汇编,直
//接写call指令也能实现代码的跳转,这里用的是声明过程类型的方法。
TPro = procedure();
procedure TForm1.Button1Click(Sender: TObject);
var
F: TextFile;
S: string;
a: TPro;
b: integer;
c: string;
d: pointer;
e: pointer;
begin
AssignFile(F, 'a.txt'); //打开存有代码的文本文件
Reset(F);
Readln(F, S); //读取该文件第一行
CloseFile(F);
b := pos('showmessage', s); //定位代码的在该行中的起始位置
if b <> 0 then
begin
c := copy(s, b + 13, 1); //只拷贝字符串的第一个字符,偷了一点懒:)
d := @showmessage; //获得要showmessage的地址
e := AllocMem(11); //在堆上分配一个11个字节的内存块,
//我们动态编译后
//的代码就放在这里
pointer(@a) := e; //这里就是将过程类型的变量赋值为动态编译代码的
//地址
pbyte(integer(e))^ := $B8; //从这里就开始用procedure的机器码填充内存块
ppointer(integer(e) + 1)^ := ppointer(c); //获得要显示字符串的地址
pbyte(integer(e) + 5)^ := $E8;
pinteger(integer(e) + 6)^ := integer(d) - (integer(e) + 10);
//计算出showmessage函数的代码地址与当前地址之间的偏移量
pbyte(integer(e) + 10)^ := $C3;
//填充完内存块后,该11个字节内存块的布局应该是这样的:
//B8 XX XX XX XX E8 YY YY YY YY C3
//其中XX XX XX XX 表示要显示字符串的地址,YY YY YY YY 表示showmessage函数
//的代码地址与当前地址之间的偏移量。接下来看看这段代码的意思:
//第一句B8 XX XX XX XX 表示将eax寄存器赋值为XX XX XX XX
//第二句E8 YY YY YY YY 表示调用一个子程序也就是showmessage,子程序的地址为
//当前地址加上YY YY YY YY 。第三句C3表示返回调用该代码(这11个字节)的地方。
//再回头看第一句,就表示将showmessage的参数放入eax(delphi中过程的调用规则是将第
//一个参数传入eax)第二句就开始调用showmessage。
a; //就从这里,程序的流程就跑到刚才建立的那11个字节的代码了,最后的C3
//执行完又返回到这里
freemem(e); //释放分配的内存块
end;
end;
这就是一个最简单的动态编译的例子,总结一下,就是要分配一块地址空间,将动态编译后的机器码放入该地址空间,再使程序流程进入到这里。微软的.net框架的JIT就是这么实现的,.net框架在你调用某个过程的时候才将该过程的IL中间码(以文本形式存在)编译为机器码然后跳到刚才编译完的机器码去执行。