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

查看:352
本文介绍了为什么不能在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?

编辑:

回应David的回答,具有使用类型回调声明的 EnumChildWindows 的相同代码。 32位工作正常:


(编辑:下面并没有真正测试大卫说的话,因为我仍然使用'@'操作符。 ,但是如果我删除它,它确实不会编译,除非我解嵌回调)

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/XE4/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位,但失败, Access针对Win64编译时违规。对于函数中的64位版本, s = nil i2 =一些随机值;

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;

它也可以预期即使对于Win64目标,如果一个提取比较 btn1Click 函数之外的函数。

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天全站免登陆