为什么不能在 64 位 Delphi 中获取嵌套本地函数的地址? [英] Why cannot take address to a nested local function in 64 bit Delphi?

查看:36
本文介绍了为什么不能在 64 位 Delphi 中获取嵌套本地函数的地址?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

AS. since closing related questions - more examples added below.

下面的简单代码(查找顶级 Ie 窗口并枚举其子窗口)在32 位 Windows"目标平台上运行正常.早期版本的 Delphi 也没有问题:

The below simple code (which finds a top-level Ie window and enumerates its children) works Ok with a '32-bit Windows' target platform. There's no problem with earlier versions of Delphi as well:

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"目标平台上失败的地方.如果我取消嵌套回调,代码就没有问题.


I've inserted an Assert to indicate where it fails with a '64-bit Windows' target platform. There's no problem with the code if I un-nest the callback.

我不确定通过参数传递的错误值是垃圾还是由于某些错误放置的内存地址(调用约定?).嵌套回调实际上是我一开始就不应该做的事情吗?或者这只是我不得不忍受的缺陷?

I'm not sure if the erroneous values passed with the parameters are just garbage or are due to some mis-placed memory addresses (calling convention?). Is nesting callbacks infact something that I should never do in the first place? Or is this just a defect that I have to live with?


响应大卫的回答,使用类型化回调声明了 EnumChildWindows 的相同代码.适用于 32 位:

(下面并没有真正测试 David 所说的话,因为我仍然使用@"运算符.它与运算符一起工作正常,但如果我删除它,它确实没有编译,除非我取消嵌套回调)

edit:
In response to David's answer, the same code having EnumChildWindows declared with a typed callback. Works fine with 32-bit:

(edit: The below does not really test what David says since I still used the '@' operator. It works fine with the operator, but if I remove it, it indeed does not compile unless I un-nest the callback)

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 回调,但是当将该函数的地址带入 过程类型 的变量并将其作为自定义比较器传递时,会发生同样的问题到 TList.Sort.


Actually this limitation is not specific to a Windows API callbacks, but the same problem happens when taking address of that function into a variable of procedural type and passing it, for example, as a custom comparator to 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 = 一些随机值;

It works as expected when compiled as 32-bit, but fails with Access Violation when compiled for Win64. For 64-bit version in function compare, s = nil and i2 = some random value;

如果在 btn1Click 函数之外提取 compare 函数,即使对于 Win64 目标,它也能按预期工作.

It also works as expected even for Win64 target, if one extracts compare function outside of btn1Click function.

推荐答案

该语言从未正式支持此技巧,由于 32 位编译器的实现细节,您迄今为止一直在使用它.文档 很清楚:

This trick was never officially supported by the language and you have been getting away with it to date due to the implementation specifics of the 32 bit compiler. The documentation is clear:

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

Nested procedures and functions (routines declared within other routines) cannot be used as procedural values.

如果我没记错的话,一个额外的、隐藏的参数被传递给嵌套函数,并带有指向封闭堆栈帧的指针.如果没有对封闭环境进行引用,则在 32 位代码中将省略此项.在 64 位代码中,总是传递额外的参数.

If I recall correctly, an extra, hidden, parameter is passed to nested functions with the pointer to the enclosing stack frame. This is omitted in 32 bit code if no reference is made to the enclosing environment. In 64 bit code the extra parameter is always passed.

当然,问题的很大一部分是 Windows 单元使用非类型化过程类型作为其回调参数.如果使用类型化过程,编译器可能会拒绝您的代码.事实上,我认为这是有理由相信你使用的技巧从来都不合法的.对于类型化回调,永远无法使用嵌套过程,即使在 32 位编译器中也是如此.

Of course a big part of the problem is that the Windows unit uses untyped procedure types for its callback parameters. If typed procedures were used the compiler could reject your code. In fact I view this as justification for the belief that the trick you used was never legal. With typed callbacks a nested procedure can never be used, even in the 32 bit compiler.

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

Anyway, the bottom line is that you cannot pass a nested function as parameter to another function in the 64 bit compiler.

这篇关于为什么不能在 64 位 Delphi 中获取嵌套本地函数的地址?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