Почему нельзя взять адрес для вложенной локальной функции в 64-битной Delphi?
КАК. после закрытия связанных вопросов - больше примеров добавлено ниже.
Приведенный ниже простой код (который находит окно Ie верхнего уровня и перечисляет его дочерние элементы) работает нормально с «32-битной Windows». целевая платформа. Нет проблем и с более ранними версиями 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>
Я вставилAssert
чтобы указать, где происходит сбой, в «64-битной Windows»; целевая платформа. Нет проблем с кодом, если яun-nest обратный звонок.
Я не уверен, являются ли ошибочные значения, передаваемые с параметрами, просто мусором или они вызваны неправильным размещением адресов памяти (соглашение о вызовах?). Действительно ли вложенные обратные вызовы влияют на то, что я никогда не должен делать? Или это просто дефект, с которым мне приходится жить?
edit:
В ответ на ответ Дэвида, тот же код, имеющийEnumChildWindows
объявлен с типизированным обратным вызовом. Работает нормально с 32-битным:
(edit: The below does not really test what David says since I still used the '@' operator. It works fine with the operator, but if I remove it, it indeed does not compile unless I un-nest the callback)
<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>
На самом деле это ограничение не относится к обратным вызовам Windows API, но такая же проблема возникает, когда адрес этой функции переносится в переменнуюprocedural type
и передать его, например, в качестве пользовательского компаратораTList.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>
Он работает, как и ожидалось, когда скомпилирован как 32-битный, но не сAccess Violation
когда скомпилировано для Win64. Для 64-битной версии в функцииcompare
, s = nil
а такжеi2
= некоторое случайное значение;
Он также работает, как и ожидалось, даже для Win64 target, если один извлекаетcompare
функционировать внеbtn1Click
функция.