自定义WindowProc导致跨线程异常?

时间:2013-07-20 01:34:25

标签: c# winforms winapi pinvoke

我有一个TextBox无法使用新类重新定义,因此我可以在WndProc中过滤一些消息。因此,我必须使用win32函数SetWindowLongWindow proc的默认TextBox替换为我自己的Window Proc。所以我可以过滤Window proc中的一些消息。我成功完成了更换。可以在我的Window proc中过滤消息。但是由于异常InvalidOperationException不一致(它说我的文本框是从创建它以外的线程访问的),因此它不完整。奇怪的是异常突出显示了设计者自动创建的表单的重写保护方法base.Dispose(disposing);中的行Dispose()

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

[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中键入一些字符,但是继续键入或尝试使用鼠标选择文本...可以引发我上面提到的异常。我没有找到任何关于这个问题的文档。我还尝试使用Invoke在我自己CallWindowProc(...)中调用MyWndProcFunc(...) myTextBox.InvokeRequired = true;,但没有差异。

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

更新

我想说清楚,我的目的是要替换TextBox的默认窗口proc,它不能被继承或属于另一个应用程序。但是上面的代码是使用标准的.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()

2 个答案:

答案 0 :(得分:2)

    [DllImport("user32")]
    private static extern int CallWindowProc(...)

至少有两个原因,没有人可以从您的示例代码中获得repro。只有在设置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是通常的选择。

答案 1 :(得分:0)

如果这个问题被隔离到库中的文本框,那么该库中的某些内容似乎可能发生在第二个线程上,据我最好的猜测,它会抛出一个异常并最终触发dispose。

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