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ć.

questionAnswers(1)

yourAnswerToTheQuestion