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

Com*_*sNo 5 delphi inline-assembly basm

我想要做的是,使用assembly,创建一个类实例,调用其中一个方法,然后释放实例.

我知道我错过了一些非常重要的东西,可能很简单,但我不知道是什么.

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.
Run Code Online (Sandbox Code Playgroud)

仅供参考:我想了解如何在低水平实现这一目标,而不是另一种方式.

更新:

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

Update2:

感谢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.
Run Code Online (Sandbox Code Playgroud)

Arn*_*hez 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);
Run Code Online (Sandbox Code Playgroud)

但它并不能保护实例try...finally.:)

关于mov dl,true参数,请参阅EMB维基的这个官方页面:

构造函数和析构函数使用与其他方法相同的调用约定,除了传递额外的Boolean标志参数以指示构造函数或析构函数调用的上下文.

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

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

flag参数的行为就像在所有其他参数之前声明一样.根据寄存器约定,它在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;
Run Code Online (Sandbox Code Playgroud)

在所有情况下,对象访问asm并不意味着以这种方式完成.您无法直接访问类型信息,因此使用它可能非常困难.使用现有class实例,您可以使用asm方法执行任何操作; 但是创建实例,并使用类类型,asm绝对不是自然的方式!


And*_*and 2

您可以在 Delphi 汇编编程的优秀指南中阅读有关此内容的内容,最初可以在此处找到。不幸的是,该网站已关闭,但您可以在此处找到存档版本。请特别查看第 5 页

  • @Kev:我完全意识到这一点,这就是为什么我只是在被要求后才发送这个答案的原因。最初,我只是将链接作为评论提供,尽管这不再明显(因为您删除了评论)。不幸的是,我现在没有时间/精力来学习我链接到的页面上的主题。 (3认同)