使用Delphi内联汇编创建类实例

5
我想做的是使用汇编语言创建一个类实例,调用其中一个方法,然后释放该实例。
我知道我缺少非常重要且可能非常简单的东西,但我不知道是什么。
program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TSomeClass = class(TObject)
  private
    FCreateDateTime: string;
  public
    constructor Create;
    procedure SayYeah;
  end;

constructor TSomeClass.Create;
begin
  FCreateDateTime := DateTimeToStr(Now);
end;

procedure TSomeClass.SayYeah;
begin
  Writeln('yeah @ ' + FCreateDateTime);
end;

procedure Doit;
asm
  CALL TSomeClass.Create; // <= Access Violation
  CALL TSomeClass.SayYeah;
  CALL TSomeClass.Free;
end;

begin
  try
    Doit;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

为您提供信息:我希望了解如何在低级别上实现它,而不是其他方法。

更新

感谢Andreas Rejbrand,我已经找到了罪魁祸首:

更新2

感谢Arnaud使用EBX而非PUSH/POP EAX发现了缺陷

var
  TSomeClass_TypeInfo: Pointer;

procedure Doit;
asm
  MOV DL, $01;
  MOV EAX, TSomeClass_TypeInfo;
  CALL TSomeClass.Create;
  PUSH EAX;
  CALL TSomeClass.SayYeah; // call method
  POP EAX;
  MOV DL, $01;
  CALL TSomeClass.Free; // pointer to instance(Self) is expected in EAX
end;

begin
  TSomeClass_TypeInfo := TSomeClass;
  try
    Doit;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

1
EBX需要被保存 - 看看Arnaud的答案。 - PhiS
@PhiS 是的先生,我已经阅读了Arnaud的答案,有兴趣的人会阅读所有答案(只有2个)和评论(: - user497849
问题在于您在更新问题时使用了EBX,但没有保存它。请修复此问题(例如,推入EBX / 弹出EBX),因为如果不这样做,您可能会冒着真正使用此函数的程序崩溃的风险 - 而找到此问题的原因将非常困难... - PhiS
2个回答

11

您的汇编代码不正确。

您超载了必须保留的ebx寄存器。而且全局变量技巧没有意义。

更好的编码方式应该是:

procedure Doit(ClassType: pointer);
asm // eax=TList
  mov dl,true // hidden boolean 2nd parameter
  call TObject.Create
  push eax
  call TList.Pack
  pop eax
  call TObject.Free
end;

DoIt(TList);

但是 try...finally 不能保护实例。 :)

关于 mov dl,true 参数,请参见 EMB wiki 的官方页面:

构造函数和析构函数使用与其他方法相同的调用约定,除了传递一个额外的布尔标志参数来指示构造函数或析构函数调用的上下文。

在构造函数调用的标志参数中为 False 表示通过实例对象或使用 inherited 关键字调用了构造函数。在这种情况下,构造函数的行为类似于普通方法。在构造函数调用的标志参数中为 True 表示通过类引用调用了构造函数。在这种情况下,构造函数创建 Self 指定的类的实例,并在 EAX 中返回对新创建对象的引用。

在析构函数调用的标志参数中为 False 表示使用 inherited 关键字调用了析构函数。在这种情况下,析构函数的行为类似于普通方法。在析构函数调用的标志参数中为 True 表示通过实例对象调用了析构函数。在这种情况下,析构函数在返回之前释放 Self 给定的实例。

标志参数的行为就像它在所有其他参数之前声明一样。在寄存器约定下,它通过 DL 寄存器传递。在 Pascal 约定下,它在所有其他参数之前被推入。在 cdecl、stdcall 和 safecall 约定下,它在 Self 参数之前被推入。

由于 DL 寄存器指示构造函数或析构函数是否是调用堆栈中最外层的,因此必须在退出之前恢复 DL 的值,以便可以正确调用 BeforeDestruction 或 AfterConstruction。

因此,一个可替代的有效编码,因为 eax 对象不是 nil,所以我们可以直接调用 析构函数,可以是:

procedure Doit(ClassType: pointer);
asm // eax=TList
  mov dl,true
  call TObject.Create
  push eax
  call TList.Pack
  pop eax
  mov dl,true
  call TList.Destroy
end;

在任何情况下,不应该这样从 asm 访问对象。您无法直接访问类型信息,因此可能很难处理它。通过现有的 class 实例,您可以随心所欲地使用 asm 方法;但要创建实例并玩弄类类型,则 asm 明显不是自然的方式!


+1谢谢你的指正。另外想说一下:我想私下联系你(关于你的一些项目),你能给我一个获取你电子邮件的链接或直接告诉我吗? - user497849
@DorinDuminica 如果涉及到 Synopse 项目,主要入口是 http://synopse.info 上的论坛,但您也可以直接写信给我 webcontact01 at synopse dot info :) - Arnaud Bouchez
为什么需要在那里推送和弹出eax? - Rodrigo Farias Rezino
@RodrigoFariasRezino 因为eax=self,所以需要在TList.Destroy调用中使用它。 - Arnaud Bouchez

2
你可以在一份优秀的Delphi汇编编程指南中了解更多相关信息,该指南最初在此处找到。不幸的是,该网站已经关闭,但你可以在此处找到存档版本。特别要注意第5页

虽然这理论上回答了问题,但我们希望您在回答中包含链接文章的关键部分,并提供参考链接。如果未能这样做,答案可能会因为链接失效而面临风险。 - Kev
3
@Kev:我非常清楚这一点,这也是为什么只有在被要求之后我才发送了这个答案。最初,我只是将链接作为评论发送,尽管现在已不再明显(因为您已删除了评论)。不幸的是,我现在没有时间/精力学习我链接到的页面上的主题。 - Andreas Rejbrand
1
@AndreasRejbrand - 我回滚了旧版本,因为它总比没有好,而且可以帮助其他人。 - kludg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接