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

Почему не удается получить адрес для вложенной локальной функции в 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". Там нет проблем с кодом, если я разблокирую обратный вызов.

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

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

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

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/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;

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

Он также работает так же, как и для целевого объекта Win64, если вы извлекаете compare функцию вне функции btn1Click.

4b9b3361

Ответ 1

Этот трюк никогда официально не поддерживался языком, и вы ушли с ним на сегодняшний день из-за особенностей реализации 32-битного компилятора. Документация понятна:

Вложенные процедуры и функции (процедуры, объявленные в других подпрограммах) не могут использоваться как процедурные значения.

Если я правильно помню, дополнительный, скрытый параметр передается вложенным функциям с указателем на охватывающий стек стека. Это исключено в 32-битном коде, если в окружение нет ссылки. В 64-битном коде всегда передается дополнительный параметр.

Конечно, значительная часть проблемы заключается в том, что в модуле Windows используются нетипизированные типы процедур для параметров обратного вызова. Если были введены типизированные процедуры, компилятор мог отклонить ваш код. На самом деле я рассматриваю это как оправдание убеждения, что трюк, который вы использовали, никогда не был законным. С типизированными обратными вызовами вложенную процедуру нельзя использовать даже в 32-битном компиляторе.

В любом случае, нижняя строка заключается в том, что вы не можете передать вложенную функцию как параметр в другую функцию в 64-битном компиляторе.