14

非表示のウィンドウに投稿されたメッセージを受信できるようにする古い API をサポートする必要がある WPF で記述された新しいアプリケーションがあります。通常、別のアプリケーションは FindWindow を使用して、そのカスタム ウィンドウ クラスの名前を使用して非表示のウィンドウを識別します。

1) 古い学校の win32 呼び出しを使用する必要があるカスタム ウィンドウ クラスを実装すると仮定しますか?

私の古い C++ アプリケーションでは、RegisterClass と CreateWindow を使用して、最も単純な非表示ウィンドウを作成していました。

私は、C#内ですべて同じことができるはずだと信じています。プロジェクトでアンマネージ コードをコンパイルする必要はありません。

System.Windows.Interop.HwndHost から継承し、System.Runtime.InteropServices.DllImport を使用して上記の API メソッドを取得しようとしました。

これを行うと、WPF 内の「リストボックス」などの標準の win32 ウィンドウを正常にホストできます。ただし、カスタム ウィンドウに対して CreateWindowEx を呼び出すと、常に null が返されます。

RegisterClass への呼び出しは成功しましたが、WNDCLASS.lpfnWndProc メンバーを何に設定すればよいかわかりません。

2)これをうまく行う方法を知っている人はいますか?

4

4 に答える 4

37

記録のために、私はついにこれを機能させました。私が抱えていた困難は、文字列のマーシャリングの問題にあることが判明しました。win32 関数のインポートでは、より正確にする必要がありました。

以下は、C# でカスタム ウィンドウ クラスを作成するコードです。カスタム ウィンドウ クラスに依存する古い API をサポートするのに役立ちます。

メッセージ ポンプがスレッドで実行されている限り、WPF または Winforms のいずれかで動作するはずです。

編集:コールバックをラップするデリゲートの初期のコレクションによる報告されたクラッシュを修正するために更新されました。デリゲートはメンバーとして保持され、デリゲートは関数ポインターとして明示的にマーシャリングされます。これにより問題が修正され、動作が理解しやすくなります。

class CustomWindow : IDisposable
{
    delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

    [System.Runtime.InteropServices.StructLayout(
        System.Runtime.InteropServices.LayoutKind.Sequential,
       CharSet = System.Runtime.InteropServices.CharSet.Unicode
    )]
    struct WNDCLASS
    {
        public uint style;
        public IntPtr lpfnWndProc;
        public int cbClsExtra;
        public int cbWndExtra;
        public IntPtr hInstance;
        public IntPtr hIcon;
        public IntPtr hCursor;
        public IntPtr hbrBackground;
        [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
        public string lpszMenuName;
        [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
        public string lpszClassName;
    }

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern System.UInt16 RegisterClassW(
        [System.Runtime.InteropServices.In] ref WNDCLASS lpWndClass
    );

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr CreateWindowExW(
       UInt32 dwExStyle,
       [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
       string lpClassName,
       [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)]
       string lpWindowName,
       UInt32 dwStyle,
       Int32 x,
       Int32 y,
       Int32 nWidth,
       Int32 nHeight,
       IntPtr hWndParent,
       IntPtr hMenu,
       IntPtr hInstance,
       IntPtr lpParam
    );

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern System.IntPtr DefWindowProcW(
        IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam
    );

    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern bool DestroyWindow(
        IntPtr hWnd
    );

    private const int ERROR_CLASS_ALREADY_EXISTS = 1410;

    private bool m_disposed;
    private IntPtr m_hwnd;

    public void Dispose() 
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) 
    {
        if (!m_disposed) {
            if (disposing) {
                // Dispose managed resources
            }

            // Dispose unmanaged resources
            if (m_hwnd != IntPtr.Zero) {
                DestroyWindow(m_hwnd);
                m_hwnd = IntPtr.Zero;
            }

        }
    }

    public CustomWindow(string class_name){

        if (class_name == null) throw new System.Exception("class_name is null");
        if (class_name == String.Empty) throw new System.Exception("class_name is empty");

        m_wnd_proc_delegate = CustomWndProc;

        // Create WNDCLASS
        WNDCLASS wind_class = new WNDCLASS();
        wind_class.lpszClassName = class_name;
        wind_class.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(m_wnd_proc_delegate);

        UInt16 class_atom = RegisterClassW(ref wind_class);

        int last_error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();

        if (class_atom == 0 && last_error != ERROR_CLASS_ALREADY_EXISTS) {
            throw new System.Exception("Could not register window class");
        }

        // Create window
        m_hwnd = CreateWindowExW(
            0,
            class_name,
            String.Empty,
            0,
            0,
            0,
            0,
            0,
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero
        );
    }

    private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) 
    {
        return DefWindowProcW(hWnd, msg, wParam, lParam);
    }

    private WndProc m_wnd_proc_delegate;
}
于 2008-09-26T09:30:44.033 に答える
1

morechilli の回答にコメントしたいと思います。

public CustomWindow(string class_name){

    if (class_name == null) throw new System.Exception("class_name is null");
    if (class_name == String.Empty) throw new System.Exception("class_name is empty");

    // Create WNDCLASS
    WNDCLASS wind_class = new WNDCLASS();
    wind_class.lpszClassName = class_name;
    wind_class.lpfnWndProc = CustomWndProc;

    UInt16 class_atom = RegisterClassW(ref wind_class);

    int last_error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();

    if (class_atom == 0 && last_error != ERROR_CLASS_ALREADY_EXISTS) {
        throw new System.Exception("Could not register window class");
    }

    // Create window
    m_hwnd = CreateWindowExW(
        0,
        class_name,
        String.Empty,
        0,
        0,
        0,
        0,
        0,
        IntPtr.Zero,
        IntPtr.Zero,
        IntPtr.Zero,
        IntPtr.Zero
    );
}

上記でコピーしたコンストラクターには、わずかなエラーがあります。WNDCLASS インスタンスは作成されていますが、保存されていません。最終的にガベージコレクションされます。ただし、WNDCLASS は WndProc デリゲートを保持します。これにより、WNDCLASS がガベージ コレクションされるとすぐにエラーが発生します。ウィンドウが破棄されるまで、WNDCLASS のインスタンスをメンバー変数に保持する必要があります。

于 2013-08-05T11:21:43.787 に答える
0

1)通常の Windows フォーム クラスをサブクラス化するだけで済みます... これらのすべての win32 呼び出しは必要ありません。WndProc メッセージを手動で解析するだけで済みます... すべてです。

2) System.Windows.Forms 名前空間をインポートして、WPF と一緒に使用できます。WPF アプリケーションに多くの Windows フォームが絡み合っていない限り、問題はないと思います。カスタムの非表示フォームをインスタンス化して、メッセージを受信したいだけですか?

WndProc サブクラス化の例:

protected override void WndProc(ref System.Windows.Forms.Message m)
{
   // *always* let the base class process the message
   base.WndProc(ref m);

   const int WM_NCHITTEST = 0x84;
   const int HTCAPTION = 2;
   const int HTCLIENT = 1;

   // if Windows is querying where the mouse is and the base form class said
   // it's on the client area, let's cheat and say it's on the title bar instead
   if ( m.Msg == WM_NCHITTEST && m.Result.ToInt32() == HTCLIENT )
      m.Result = new IntPtr(HTCAPTION);
}

RegisterClass とすべての Win32 呼び出しを既に知っているので、WndProc メッセージは問題にならないと思います...

于 2008-09-24T17:45:08.683 に答える
-1

WNDCLASS wind_class; 関数ではなくクラスに定義を置くと、クラッシュが修正されます。

于 2013-01-04T18:39:55.067 に答える