Create class instance using Delphi inline assembler
Asked Answered
S

2

5

What I would like to do is, using assembly, create a class instance, call one of it's methods and then free the instance.

I know I'm missing something very important and probably very simple, but I don't know what.

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.

FYI: I want to understand how I can achieve this at low level, not another way of doing it.

UPDATE:

Thanks to Andreas Rejbrand, I've managed to find the culprit:

Update2:

Thanks to Arnaud for finding flaw using EBX, rather than 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.
Scalpel answered 16/5, 2012 at 13:54 Comment(3)
EBX needs to be saved - have a look at Arnaud's answerOteliaotero
@Oteliaotero yes Sir, I've read Arnaud's answer, whomever is interested in this, will read all answers(only 2) and comments (:Scalpel
the thing is that in your update to the question you are using EBX without saving it. Please fix this (e.g. push ebx/pop ebx), since if you don't you run the risk that a real program using such a function might just crash -- and the reason for this would be very hard to find...Oteliaotero
V
2

You can read about this in an excellent guide to Delphi assembly programming, originally found here. Unfortunately, the site is down, but you can find an archived version here. Look in particular at page 5.

Vinosity answered 16/5, 2012 at 15:16 Comment(3)
Whilst this may theoretically answer the question, we would like you to include the essential parts of the linked article in your answer, and provide the link for reference. Failing to do that leaves the answer at risk from link rot.Volkslied
@Kev: I am perfectly aware of that, and that's the reason why I only sent this answer after being requested to do so. Originally, I only gave the link as a comment, although that is not apparent any longer (since you deleted the comments). Unfortunately, I don't have time/energy right now to learn the topics on the page I linked to.Vinosity
@AndreasRejbrand - I rolled old revision back, since it is better than nothing and can help others.Wilhite
B
11

Your asm code is not correct.

You are overloading the ebx register, which must be preserved. And the global variable trick does not make sense.

A better coding should be:

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);

But it does not protect the instance with a try...finally. :)

About the mov dl,true parameter, see this official page from the EMB wiki:

Constructors and destructors use the same calling conventions as other methods, except that an additional Boolean flag parameter is passed to indicate the context of the constructor or destructor call.

A value of False in the flag parameter of a constructor call indicates that the constructor was invoked through an instance object or using the inherited keyword. In this case, the constructor behaves like an ordinary method. A value of True in the flag parameter of a constructor call indicates that the constructor was invoked through a class reference. In this case, the constructor creates an instance of the class given by Self, and returns a reference to the newly created object in EAX.

A value of False in the flag parameter of a destructor call indicates that the destructor was invoked using the inherited keyword. In this case, the destructor behaves like an ordinary method. A value of True in the flag parameter of a destructor call indicates that the destructor was invoked through an instance object. In this case, the destructor deallocates the instance given by Self just before returning.

The flag parameter behaves as if it were declared before all other parameters. Under the register convention, it is passed in the DL register. Under the pascal convention, it is pushed before all other parameters. Under the cdecl, stdcall, and safecall conventions, it is pushed just before the Self parameter.

Since the DL register indicates whether the constructor or destructor is the outermost in the call stack, you must restore the value of DL before exiting so that BeforeDestruction or AfterConstruction can be called properly.

So an alternate valid coding, since eax our object is not nil so we can call the destructor directly, could be:

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;

In all case, object access from asm is not meant to be done this way. You do not have access to the type information directly, so it may be very difficult to work with it. With an existing class instance, you can do whatever you want with asm methods; but to create instances, and play with class types, asm is definitively not the natural way!

Banderole answered 16/5, 2012 at 15:46 Comment(4)
+1 thank you for the catch, side note: I wanted to contact you privately(regarding some of your projects), can you give me a link to where I can get your e-mail or give it directly please?Scalpel
@DorinDuminica If it is about the synopse projects, the main entry point is the forum at synopse.info but you can also write directly to me webcontact01 at synopse dot info :)Banderole
Why is necessary to push and pop the eax there ?Overrefinement
@RodrigoFariasRezino Because eax=self so is needed for property TList.Destroy call.Banderole
V
2

You can read about this in an excellent guide to Delphi assembly programming, originally found here. Unfortunately, the site is down, but you can find an archived version here. Look in particular at page 5.

Vinosity answered 16/5, 2012 at 15:16 Comment(3)
Whilst this may theoretically answer the question, we would like you to include the essential parts of the linked article in your answer, and provide the link for reference. Failing to do that leaves the answer at risk from link rot.Volkslied
@Kev: I am perfectly aware of that, and that's the reason why I only sent this answer after being requested to do so. Originally, I only gave the link as a comment, although that is not apparent any longer (since you deleted the comments). Unfortunately, I don't have time/energy right now to learn the topics on the page I linked to.Vinosity
@AndreasRejbrand - I rolled old revision back, since it is better than nothing and can help others.Wilhite

© 2022 - 2024 — McMap. All rights reserved.