2012-04-15 16 views
16

AS. desde el cierre de preguntas relacionadas - más ejemplos se agregan a continuación.¿Por qué no se puede llevar la dirección a una función local anidada en Delphi de 64 bits?

La continuación código simple (que encuentra una ventana de IE de nivel superior y enumera sus hijos) funciona bien con un '32 bits de Windows plataforma de destino. No hay ningún problema con las versiones anteriores de Delphi, así:

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 una Assert para indicar dónde se produce un error con un '64 bits de Windows plataforma de destino. No hay problema con el código si anula la devolución de llamada.

no estoy seguro de si los valores erróneos pasados ​​con los parámetros son sólo basura o se deben a algunas direcciones de memoria erróneas colocados convención de llamada (?). ¿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?

edición:
En respuesta a la respuesta de David, el mismo código que tiene EnumChildWindows declarada con una devolución de llamada con tipo. Funciona bien con 32 bits:

(edit: Lo que sigue 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 lo hace compilado a menos que un-nido de la devolución de llamada)

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ífico de una devoluciones de llamada API de Windows, pero el mismo problema ocurre cuando se toma la dirección de dicha función en una variable de procedural type y pasarlo, por ejemplo, como un comparador personalizado al 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 compila como de 32 bits, pero falla con Access Violation cuando se compila para Win64. Para la versión de 64 bits en función compare, s = nil y i2 = alguna valor aleatorio;

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

+0

Si el código no anidado es funcionalmente equivalente al anidado, entonces es un error de compilación o un problema de devolución de parámetros de devolución de llamada. Voto por este último, es posible que esté obteniendo daños en la pila ya que la convención de llamadas de 64 bits es diferente a las de 32 bits, por lo que quizás "stdcall" no sea lo que debería usar aquí. Intente eliminarlo y vea si sucede nuevamente. De lo contrario, las devoluciones de llamadas de anidación están perfectamente bien (al menos en la forma que se muestra aquí). – Thomas

+0

SÓLO HAY UNA convención de llamadas en Win64; Por lo tanto, cualquier convención de llamadas que especifique en el código se ignora en el modo de 64 bits. Sin embargo, su firma de función/procedimiento/dll-import podría estar equivocada, y eso podría corromper las cosas. Sin embargo, la imposibilidad de que Delphi implemente una devolución de llamada desde una función suena como un error del compilador. –

+0

@Thomas - La aserción falla nuevamente cuando elimino 'stdcall', supongo que el compilador Delphi simplemente lo ignora cuando apunta a 64 bits. –

Respuesta

20

Este truco no fue apoyada oficialmente por el lenguaje y que ha estado recibiendo lejos con él hasta la fecha debido a las implementaciones específicas del compilador de 32 bits. El documentation es claro:

Los procedimientos y funciones anidados (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 pila 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 utiliza tipos de procedimiento sin tipo para sus parámetros de devolución de llamada. Si se utilizaron procedimientos escritos, el compilador 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 anidado, incluso en el compilador 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 compilador de 64 bits.

+0

Veo su edición. No use @ en frente de la devolución de llamada. –

+0

Llámalo así: TypedEnumChildWindows (Wnd, EnumChildren, LongWord (@WndChild)); –

+0

¡Pero entonces no compilará! ... –

Cuestiones relacionadas