2012-04-15 10 views
16

AS. dalla chiusura di domande correlate - altri esempi aggiunti di seguito.Perché non è possibile prendere l'indirizzo di una funzione locale nidificata in Delphi a 64 bit?

Il seguente codice semplice (che trova una finestra di Ie di primo livello e enumera i suoi figli) funziona correttamente con una piattaforma di destinazione '32 -bit Windows '. Non c'è nessun problema con le versioni precedenti di Delphi così:

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; 


ho inserito un Assert per indicare dove non riesce con un '64 bit piattaforma di destinazione di Windows'. Non c'è alcun problema con il codice se io disattivi il il callback.

Non sono sicuro se i valori errati passati con i parametri siano solo inutili o siano dovuti ad alcuni indirizzi di memoria errati (convenzione di chiamata?). La nidificazione dei callback è qualcosa che non dovrei mai fare in primo luogo? O è solo un difetto con cui devo convivere?

edit:
In risposta alla risposta di David, lo stesso codice avendo EnumChildWindows dichiarato con un callback digitato. Funziona bene con 32-bit:

(modifica: Il sotto non verifica realmente ciò che David dice dal momento che ho ancora utilizzato l'operatore '@'. Funziona perfettamente con l'operatore, ma se lo rimuovo, in effetti non lo fa compilare meno che un-nido 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; 

realtà questa limitazione non è specifico di un callback API di Windows, ma lo stesso problema accade quando prende indirizzo di tale funzione in una variabile di procedural type e passandolo, ad esempio, come comparatore personalizzato 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; 

funziona come previsto quando compilato come a 32 bit, ma non riesce con Access Violation quando compilato per Win64. Per la versione a 64 bit nella funzione compare, s = nil e i2 = un valore casuale;

Funziona anche come previsto anche per target Win64, se si estrae la funzioneal di fuori della funzione btn1Click.

+0

Se il codice non annidato è funzionalmente equivalente a quello annidato, è un errore del compilatore o un problema di passaggio dei parametri di callback. Io voto per il secondo, si potrebbe ottenere il danneggiamento dello stack in quanto la convenzione di chiamata a 64 bit è diversa da quella a 32 bit, quindi forse "stdcall" non è quello che dovresti usare qui. Prova a rimuoverlo e vedi se succede di nuovo. Altrimenti, le callback di nidificazione sono perfette (almeno nel modo mostrato qui). – Thomas

+0

C'è solo una convenzione di chiamata in Win64; Quindi qualsiasi convenzione di chiamata specificata nel codice viene ignorata in modalità 64 bit. Tuttavia la funzione/procedura/firma di importazione dll potrebbe essere errata e ciò potrebbe corrompere le cose. Tuttavia l'impossibilità di avere Delphi implementare un callback da una funzione sembra un errore del compilatore. –

+0

@Thomas - L'asserzione fallisce di nuovo quando rimuovo 'stdcall', suppongo che il compilatore Delphi lo ignori solo quando viene indirizzato a 64 bit. –

risposta

20

Questo trucco non è mai stato ufficialmente supportato dal linguaggio e fino ad ora è stato superato a causa delle specifiche di implementazione del compilatore a 32 bit. Il documentation è chiaro:

procedure e funzioni (routine dichiarate all'interno altre routine) nidificate non possono essere utilizzati come valori procedurali.

Se ricordo correttamente, un parametro aggiuntivo nascosto viene passato alle funzioni annidate con il puntatore al frame di stack che lo racchiude. Questo viene omesso nel codice a 32 bit se non viene fatto alcun riferimento all'ambiente che lo racchiude. Nel codice a 64 bit il parametro aggiuntivo viene sempre passato.

Ovviamente gran parte del problema è che l'unità Windows utilizza tipi di procedura non tipizzata per i suoi parametri di callback. Se sono state utilizzate le procedure digitate, il compilatore potrebbe rifiutare il codice. In realtà considero questo come una giustificazione per la convinzione che il trucco che hai usato non sia mai stato legale. Con callback digitati non è mai possibile utilizzare una procedura nidificata, anche nel compilatore a 32 bit.

In ogni caso, la linea di fondo è che non è possibile passare una funzione nidificata come parametro ad un'altra funzione nel compilatore a 64 bit.

+0

Vedo la tua modifica. Non utilizzare @ prima di richiamare. –

+0

Chiamalo in questo modo: TypedEnumChildWindows (Wnd, EnumChildren, LongWord (@WndChild)); –

+0

Ma poi non verrà compilato! .. –

Problemi correlati