Почему нельзя взять адрес во вложенную локальную функцию в 64-битной версии Delphi?

AS. после закрытия связанных вопросов -ниже добавлено больше примеров.

Приведенный ниже простой код (, который находит окно Ie верхнего -уровня и перечисляет его дочерние элементы ), работает нормально с целевой платформой «32 -бит Windows». С более ранними версиями 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;


. Я вставил Assert, чтобы указать, где происходит сбой с целевой платформой «64 -бит Windows». Нет проблем с кодом, если я un -вложу обратный вызов.

Я не уверен, являются ли ошибочные значения, передаваемые с параметрами, просто мусором или они вызваны неправильным -расположением адресов памяти (соглашением о вызовах? ). Являются ли вложенные обратные вызовы чем-то, что я никогда не должен делать в первую очередь? Или это просто недостаток, с которым мне приходится жить?

редактировать:
В ответ на ответ Дэвида, тот же код с EnumChildWindowsобъявлен с типизированным обратным вызовом. Отлично работает с 32 -битами:

(редактировать :Приведенное ниже не проверяет то, что говорит Дэвид, поскольку я все еще использовал оператор '@'. С оператором работает нормально, но если его убрать,он действительно не компилируется, если я не -вложил обратный вызов )

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;

. На самом деле это ограничение не относится к обратным вызовам Windows API, но та же проблема возникает, когда адрес этой функции берется в переменную procedural typeи передается, например, в качестве пользовательского компаратора для TList.Sort.

http://docwiki.embarcadero.com/RADStudio/Rio/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;

Он работает должным образом при компиляции в 32 -бит, но не работает с Access Violationпри компиляции для Win64. Для 64-битной версии -в функции compare, s = nilи i2= некоторое случайное значение;

Это также работает должным образом даже для цели Win64, если извлечь функцию compareвне функции btn1Click.

17
задан Shaun Roselt 24 October 2019 в 12:32
поделиться