¿Por qué no puede llevar la dirección a una función local anidada en Delphi de 64 bits?

COMO. desde el cierre de preguntas relacionadas – más ejemplos se agregan a continuación.

El código simple a continuación (que encuentra una ventana Ie de nivel superior y enumera sus hijos) funciona bien con una plataforma de destino ’32 bits de Windows ‘. No hay problema con las versiones anteriores de Delphi también:

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; 

He insertado un Assert para indicar dónde falla con una plataforma de destino ’64 bits de Windows ‘. No hay problema con el código si anulo la callback.

No estoy seguro de si los valores erróneos pasados ​​con los parámetros son simplemente basura o se deben a algunas direcciones de memoria mal colocadas (convención de llamadas?). ¿Las devoluciones de llamadas de anidación son algo que nunca debería hacer en primer lugar? ¿O es solo un defecto con el que tengo que vivir?

editar:
En respuesta a la respuesta de David, el mismo código que tiene EnumChildWindows declaró con una callback EnumChildWindows . Funciona bien con 32 bits:

(editar: Lo siguiente no prueba realmente lo que David dice ya que todavía uso el operador ‘@’. Funciona bien con el operador, pero si lo elimino, de hecho no comstack a menos que anule la 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; 

En realidad, esta limitación no es específica de las devoluciones de llamada de la API de Windows, pero ocurre el mismo problema al tomar la dirección de esa función en una variable de procedural type y pasarla, por ejemplo, como un comparador personalizado a 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; 

Funciona como se esperaba cuando se comstack como de 32 bits, pero falla con la Access Violation cuando se comstack para Win64. Para la versión de 64 bits en compare funciones, s = nil e i2 = algún valor aleatorio;

También funciona como se espera, incluso para el objective Win64, si se extrae una función de compare fuera de la función btn1Click .

    Este truco nunca fue respaldado oficialmente por el lenguaje y usted se ha salido con la suya hasta la fecha debido a los detalles de implementación del comstackdor de 32 bits. La documentación es clara:

    Los procedimientos y funciones nesteds (rutinas declaradas dentro de otras rutinas) no se pueden usar como valores de procedimiento.

    Si recuerdo correctamente, un parámetro extra, oculto, se pasa a las funciones anidadas con el puntero al marco de la stack que lo rodea. Esto se omite en el código de 32 bits si no se hace referencia al entorno circundante. En el código de 64 bits, el parámetro adicional siempre se pasa.

    Por supuesto, una gran parte del problema es que la unidad de Windows usa tipos de procedimiento sin tipo para sus parámetros de callback. Si se utilizaron procedimientos escritos, el comstackdor podría rechazar su código. De hecho, veo esto como una justificación para la creencia de que el truco que usaste nunca fue legal. Con las devoluciones de llamada tipeadas nunca se puede usar un procedimiento nested, incluso en el comstackdor de 32 bits.

    De todos modos, la conclusión es que no se puede pasar una función anidada como parámetro a otra función en el comstackdor de 64 bits.