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.
4

2 回答 2

10

您的 asm 代码不正确。

您正在重载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 值表示构造函数是通过实例对象或使用继承关键字调用的。在这种情况下,构造函数的行为类似于普通方法。构造函数调用的标志参数中的 True 值表示构造函数是通过类引用调用的。在这种情况下,构造函数创建 Self 给定的类的实例,并返回对 EAX 中新创建的对象的引用。

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

flag 参数的行为就像它在所有其他参数之前声明一样。在寄存器约定下,它在 DL 寄存器中传递。根据帕斯卡约定,它在所有其他参数之前被推送。在 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绝对不是自然的方式!

于 2012-05-16T15:46:16.007 回答
2

您可以在最初在这里找到的 Delphi 汇编编程的优秀指南中了解这一点。不幸的是,该站点已关闭,但您可以在此处找到存档版本。请特别查看第 5 页

于 2012-05-16T15:16:58.717 回答