我有一个用WPF编写的新应用程序需要支持一个旧的API,它允许它接收一个已经发布到隐藏窗口的消息。 通常,另一个应用程序使用FindWindow来使用自定义窗口类的名称来标识隐藏窗口。
1)我假设实现一个自定义的窗口类,我需要使用老派的win32调用?
我的旧的c + +应用程序使用RegisterClass和CreateWindow使最简单的可能不可见的窗口。
我相信我应该能够在c#中完成相同的工作。 我不希望我的项目必须编译任何非托pipe代码。
我已经尝试从System.Windows.Interop.HwndHostinheritance并使用System.Runtime.InteropServices.DllImport来引入上述API方法。
这样做,我可以成功地举办一个标准的Win32窗口,如WPF内的“列表框”。 但是,当我为我的自定义窗口调用CreateWindowEx时,它总是返回null。
我的RegisterClass调用成功,但我不知道我应该设置WNDCLASS.lpfnWndProc成员。
2)有谁知道如何成功地做到这一点?
记录我终于得到这个工作。 发现我遇到的困难是串串问题。 我必须在导入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; }
1)你可以继承一个正常的Windows窗体类…不需要所有这些win32调用,你只需要手动解析WndProc消息…就这样了。
2)您可以导入System.Windows.Forms命名空间,并将其与WPF一起使用,只要您不将太多的窗体交织到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消息对你来说不会是一个问题…
我想评论一下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的实例应该保存在一个成员变量中,直到窗口被销毁。
WNDCLASS wind_class; 把定义放在类中,而不是函数中,崩溃就会被修复。