我有一个
TextBox
,无法用新类重新定义,以便我可以过滤WndProc
中的一些消息。所以我必须使用win32函数SetWindowLong
将Window proc
的默认TextBox
替换为我自己的Window Proc
。所以我可以过滤其中的一些消息Window proc
。我已经更换成功了。消息可以在我的Window proc
中过滤。
但是,由于不一致的异常
InvalidOperationException
,它并不完整(这表示我的文本框是从创建它的线程以外的线程访问的)。奇怪的是,异常突出显示了设计者自动创建的表单的重写受保护方法 base.Dispose(disposing);
中的行 Dispose()
。
这是我替换默认窗口过程的代码:
[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
的默认窗口过程,它不能被继承或属于另一个应用程序。但上面的代码是使用标准 .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()
[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 是通常的选择。
我建议查看库的源代码并尝试确定是否发生任何多线程。如果您无法访问源代码,您始终可以使用
ILSpy,它可以反编译以及调试已编译的程序集。
Marshal.GetFunctionPointerForDelegate(new MyWndProc(MyWndProcFunc))
当垃圾收集器清理托管委托 (new MyWndProc
) 时,蹦床将被销毁,但您没有保存该委托对象的句柄,因此它立即符合清理条件。这里不能使用临时表达式,必须将
new MyWndProc(...)
保存在变量中,并且必须使用该变量来管理委托的生命周期,这也是
GetFunctionPointerForDelegate
生成的非托管 shim/trampoline 的生命周期.