2012-04-15 10 views
16

AS。関連する質問を締め切りました。64ビットDelphiでネストされたローカル関数にアドレスを指定できないのはなぜですか?

以下の単純なコード(最上位のIeウィンドウを見つけてその子を列挙する)は、'32ビットWindows 'ターゲットプラットフォームで動作します。私はそれが'64ビットWindowsのターゲットプラットフォームで失敗する場所を示すためにAssertを挿入した

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; 


:同様のDelphiの以前のバージョンに問題なしはありません。私がのネストを解除した場合、コードに問題はありませんコールバック。

パラメータで渡された誤った値がちょうどゴミであるのか、メモリのアドレスが誤って配置されているのか(呼び出し規約?)はわかりません。私が最初にやってはならないコールバックの本質的な入れ子ですか?それとも、これは私が生きなければならない欠点ですか?

編集:デビッドの答えを受けて
、型付きコールバックを使って宣言EnumChildWindowsを有する同一のコード。

(編集:私がまだ '@'演算子を使用して以来、Davidが言っていることは実際にはテストされません)それはオペレータとうまく動作しますが、実際にはこの制限は、Windows APIのコールバックに特有のものではなく、procedural typeの変数にその関数のアドレスを取得する場合、同じ問題が起こる

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; 

)I非巣ない限り、コールバックをコンパイルそれをたとえばカスタムコンパレータとしてに渡します。

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で失敗します。関数compareの64ビット版では、s = nili2 =いくつかのランダムな値。

btn1Click以外の機能がcompareの場合は、Win64ターゲットでも期待どおりに動作します。

+0

ネストされていないコードがネストされたコードと機能的に同等である場合、コンパイラのバグまたはコールバックパラメータの受け渡しの問題です。私は後者のために投票します.64ビットの呼び出し規約が32ビットの呼び出し規約と異なるため、スタックの破損が発生する可能性があります。したがって、おそらく "stdcall"はここで使用するものではありません。それを取り除き、再度起こるかどうか確認してください。さもなければ入れ子コールバックは(少なくともここに示された方法で)完全にうまくいきます。 – Thomas

+0

Win64には1つの呼び出し規約しかありません。 64ビットモードでは、コードで指定した呼び出し規約は無視されます。しかし、あなたの関数/プロシージャー/ dllインポートのシグニチャーは間違っているかもしれません。しかし、Delphiに関数からのコールバックを実装できないことは、コンパイラのバグのように聞こえます。 –

+0

@Thomas - 'stdcall'を削除すると、アサーションが再び失敗します.64ビットがターゲットになっているときには、Delphiコンパイラがそれを無視すると思います。 –

答えて

20

このトリックは、この言語では正式にはサポートされていませんでした。このテクニックは、32ビットコンパイラの実装の特質のためにこれまでになくなりました。 documentationはクリアされています。

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

正しく読み返しておけば、余分な隠されたパラメータが、囲みスタックフレームへのポインタを持つネストされた関数に渡されます。これは、囲む環境への参照がない場合、32ビットコードでは省略されます。 64ビットコードでは、余分なパラメータが常に渡されます。

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

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

+0

あなたの編集が表示されます。コールバックの前に@を使用しないでください。 –

+0

これを次のように呼び出します。TypedEnumChildWindows(Wnd、EnumChildren、LongWord(@WndChild)); –

+0

しかし、それはコンパイルされません!... –

関連する問題