Perché non è ansible accedere a una funzione locale nidificata in Delphi a 64 bit?

COME. dalla chiusura di domande correlate – altri esempi aggiunti di seguito.

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

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 fallisce con una piattaforma di destinazione ’64 -bit Windows ‘. Non c’è alcun problema con il codice se annullo il callback.

Non sono sicuro che i valori errati trasmessi 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?

modificare:
In risposta alla risposta di David, lo stesso codice con 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 usato l’operatore ‘@’. Funziona perfettamente con l’operatore, ma se lo rimuovo, in realtà non viene compilato a meno che non annulli la richiamata)

 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; 

In realtà questa limitazione non è specifica per i richiami dell’API di Windows, ma lo stesso problema si verifica quando si prende l’indirizzo di quella funzione in una variabile di procedural type e passandola, 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 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 funzione di compare al di fuori della funzione btn1Click .

Questo trucco non è mai stato ufficialmente supportato dalla lingua e fino ad ora è stato superato a causa delle specifiche di implementazione del compilatore a 32 bit. La documentazione è chiara:

Le procedure e le funzioni annidate (routine dichiarate all’interno di altre routine) non possono essere utilizzate come valori procedurali.

Se ricordo correttamente, un parametro extra nascosto viene passato alle funzioni annidate con il puntatore al frame dello 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 usa tipi di procedura non tipizzati 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 ansible utilizzare una procedura nidificata, anche nel compilatore a 32 bit.

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