Custom WindowProc导致跨线程异常

本文关键字:线程 异常 WindowProc Custom | 更新日期: 2023-09-27 18:23:56

我有一个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中键入一些字符,但可以继续键入或尝试使用鼠标选择文本。。。可以提出我上面提到的例外情况。我找不到任何关于这个问题的文档。我也尝试过使用Invoke在我自己的MyWndProcFunc(...)中调用CallWindowProc(...),如果是myTextBox.InvokeRequired = true;,但没有区别。

你能深入研究这个问题来帮助我吗?用我发布的代码可以很容易地重现这个问题。谢谢

更新

我想明确一点,我的目的是替换不能继承或属于另一个应用程序的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()

Custom WindowProc导致跨线程异常

    [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是通常的选择。

如果这个问题与库中的文本框隔离,那么该库中的某些事情很可能发生在第二个线程上,据我猜测,这是在抛出异常并最终触发处置。

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