Dlaczego nie można przenieść adresu do zagnieżdżonej funkcji lokalnej w 64-bitowym Delphi?
TAK JAK. od zamknięcia powiązanych pytań - więcej przykładów dodanych poniżej.
Poniższy prosty kod (który znajduje okno Ie najwyższego poziomu i wylicza jego dzieci) działa poprawnie z docelową platformą Windows '32-bitową. Nie ma problemu z wcześniejszymi wersjami Delphi:
<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>
DodałemAssert
aby wskazać, gdzie kończy się niepowodzeniem z platformą docelową systemu Windows w wersji 64-bitowej. Kod nie ma problemu, jeśli jaun-nest wywołanie zwrotne.
Nie jestem pewien, czy błędne wartości przekazane wraz z parametrami są po prostu niepotrzebne lub wynikają z niektórych źle umieszczonych adresów pamięci (konwencja wywołania?). Czy zagnieżdżanie wywołań zwrotnych jest czymś, czego nigdy nie powinienem robić? Czy jest to tylko wada, z którą muszę żyć?
edytować:
W odpowiedzi na odpowiedź Davida ten sam kod maEnumChildWindows
zadeklarowano przy użyciu typowego wywołania zwrotnego. Działa dobrze z 32-bitami:
(edytuj: Poniższe nie testuje tak naprawdę tego, co mówi David, ponieważ wciąż używałem operatora „@”. Działa dobrze z operatorem, ale jeśli go usunę, to rzeczywiście nie kompiluje, dopóki nie cofnę zagnieżdżenia wywołania zwrotnego)
<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>
Właściwie to ograniczenie nie jest specyficzne dla wywołań zwrotnych interfejsu API systemu Windows, ale ten sam problem występuje, gdy adres tej funkcji zmienia się na zmiennąprocedural type
i przekazując go, na przykład, jako niestandardowy komparatorTList.Sort
.
http://docwiki.embarcadero.com/RADStudio/XE4/en/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>
Działa zgodnie z oczekiwaniami, gdy jest skompilowany jako 32-bitowy, ale kończy się niepowodzeniemAccess Violation
po skompilowaniu dla Win64. Dla wersji 64-bitowej w funkcjicompare
, s = nil
ii2
= pewna losowa wartość;
Działa również zgodnie z oczekiwaniami, nawet w przypadku Win64, jeśli się rozpakujecompare
funkcja pozabtn1Click
funkcjonować.