Warum kann in 64-Bit-Delphi keine Adresse an eine verschachtelte lokale Funktion übergeben werden?
WIE. seit dem Schließen verwandter Fragen - weitere Beispiele unten hinzugefügt.
Der folgende einfache Code (der ein Fenster der obersten Ebene findet und dessen untergeordnete Elemente auflistet) funktioniert auf einer Zielplattform mit 32-Bit-Windows einwandfrei. Auch mit früheren Delphi-Versionen gibt es keine Probleme:
<code>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; </code>
Ich habe eine eingefügtAssert
um anzuzeigen, wo ein Fehler mit einer 64-Bit-Windows-Zielplattform auftritt. Es gibt kein Problem mit dem Code, wenn ichNest auflösen der Rückruf.
Ich bin nicht sicher, ob die mit den Parametern übergebenen fehlerhaften Werte nur Müll sind oder auf falsch platzierte Speicheradressen zurückzuführen sind (Aufrufkonvention?). Ist das Verschachteln von Rückrufen etwas, was ich niemals tun sollte? Oder ist das nur ein Defekt, mit dem ich leben muss?
bearbeiten:
Als Antwort auf Davids Antwort hatte derselbe CodeEnumChildWindows
deklariert mit einem getippten Rückruf. Funktioniert gut mit 32-Bit:
(edit: Das Folgende testet nicht wirklich, was David sagt, da ich immer noch den '@' Operator verwendet habe. Es funktioniert gut mit dem Operator, aber wenn ich es entferne, wird es in der Tat nicht kompiliert, es sei denn, ich hebe die Verschachtelung des Rückrufs auf)
<code>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; </code>
Tatsächlich ist diese Einschränkung nicht spezifisch für Windows-API-Rückrufe, aber das gleiche Problem tritt auf, wenn die Adresse dieser Funktion in eine Variable von übernommen wirdprocedural type
und übergeben Sie es beispielsweise als benutzerdefinierten Komparator anTList.Sort
.
http://docwiki.embarcadero.com/RADStudio/XE4/de/Procedural_Types
<code>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; </code>
Es funktioniert wie erwartet, wenn es als 32-Bit-Version kompiliert wird, schlägt jedoch mit fehlAccess Violation
wenn für Win64 kompiliert. Für 64-Bit-Version in Funktioncompare
, s = nil
undi2
= ein zufälliger Wert;
Es funktioniert auch wie erwartet, auch für Win64-Ziel, wenn man extrahiertcompare
Funktion außerhalb vonbtn1Click
Funktion.