从 C# 中的线程子类化窗口
我正在创建一个寻找窗口的线程。当它找到窗口时,它会覆盖其 windowproc,并处理 WM_COMMAND 和 WM_CLOSE。
下面是查找窗口并对其进行子类化的代码:
public void DetectFileDialogProc()
{
Window fileDialog = null;
// try to find the dialog twice, with a delay of 500 ms each time
for (int attempts = 0; fileDialog == null && attempts < 2; attempts++)
{
// FindDialogs enumerates all windows of class #32770 via an EnumWindowProc
foreach (Window wnd in FindDialogs(500))
{
IntPtr parent = NativeMethods.User32.GetParent(wnd.Handle);
if (parent != IntPtr.Zero)
{
// we're looking for a dialog whose parent is a dialog as well
Window parentWindow = new Window(parent);
if (parentWindow.ClassName == NativeMethods.SystemWindowClasses.Dialog)
{
fileDialog = wnd;
break;
}
}
}
}
// if we found the dialog
if (fileDialog != null)
{
OldWinProc = NativeMethods.User32.GetWindowLong(fileDialog.Handle, NativeMethods.GWL_WNDPROC);
NativeMethods.User32.SetWindowLong(fileDialog.Handle, NativeMethods.GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(new WindowProc(WndProc)).ToInt32());
}
}
以及 windowproc:
public IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
lock (this)
{
if (!handled)
{
if (msg == NativeMethods.WM_COMMAND || msg == NativeMethods.WM_CLOSE)
{
// adding to a list. i never access the window via the hwnd from this list, i just treat it as a number
_addDescriptor(hWnd);
handled = true;
}
}
}
return NativeMethods.User32.CallWindowProc(OldWinProc, hWnd, msg, wParam, lParam);
}
这在正常情况下都运行良好。但我看到了两个不良行为实例(按严重程度排序):
如果我没有在一分钟左右关闭对话框,应用程序就会崩溃。这是因为线程正在被垃圾收集吗?这是有道理的,因为 GC 可以判断线程已完成?如果是这种情况(我不知道是这样),只要对话框存在,我怎样才能使线程保持不变?
如果我立即使用“X”按钮(WM_CLOSE)关闭对话框,应用程序就会崩溃。我相信它在 windowproc 中崩溃了,但我无法在那里设置断点。我收到 AccessViolationException,该异常显示“尝试读取或写入受保护的内存。这通常表明其他内存已损坏。”这是一个竞争条件,但我不知道。仅供参考,在处理命令后,我一直在重置旧的 windowproc,但崩溃的频率更高!
关于如何解决这些问题有什么想法吗?
I'm creating a thread that looks for a window. When it finds the window, it overrides its windowproc, and handles WM_COMMAND and WM_CLOSE.
Here's the code that looks for the window and subclasses it:
public void DetectFileDialogProc()
{
Window fileDialog = null;
// try to find the dialog twice, with a delay of 500 ms each time
for (int attempts = 0; fileDialog == null && attempts < 2; attempts++)
{
// FindDialogs enumerates all windows of class #32770 via an EnumWindowProc
foreach (Window wnd in FindDialogs(500))
{
IntPtr parent = NativeMethods.User32.GetParent(wnd.Handle);
if (parent != IntPtr.Zero)
{
// we're looking for a dialog whose parent is a dialog as well
Window parentWindow = new Window(parent);
if (parentWindow.ClassName == NativeMethods.SystemWindowClasses.Dialog)
{
fileDialog = wnd;
break;
}
}
}
}
// if we found the dialog
if (fileDialog != null)
{
OldWinProc = NativeMethods.User32.GetWindowLong(fileDialog.Handle, NativeMethods.GWL_WNDPROC);
NativeMethods.User32.SetWindowLong(fileDialog.Handle, NativeMethods.GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(new WindowProc(WndProc)).ToInt32());
}
}
And the windowproc:
public IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
lock (this)
{
if (!handled)
{
if (msg == NativeMethods.WM_COMMAND || msg == NativeMethods.WM_CLOSE)
{
// adding to a list. i never access the window via the hwnd from this list, i just treat it as a number
_addDescriptor(hWnd);
handled = true;
}
}
}
return NativeMethods.User32.CallWindowProc(OldWinProc, hWnd, msg, wParam, lParam);
}
This all works well under normal conditions. But I am seeing two instances of bad behavior in order of badness:
If I do not close the dialog within a minute or so, the app crashes. Is this because the thread is getting garbage collected? This would kind of make sense, as far as GC can tell the thread is done? If this is the case, (and I don't know that it is), how can I make the thread stay around as long as the dialog is around?
If I immediately close the dialog with the 'X' button (WM_CLOSE) the app crashes. I believe its crashing in the windowproc, but I can't get a breakpoint in there. I'm getting an AccessViolationException, The exception says "Attempted to read or write protected memory. This is often an indication that other memory is corrupt." Its a race condition, but of what I don't know. FYI, I had been reseting the old windowproc once I processed the commands, but that was crashing even more often!
Any ideas on how I can solve these issues?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我可以观察到两点......
DetectFileDialogProc
中,您将wnd
与 null 进行比较,这是一个IntPtr
类型 yes ?如果是这样,比较检查应该是if (wnd > IntPtr.Zero){ .... }
WndProc
中,您正在使用
变量这是一件坏事......你应该做这样的事情lock
的 thisprivate readonly object objLock = new object();
和在您的WndProc
中使用此lock (objLock){....}
并查看是否可以解决问题...
Two points of observation that I can make....
DetectFileDialogProc
, you are comparingwnd
to null, that is anIntPtr
type yes? if so, that check for the comparison should beif (wnd > IntPtr.Zero){ .... }
WndProc
, you are using thethis
variable for thelock
which is a bad thing to do...you should do something like thisprivate readonly object objLock = new object();
and within yourWndProc
use thislock (objLock){....}
and see if that resolves the issue....
最后想出了一个解决方案,从不同的角度解决问题。我能够使用 SetWinEventHook 和选项 WINEVENT_OUTOFCONTEXT 在托管代码中设置系统范围的挂钩,该选项令人惊奇地具有以下属性:回调函数未映射到生成事件的进程的地址空间。
我捕获事件 EVENT_SYSTEM_DIALOGSTART 以在创建对话框时接收通知,并在对话框被销毁时捕获 EVENT_SYSTEM_DIALOGEND。
Finally came up with a solution, attacking the problem from a different angle. I was able to set a system-wide hook in managed code using SetWinEventHook, and the option WINEVENT_OUTOFCONTEXT, which amazingly has the property: The callback function is not mapped into the address space of the process that generates the event.
I trap the event EVENT_SYSTEM_DIALOGSTART to receive notifications whenever a dialog is created, and EVENT_SYSTEM_DIALOGEND when its destroyed.