为什么不能在64位Delphi中获取嵌套本地函数的地址?
作为。自结束相关问题以来-下面添加了更多示例 下面的简单代码(查找顶级Ie窗口并枚举其子窗口)适用于“32位Windows”目标平台。早期版本的Delphi也没有问题:为什么不能在64位Delphi中获取嵌套本地函数的地址?,delphi,delegates,nested,delphi-xe2,32bit-64bit,Delphi,Delegates,Nested,Delphi Xe2,32bit 64bit,作为。自结束相关问题以来-下面添加了更多示例 下面的简单代码(查找顶级Ie窗口并枚举其子窗口)适用于“32位Windows”目标平台。早期版本的Delphi也没有问题: procedure TForm1.Button1Click(Sender: TObject); function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall; const Server = 'Internet Explorer_Server';
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;
实际上,此限制并不特定于Windows API回调,但当将该函数的地址放入
过程类型的变量中并将其作为自定义比较器传递给TList.Sort
时,也会出现相同的问题
当编译为32位时,它可以正常工作,但在为Win64编译时,由于访问冲突而失败。对于函数中的64位版本,比较,s=nil
和i2
=一些随机值
如果在btn1Click
函数之外提取compare
函数,即使对于Win64目标,它也能像预期的那样工作。这种技巧从未得到该语言的正式支持,而且由于32位编译器的实现细节,迄今为止您一直没有使用它。问题很清楚:
嵌套过程和函数(在其他例程中声明的例程)不能用作过程值
如果我没记错的话,一个额外的隐藏参数被传递给嵌套函数,指针指向封装的堆栈框架。如果未引用封闭环境,则在32位代码中会忽略此项。在64位代码中,始终传递额外的参数
当然,问题的很大一部分是Windows单元使用非类型化过程类型作为其回调参数。如果使用类型化过程,编译器可能会拒绝您的代码。事实上,我认为这是你所使用的伎俩永远都不合法的理由。对于类型化回调,嵌套过程永远不能使用,即使在32位编译器中也是如此
无论如何,底线是您不能将嵌套函数作为参数传递给64位编译器中的另一个函数。该语言从未正式支持此技巧,并且由于32位编译器的实现细节,您迄今为止一直没有使用它。问题很清楚:
嵌套过程和函数(在其他例程中声明的例程)不能用作过程值
如果我没记错的话,一个额外的隐藏参数被传递给嵌套函数,指针指向封装的堆栈框架。如果未引用封闭环境,则在32位代码中会忽略此项。在64位代码中,始终传递额外的参数
当然,问题的很大一部分是Windows单元使用非类型化过程类型作为其回调参数。如果使用类型化过程,编译器可能会拒绝您的代码。事实上,我认为这是你所使用的伎俩永远都不合法的理由。对于类型化回调,嵌套过程永远不能使用,即使在32位编译器中也是如此
无论如何,底线是不能将嵌套函数作为参数传递给64位编译器中的另一个函数。如果未嵌套的代码在功能上等同于嵌套的代码,则可能是编译器错误或回调参数传递问题。我赞成后者,因为64位调用约定不同于32位调用约定,所以可能会出现堆栈损坏,所以这里不应该使用“stdcall”。尝试移除它,看看是否再次发生。否则嵌套回调就完全可以了(至少以这里显示的方式);因此,您在代码中指定的任何调用约定在64位模式下都会被忽略。但是,您的函数/过程/dll导入签名可能是错误的,这可能会损坏某些东西。然而,无法让Delphi实现来自函数的回调听起来确实像是编译器的错误。@Thomas-当我删除“stdcall”时,断言再次失败,我想Delphi编译器只是在64位为目标时忽略了它。请注意,您正在将指向HWND的指针强制转换为一个4字节的长字。您应该在x64上使用NativeInt,或者只传递已经是8字节无符号整数(NativeUInt)的HWND。我们发现这不仅是winapi回调的问题,也是传递本地函数/过程作为对我们自己的Delphi代码的回调的问题,特别是当涉及多线程时。我记不清具体细节,但它确实与编译器如何处理本地方法、插入/不插入的内容以及触发回调时堆栈上可用的内容有关。如果未嵌套的代码在功能上与嵌套的代码等效,这要么是编译器错误,要么是回调参数传递问题。我赞成后者,因为64位调用约定不同于32位调用约定,所以可能会出现堆栈损坏,所以这里不应该使用“stdcall”。尝试移除它,看看是否再次发生。否则嵌套回调就完全可以了(至少以这里显示的方式);因此,您在代码中指定的任何调用约定在64位模式下都会被忽略。但是,您的函数/过程/dll导入签名可能是错误的,这可能会损坏某些东西。然而,无法让Delphi实现来自函数的回调听起来确实像是编译器的错误。@Thomas-当我删除“stdcall”时,断言再次失败,我想Delphi编译器只是在64位为目标时忽略了它。请注意,您正在将指向HWND的指针强制转换为一个4字节的长字。您应该在x64上使用NativeInt,或者只传递已经是8字节无符号整数(NativeUInt)的HWND。我们发现这不仅是winapi回调的问题,也是传递本地函数/过程作为对我们自己的Delphi代码的回调的问题,特别是当涉及多线程时。我
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;
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;