【问题标题】:Custom WindowProc causes cross-thread exception?自定义WindowProc导致跨线程异常?
【发布时间】:2013-07-20 01:34:24
【问题描述】:

我有一个TextBox,它不能用新类重新定义,这样我就可以过滤WndProc 中的一些消息。所以我必须使用win32函数SetWindowLongTextBox的默认Window proc替换为我自己的Window Proc。所以我可以过滤该Window proc 中的一些消息。我已经成功完成了替换。可以在我的Window proc 中过滤消息。但是它并不完整,因为不一致的异常InvalidOperationException(这表示我的文本框是从创建它的线程之外的线程访问的)。奇怪的是,我的表单由设计师自动创建的被覆盖的受保护方法Dispose() 中的异常突出显示了base.Dispose(disposing); 行。

这是我替换为默认窗口过程的代码:

[DllImport("user32")]
private static extern IntPtr SetWindowLong(IntPtr hwnd, int nIndex, IntPtr proc);
[DllImport("user32")]
private static extern int CallWindowProc(IntPtr proc, IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
private delegate int MyWndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
public int MyWndProcFunc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
{
   //Call the default window proc to test
   //However even this can cause the exception after some keystrokes or mouse selection.
   return CallWindowProc(defProc, hwnd, msg, wParam, lParam);
}
IntPtr defProc;
public Form1(){
  InitializeComponent();
  Load += (s,e) => {
     defProc = SetWindowLong(myTextBox.Handle, -4, Marshal.GetFunctionPointerForDelegate(new MyWndProc(MyWndProcFunc)));//GWL_WNDPROC = -4
  };
}

表单开始正常,我可以在TextBox 中输入一些字符,但是继续输入或尝试使用鼠标选择文本...可能会引发我上面提到的异常。我没有找到任何有关此问题的文档。如果myTextBox.InvokeRequired = true;,我也尝试使用Invoke 在我自己的MyWndProcFunc(...) 中调用CallWindowProc(...),但没有区别。

你能深入研究这个问题来帮助我吗?使用我发布的代码可以轻松重现该问题。谢谢!

更新

我想明确一点,我的目的是要替换不能被继承或属于另一个应用程序的TextBox 的默认窗口进程。但是上面的代码是用标准的 .NET TextBox 测试的。这是在我的项目中应用之前测试的第一步。 这是堆栈跟踪:

  at System.Windows.Forms.Control.get_Handle()
  at System.Windows.Forms.TextBox.ResetAutoComplete(Boolean force)
  at System.Windows.Forms.TextBox.Dispose(Boolean disposing)
  at System.ComponentModel.Component.Dispose()
  at System.Windows.Forms.Control.Dispose(Boolean disposing)
  at System.Windows.Forms.ContainerControl.Dispose(Boolean disposing)
  at System.Windows.Forms.Form.Dispose(Boolean disposing)
  at WindowsFormsApplication1.Form1.Dispose(Boolean disposing) in C:\Users\iec\AppData\Local\Temporary Projects\WindowsFormsApplication1\Form1.Designer.cs:line 20
  at System.ComponentModel.Component.Dispose()
  at System.Windows.Forms.ApplicationContext.Dispose(Boolean disposing)
  at System.Windows.Forms.Application.ThreadContext.DisposeThreadWindows()

【问题讨论】:

  • 我已经在 Windows Vista 和 7、32 位和 64 位上进行了尝试,但到目前为止我无法重现该错误。你能发布堆栈跟踪吗?
  • @cokeman19 你的意思是你成功运行了代码吗?太棒了。我正在使用 Windows 7 - Ultimate 64 位。我更新了堆栈跟踪,请参阅。
  • @cokeman19 顺便说一句,请仔细阅读我的问题,您必须尝试输入足够长的字符才能引发异常。使用鼠标选择文本是引发异常的最快方法。谢谢!
  • 是的,它对我来说运行成功。我同时单击并输入文本框,但无济于事。看起来您的表单肯定正在被处理。对我来说,这说明某处发生了异常。几个想法。 1)打开VS中的选项以停止所有抛出的异常。 2) 在您的 SetWindowLong 声明中,将您的 IntPtr 参数更改为 HandleRef。
  • @cokeman19 奇怪的是它对你有用。我尝试将IntPtr 替换为HandleRef,如下所示:HandleRef hwnd = new HandleRef(myFormInstance, IntPtr_handle);,但它仍然不起作用。

标签: c# winforms winapi pinvoke


【解决方案1】:
    [DllImport("user32")]
    private static extern int CallWindowProc(...)

至少有两个原因没有人可以从您的示例代码中获得重现。只有设置 TextBox.AutoCompleteMode 属性时,才会发生调用堆栈中显示的崩溃。并且代码中的错误只会在您将程序作为 64 位进程运行时才字节,大多数 SO 用户将使用 x86 的默认平台目标设置。

您对 CallWindowProc(和 MyWndProcFunc)的声明是错误的,返回值类型是 IntPtr,而不是 int。这可能会在 64 位模式下导致许多奇怪的问题,尽管句柄所有者测试失败不会出现在我的列表顶部。

不要使用 pinvoke,因为永远存在此类细微错误的风险,更安全的方法是从 NativeWindow 派生您自己的类:

    private class MyTextBoxWindow : NativeWindow {
        protected override void WndProc(ref Message m) {
            // Customizations here
            //...
            base.WndProc(ref m);
        }
    }

并在您的 Load 事件处理程序中使用其 AssignHandle() 方法。当您收到 WM_NCDESTROY 消息时,您应该调用 ReleaseHandle()。

当编辑控件由另一个进程拥有时,不要不要尝试这样做。窗口过程必须在同一个进程中。这需要将 DLL 注入进程,您不能在 C# 中执行此操作,因为进程不会加载 CLR 来执行托管代码。本机代码是必需的,C 是通常的选择。

【讨论】:

  • 非常感谢,现在可以使用了。首先,我将int 更改为IntPtr,但它不起作用。这里的重点是应用程序目标是 x64,更改为 x86 使其工作。事实上,我的文本框的AutoCompleteModeNone。您对如何使其作为 x64 应用程序工作有任何想法吗?关于NativeWindow,我没有找到任何Attach()Detach() 方法,我也在Object Browser 中搜索过,没有找到任何东西。你能告诉我如何找到/获得对它们的访问权限吗?
  • 那太糟糕了,内存中的包装类太多了。我纠正了它。不,如果我不能让你的重现程序轰炸,我不知道如何让它在 64 位模式下工作。没有人可以。
  • 您的意思是即使应用程序目标是 x64,您也无法重现我的问题?我仍然对从NativeWindow 派生的类感到困惑,它是否用于替换我从System.Windows.Forms.Form 继承的表单?如果没有,我不知道如何使用它,谢谢。现在已经很晚了,我要睡觉了。 :)
【解决方案2】:

如果此问题与库中的文本框隔离,那么似乎该库中的某些内容正在第二个线程上发生,据我推测,该线程正在引发异常并最终触发处置。

我建议查看库的源代码并尝试确定是否发生了多线程。如果您无权访问源代码,则始终可以使用ILSpy,它可以反编译以及调试已编译的程序集。

【讨论】:

  • 但是,我刚刚在普通的TextBox 上进行了测试,如果测试成功,我将申请实际的TextBox 并在我的项目中使用需要挂钩的技术TextBox 在另一个应用程序中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多