我有一个用WPF编写的新应用程序,它需要支持一个旧API,允许它接收已发布到隐藏窗口的消息。通常,另一个应用程序使用FindWindow使用其自定义窗口类的名称来标识隐藏窗口。
1)我假设要实现一个自定义窗口类,我需要使用旧学校的win32调用?
我的旧c ++应用程序使用RegisterClass和CreateWindow来创建最简单的隐形窗口。
我相信我应该能够在c#中做同样的事情。我不希望我的项目必须编译任何非托管代码。
我尝试从System.Windows.Interop.HwndHost继承并使用System.Runtime.InteropServices.DllImport来引入上述API方法。
这样做我可以成功托管标准的win32窗口,例如WPF中的“listbox”。 但是当我为自定义窗口调用CreateWindowEx时,它总是返回null。
我对RegisterClass的调用成功,但我不确定我应该设置什么 WNDCLASS.lpfnWndProc成员。
2)有没有人知道如何成功完成这项工作?
答案 0 :(得分:33)
为了记录,我终于让它工作了。 结果发现我遇到的困难是编组编组问题。 我必须更精确地导入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 :(得分: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的实例应保存在成员变量中,直到窗口被销毁。
答案 2 :(得分: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消息对你来说不是问题...
答案 3 :(得分:-1)
WNDCLASS wind_class; 将定义放在类中,而不是函数中,崩溃将被修复。