为什么不能在64位Delphi中将地址传递给嵌套的局部函数?

问题描述 投票:17回答:1

AS。自关闭相关问题以来-下面添加了更多示例。

下面的简单代码(它会找到一个顶级的Ie窗口并枚举其子级)可以在“ 32位Windows”目标平台上正常工作。早期版本的Delphi也没有问题:

procedure TForm1.Button1Click(Sender: TObject);

  function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
  const
    Server = 'Internet Explorer_Server';
  var
    ClassName: array[0..24] of Char;
  begin
    Assert(IsWindow(hwnd));            // <- Assertion fails with 64-bit
    GetClassName(hwnd, ClassName, Length(ClassName));
    Result := ClassName <> Server;
    if not Result then
      PUINT_PTR(lParam)^ := hwnd;
  end;

var
  Wnd, WndChild: HWND;
begin
  Wnd := FindWindow('IEFrame', nil); // top level IE
  if Wnd <> 0 then begin
    WndChild := 0;
    EnumChildWindows(Wnd, @EnumChildren, UINT_PTR(@WndChild));

    if WndChild <> 0 then
      ..    

end;

我插入了Assert来指示在使用“ 64位Windows”目标平台时失败的地方。如果我嵌套回调,则代码没有问题。

我不确定参数传递的错误值只是垃圾,还是由于某些错误的内存地址(调用约定?)引起的。嵌套回调实际上是我一开始不应该做的事情吗?还是这仅仅是我必须忍受的缺陷?

edit:为了回答David的回答,用相同类型的回调声明了EnumChildWindows的同一代码。在32位上正常工作:((编辑:由于我仍然使用'@'运算符,因此下面的内容并没有真正测试David所说的内容。它在该运算符上可以正常工作,但是如果删除它,除非取消取消嵌套该回调,否则它的确不会编译)

type
  TFNEnumChild = function(hwnd: HWND; lParam: LPARAM): Bool; stdcall;

function TypedEnumChildWindows(hWndParent: HWND; lpEnumFunc: TFNEnumChild;
    lParam: LPARAM): BOOL; stdcall; external user32 name 'EnumChildWindows';

procedure TForm1.Button1Click(Sender: TObject);

  function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
  const
    Server = 'Internet Explorer_Server';
  var
    ClassName: array[0..24] of Char;
  begin
    Assert(IsWindow(hwnd));            // <- Assertion fails with 64-bit
    GetClassName(hwnd, ClassName, Length(ClassName));
    Result := ClassName <> Server;
    if not Result then
      PUINT_PTR(lParam)^ := hwnd;
  end;

var
  Wnd, WndChild: HWND;
begin
  Wnd := FindWindow('IEFrame', nil); // top level IE
  if Wnd <> 0 then begin
    WndChild := 0;
    TypedEnumChildWindows(Wnd, @EnumChildren, UINT_PTR(@WndChild));

    if WndChild <> 0 then
      ..

end;

实际上,此限制并非特定于Windows API回调,但是当将该函数的地址带入procedural type的变量并将其作为一个自定义比较器传递给TList.Sort时,也会发生相同的问题。

http://docwiki.embarcadero.com/RADStudio/Rio/en/Procedural_Types

procedure TForm2.btn1Click(Sender: TObject);
var s : TStringList;

  function compare(s : TStringList; i1, i2 : integer) : integer;
  begin
    result := CompareText(s[i1], s[i2]);
  end;

begin
  s := TStringList.Create;
  try
    s.add('s1');
    s.add('s2');
    s.add('s3');
    s.CustomSort(@compare);
  finally
    s.free;
  end;
end;

当编译为32位时,它可以按预期工作,但是为Win64编译时,它以Access Violation失败。对于函数compare中的64位版本,s = nili2 =某个随机值;

即使一个人从compare函数中提取btn1Click函数,它对于Win64目标也可以按预期工作。

delphi delegates nested delphi-xe2 32bit-64bit
1个回答
20
投票

此技巧从未得到该语言的正式支持,并且由于32位编译器的实现细节,您到目前为止一直没有使用它。 documentation清晰:

嵌套过程和函数(在其他例程中声明的例程)不能用作过程值。

如果我没记错的话,一个额外的隐藏参数将通过指向封闭堆栈框架的指针传递给嵌套函数。如果未引用封闭环境,则在32位代码中将其省略。在64位代码中,总是传递额外的参数。

当然,问题的很大一部分是Windows单元对其回调参数使用无类型的过程类型。如果使用了键入程序,则编译器可能会拒绝您的代码。实际上,我认为这是对您所使用的欺骗绝不合法的信念的证明。对于类型化的回调,即使在32位编译器中,也永远无法使用嵌套过程。

无论如何,最重要的是,您不能将嵌套函数作为参数传递给64位编译器中的另一个函数。

© www.soinside.com 2019 - 2024. All rights reserved.