14

我有一个用 WPF 编写的新应用程序,它需要支持一个旧 API,允许它接收已发布到隐藏窗口的消息。通常,另一个应用程序使用 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 Forms类......不需要所有那些win32调用,你只需要手动解析WndProc消息......就是全部。

2)您可以导入 System.Windows.Forms 命名空间并将其与 WPF 一起使用,我相信只要您不将过多的 windows 窗体交织到您的 WPF 应用程序中,我相信不会有任何问题。您只想实例化您的自定义隐藏表单以接收消息,对吗?

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 风类;将定义放在类中,而不是函数中,崩溃将得到修复。

于 2013-01-04T18:39:55.067 回答