17

なので。関連する質問を閉じてから-以下にさらに例を追加しました。

以下の単純なコード(トップレベルの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」ターゲットプラットフォームで失敗する場所を示すためにを挿入しました。コールバックのネストを解除しても、コードに問題はありません。

パラメータで渡された誤った値が単なるゴミなのか、メモリアドレスの配置ミスによるものなのかわかりません(呼び出し規約?)。ネストされたコールバックは、そもそも絶対にやるべきではないことを実際に行っているのでしょうか。それとも、これは私が生きなければならない単なる欠陥ですか?

編集:
Davidの答えに応えて、同じコードがEnumChildWindows型付きコールバックで宣言しました。32ビットで正常に 動作します:(

編集:「@」演算子をまだ使用しているため、以下はDavidの言うことを実際にはテストしません。演算子では正常に動作しますが、削除すると、 -コールバックをネストします)

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;

Access Violation32ビットとしてコンパイルすると期待どおりに動作しますが、Win64用にコンパイルすると失敗します。関数内の64ビットバージョンの場合compares = nil およびi2=ランダムな値。

compareまた、関数の外で関数を抽出すると、Win64ターゲットでも期待どおりに機能しbtn1Clickます。

4

1 に答える 1

21

このトリックは、言語によって公式にサポートされることはなく、32ビットコンパイラの実装の詳細により、これまでは問題を解決してきました。ドキュメントは明確です:

ネストされたプロシージャと関数(他のルーチン内で宣言されたルーチン)は、プロシージャ値として使用できません。

正しく思い出せば、余分な非表示のパラメーターが、囲んでいるスタックフレームへのポインターとともにネストされた関数に渡されます。囲んでいる環境が参照されていない場合、これは32ビットコードでは省略されます。64ビットコードでは、追加のパラメータが常に渡されます。

もちろん、問題の大部分は、Windowsユニットがコールバックパラメータに型指定されていないプロシージャタイプを使用することです。型付きプロシージャが使用された場合、コンパイラはコードを拒否する可能性があります。実際、私はこれを、あなたが使用したトリックは決して合法ではなかったという信念の正当化と見なしています。型付きコールバックでは、32ビットコンパイラであっても、ネストされたプロシージャを使用することはできません。

とにかく、肝心なのは、ネストされた関数をパラメーターとして64ビットコンパイラの別の関数に渡すことはできないということです。

于 2012-04-15T14:25:20.433 に答える