Delphi汇编转换为纯Pascal

3

我正在尝试将Delphi 5代码迁移到Delphi XE7-WIN64,但在以下代码块中遇到了混合汇编代码的问题。同时,我是一个汇编新手。

procedure IterateMenus(Func: Pointer; Menu1, Menu2: TElMenuItem);
var
  I, J: Integer;
  IIndex, JIndex: Byte;
  Menu1Size, Menu2Size: Integer;
  Done: Boolean;

  function Iterate(var I: Integer; MenuItem: TElMenuItem; AFunc: Pointer): Boolean;
  var
    Item: TMenuItem;
  begin
    if MenuItem = nil then Exit;
    Result := False;
    while not Result and (I < MenuItem.Count) do
    begin
      Item := MenuItem[I];
      if Item.GroupIndex > IIndex then Break;
      asm
        MOV     EAX,Item
        MOV     EDX,[EBP+8]
        PUSH    DWORD PTR [EDX]
        CALL    DWORD PTR AFunc
        ADD     ESP,4
        MOV     Result,AL
      end;
      Inc(I);
    end;
  end;

begin
  I := 0;
  J := 0;
  Menu1Size := 0;
  Menu2Size := 0;
  if Menu1 <> nil then
    Menu1Size := Menu1.Count;
  if Menu2 <> nil then
    Menu2Size := Menu2.Count;
  Done := False;
  while not Done and ((I < Menu1Size) or (J < Menu2Size)) do
  begin
    IIndex := High(Byte);
    JIndex := High(Byte);
    if (I < Menu1Size) then
      IIndex := Menu1[I].GroupIndex;
    if (J < Menu2Size) then
      JIndex := Menu2[J].GroupIndex;
    if IIndex <= JIndex then
      Done := Iterate(I, Menu1, Func)
    else
    begin
      IIndex := JIndex;
      Done := Iterate(J, Menu2, Func);
    end;
    while (I < Menu1Size) and (Menu1[I].GroupIndex <= IIndex) do
      Inc(I);
    while (J < Menu2Size) and (Menu2[J].GroupIndex <= IIndex) do
      Inc(J);
  end;
end;

我正在尝试将asm块转换为PurePascal,因为Delphi x64不允许混合代码,并且asm不易于程序员使用。

据我所知,Item的地址被移动到EAX, 然后我就不知道了。EBP从哪里来?ESP和AL是什么? 上面的代码片段来自ELPack中的ELmenus.pas。

因此,asm代码块的PurePascal版本将是什么?

对于func,我找到了这个:

procedure TElMenuItem.UpdateItems;

  function UpdateItem(MenuItem: TElMenuItem): Boolean;
  begin
    Result := False;
    IterateMenus(@UpdateItem, MenuItem.FMerged, MenuItem);
    MenuItem.SubItemChanged(MenuItem, MenuItem, True);
  end;

begin
  IterateMenus(@UpdateItem, FMerged, Self);
end;

procedure TElMenuItem.PopulateMenu;
var
  MenuRightToLeft: Boolean;

  function AddIn(MenuItem: TElMenuItem): Boolean;
  begin
    MenuItem.AppendTo(FHandle, MenuRightToLeft);
    Result := False;
  end;

begin    // all menu items use BiDiMode of their root menu
  {$ifdef VCL_4_USED}
  MenuRightToLeft := (FMenu <> nil) and FMenu.IsRightToLeft;
  {$else}
  MenuRightToLeft := false;
  {$endif}
  IterateMenus(@AddIn, FMerged, Self);
end;

能否展示一下 "Func" 函数的签名?也就是说,函数 "Func" 期望哪些参数作为传递给 "IterateMenus" 函数的参数? - Ville Krumlinde
在原始代码中查找IterateMenus调用中使用的func是什么?了解其原型很有用。[EBP+8]看起来像一个变量,AL是func返回值(布尔值)的低字节。 - MBo
@VilleKrumlinde 更新了带有“Func”的问题。 - AEonAX
2个回答

