Создайте экземпляр класса, используя встроенный ассемблер Delphi.

Что я хотел бы сделать, так это, используя сборку, создать экземпляр класса, вызвать один из его методов и затем освободить экземпляр.

Я знаю, что упускаю что-то очень важное и, возможно, очень простое, но не знаю что.

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.

К вашему сведению: я хочу понять, как я могу добиться этого на низком уровне, а не другим способом.

ОБНОВЛЕНИЕ:

Благодаря Андреасу Рейбранду мне удалось найти виновника:

Обновление 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.

person Community    schedule 16.05.2012    source источник
comment
EBX нужно сохранить - посмотрите ответ Арно   -  person PhiS    schedule 17.05.2012
comment
@PhiS да, сэр, я прочитал ответ Арно, кому это интересно, тот прочитает все ответы (только 2) и комментарии (:   -  person    schedule 17.05.2012
comment
дело в том, что в своем обновлении вопроса вы используете EBX, не сохраняя его. Пожалуйста, исправьте это (например, нажмите ebx/pop ebx), так как если вы этого не сделаете, вы рискуете, что реальная программа, использующая такую ​​функцию, может просто рухнуть - и причину этого будет очень трудно найти...   -  person PhiS    schedule 17.05.2012


Ответы (2)


Вы можете прочитать об этом в превосходном руководстве по программированию на ассемблере для Delphi, первоначально найденном здесь. К сожалению, сайт недоступен, но вы можете найти архивную версию здесь. Посмотрите, в частности, на страницу 5.

person Andreas Rejbrand    schedule 16.05.2012
comment
Хотя теоретически это может ответить на вопрос, мы хотели бы, чтобы вы включили основные части связанной статьи в свой ответ и предоставили ссылку для ссылка. В противном случае ответ подвергается риску из-за гниения ссылок. - person Kev; 16.05.2012
comment
@Kev: Я прекрасно знаю об этом, и именно поэтому я отправил этот ответ только после того, как меня попросили сделать это. Первоначально я дал ссылку только в качестве комментария, хотя это уже не очевидно (поскольку вы удалили комментарии). К сожалению, сейчас у меня нет времени/энергии для изучения тем на странице, на которую я дал ссылку. - person Andreas Rejbrand; 16.05.2012
comment
@AndreasRejbrand - я откатил старую версию, так как это лучше, чем ничего, и может помочь другим. - person kludg; 16.05.2012

Ваш ассемблерный код неверен.

Вы перегружаете регистр 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:

Конструкторы и деструкторы используют те же соглашения о вызовах, что и другие методы, за исключением того, что передается дополнительный логический параметр флага, чтобы указать контекст вызова конструктора или деструктора.

Значение False в параметре флага вызова конструктора указывает, что конструктор был вызван через объект экземпляра или с использованием ключевого слова inherited. В этом случае конструктор ведет себя как обычный метод. Значение True в параметре флага вызова конструктора указывает, что конструктор был вызван через ссылку на класс. В этом случае конструктор создает экземпляр класса, заданного Self, и возвращает ссылку на вновь созданный объект в EAX.

Значение False в параметре флага вызова деструктора указывает, что деструктор был вызван с использованием унаследованного ключевого слова. В этом случае деструктор ведет себя как обычный метод. Значение True в параметре флага вызова деструктора указывает, что деструктор был вызван через экземпляр объекта. В этом случае деструктор освобождает экземпляр, заданный Self, непосредственно перед возвратом.

Параметр флага ведет себя так, как если бы он был объявлен перед всеми остальными параметрами. Согласно соглашению о регистрах, он передается в регистре 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 определенно не является естественным способом!

person Arnaud Bouchez    schedule 16.05.2012
comment
+1 спасибо за улов, примечание: я хотел связаться с вами в частном порядке (по поводу некоторых ваших проектов), не могли бы вы дать мне ссылку, где я могу получить ваше электронное письмо или дать его напрямую, пожалуйста? - person ; 16.05.2012
comment
@DorinDuminica Если речь идет о проектах синопсиса, основной точкой входа является форум по адресу synopse.info, но вы также можете написать прямо ко мне webcontact01 в синопсисе dot info :) - person Arnaud Bouchez; 16.05.2012
comment
Зачем нужно нажимать и совать туда EAX? - person Rodrigo Farias Rezino; 09.02.2017
comment
@RodrigoFariasRezino Поскольку eax=self необходим для вызова свойства TList.Destroy. - person Arnaud Bouchez; 15.02.2017