SendMessage 的当前声明在 PInvoke.net 上是:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern IntPtr SendMessage(HandleRef hWnd, uint Msg,
IntPtr wParam, IntPtr lParam);
注意: hWnd不再是IntPtr,而已被HandleRef所取代。对于此变化的解释如下:
你可以用“IntPtr”替换“HandleRef”。但是这样做存在风险——它可能会在运行时释放窗口句柄,从而导致各种问题!有人提出了一个后续问题:
问题:这个问题不能通过驻留操作(pinning)来解决吗?
答案:你可以在SendMessage()之后使用GC.KeepAlive(),并将Form对象作为参数传递给KeepAlive()。
所有这些“在你下面释放你的表单”似乎对我来说很奇怪。 SendMessage是同步调用。它将一直等到发送的消息被处理完才返回。
因此,暗示着窗体句柄随时都可能被销毁。例如:
private void DoStuff()
{
//get the handle
IntPtr myHwnd = this.Handle;
//Is the handle still valid to use?
DoSomethingWithTheHandle(myHwnd); //handle might not be valid???
//fall off the function
}
这意味着在我使用窗口句柄和方法结束之间,窗口句柄可能会失效?
更新一
我理解这个概念,即一旦一个窗体超出了作用域,它的句柄就无效了。例如:
private IntPtr theHandle = IntPtr.Zero;
private void DoStuff()
{
MyForm frm = new MyForm())
theHandle = frm.Handle;
//Note i didn't dispose of the form.
//But since it will be unreferenced once this method ends
//it will get garbage collected,
//making the handle invalid
}
对我来说很明显,一旦DoStuff返回,表单的句柄就无效了。无论使用什么技术,如果没有在某个作用域中保留表单,就不能使用它。
我不同意(待完成的链接)的观点,即表单将一直保留到所有发送的消息都被接收到。CLR不知道可能已经分配给我的窗口句柄的人,并且无法得知未来可能调用SendMessage()的人。
换句话说,我无法想象调用:
IntPtr hWnd = this.Handle;
现在将防止此内容被垃圾回收。
更新二
我无法想象拥有一个窗口句柄会阻止表单被垃圾回收。例如:
Clipboard.AsText = this.Handle.ToString();
IntPtr theHandle = (IntPtr)(int)Clipboard.AsText;
答案
但这些都不是重点 - 最初的问题仍然是:
运行时可以在我下面处理一个窗体的句柄吗?
答案是不会。运行时不会在我下面处理窗体。它将处理未引用的窗体,但未引用的窗体不在我下面。 "在我下面" 意味着我对窗体有引用。
另一方面,窗体对象的底层 Windows 窗口句柄可以在我下面被销毁(实际上它怎么可能不会 - 窗口句柄不是引用计数的 - 也不应该是):
IntPtr hwnd = this.Handle;
this.RightToLeft = RightToLeft.Yes;
//hwnd is now invalid
重要提示:HandleRef并不能防止在Windows窗口句柄周围创建对象包装时可能出现的问题: 原因1 如果一个表单对象正在被销毁,因为你没有对它的引用 - 那么你就是愚蠢的,因为你试图与一个按照规定不应该再存在的表单交互。仅仅因为GC还没有处理它而不是让你聪明 - 这使你很幸运。HandleRef是一种保持对表单引用的技巧。而不是使用:
HandleRef hr = new HandleRef(this, this.Handle);
DoSomethingWithHandle(this.Handle);
你可以轻松地使用:
Object o = this;
DoSomethingWithHandle(this.Handle);
原因2 HandleRef无法阻止一个窗体重新创建其底层的窗口句柄,例如:
HandleRef hr = new HandleRef(this, this.Handle);
this.RightToLeft = RightToLeft.Yes;
//hr.Hande is now invalid
因此,虽然P/Invoke中SendMessage的原始修饰符指出了一个问题,但他的解决方案并不是一个真正的解决方案。