4
我猜原本的程序员编写了奇怪的代码来绕过Delphi不支持本地函数指针的限制。(这个限制有时候会让我感到很烦恼。)以下内容应该是可行的。(尽管我对asm并不太精通,不能完全确定。我建议首先通过D5中的调试器逐步调试asm代码,以确认代码行“CALL DWORD PTR AFunc”是否传递了我期望的值。)
1)声明一个函数指针类型,并将UpdateItem移动到不再是本地的位置。
type
  TMenuOperation = function (AMenuItem: TElMenuItem): Boolean;
  //Or if you want to move the UpdateItem to a method of a class..
  //TMenuOperation = function (AMenuItem: TElMenuItem): Boolean of object;

2) 进行以下更改:

procedure IterateMenus(Func: Pointer; Menu1, Menu2: TElMenuItem);
//becomes
procedure IterateMenus(Func: TMenuOperation; Menu1, Menu2: TElMenuItem);

//...

  function Iterate(var I: Integer; MenuItem: TElMenuItem; AFunc: Pointer): Boolean;
  var
    Item: TMenuItem;
  //becomes
  function Iterate(var I: Integer; MenuItem: TElMenuItem; AFunc: TMenuOperation): Boolean;
  var
    Item: TElMenuItem;

  //...

      Item := MenuItem[I];
      //becomes
      Item := MenuItem[I] as TElMenuItem;

3) 最后,汇编程序块变为:

Result := AFunc(Item);

3
那段汇编代码在调用“Item”的一个方法。我想说写这段代码的人需要检查一下自己的头脑。就像他们不知道什么是方法指针一样。
我会尝试找到调用该函数的代码,看看作为“Func”参数传递了什么。那就是被“asm”块所调用的东西。将“Func”参数的类型更改为适当的过程类型,并用一个方法指针来替换“asm”块中的调用。
好的,现在我明白原始代码正在快速松动本地程序。你应该将“Func”设为匿名方法类型:
Func: TFunc<TElMenuItem, Boolean>;

然后将本地函数转换为匿名方法。


通常升级Delphi版本的方法是同时升级第三方库。如果您这样做,我猜您就不需要移植15年前的库代码了。


2
我猜原始程序员编写了奇怪的代码来绕过Delphi不支持本地函数指针的限制。值得一提的是,最初开始并推广这种模式的“原始程序员”正是Anders Hejlsberg,他是Turbo Pascal编译器的开发人员和Delphi的首席架构师(现在是微软的杰出工程师)。是的,它是在解决无法引用嵌套过程的问题。这种技术是在Turbo Vision中的Turbo Pascal 6.0中引入的。在许多方面,这是真正的“闭包”的第一个版本。 - Allen Bauer
1
真的吗?怎么做到的?它解决了一个非常实际的问题,即能够在不设置一些全局状态或传递不透明的“上下文”情况下迭代一系列项目并执行操作。是的,今天显然有更好的解决方案,但当时它非常有用。仅通过当前技术水平的视角来批评解决方案,是不诚实和傲慢的典范。电灯对于当时来说是一项相当创新的技术,然而按照你的逻辑,它是暗淡无光、耗电量大且不必要的(夸张意图用于说明目的)。 - Allen Bauer
2
@David,如果当时工具的状态与今天一样,我肯定会同意你的评估,并质疑那段代码的作者的理智。在许多事情上,忽略或折扣历史可能会无意中关闭一个人的思维,使其无法接受许多有效的解决方案,甚至是构建下一个伟大解决方案的平台。我们今天并不比过去的开发者更聪明(在能力意义上); 我们只是有后见之明的优势。“如果我看得更远,那是因为我站在巨人的肩膀上”-艾萨克·牛顿。 - Allen Bauer
1
@Allen:Gnu Pascal(GCC前端)在与GCC后端进行TV thunking时也遇到了麻烦,特别是在主要版本之间。 - Marco van de Voort
1
@David:Delphi早在(TP-)objects和FV还不流行的时候就没有提供支持。此外,使用2或3个arch进行ifdef已经很痛苦了,而10个以上就更加困难。FPC从2003年中期开始减少RTL中的asm,并且可以使用一些简单的内置函数对构造进行参数化,这些函数返回父帧等。这些相同的基元在实现更深层次的异常处理程序时也非常有用。请参阅FPC rtl,get_caller_* 函数。 - Marco van de Voort
显示剩余29条评论

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