.NET Winforms:运行时是否可能在我使用时释放窗体句柄?

14

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的原始修饰符指出了一个问题,但他的解决方案并不是一个真正的解决方案。


这是一个非常好的问题,更新使其更加完美。干得好! - CodingBarfield
1个回答

3

当你调用 SendMessage 时,通常是从另一个线程或至少与你的窗体分离的另一个组件中进行的。我认为这里要表达的观点是,仅仅因为你有一个IntPtr,其中包含一个有效的窗口句柄,你不能假设它仍然是有效的。

假设你有这个类:

class MyClass {
   IntPtr hwnd;
   public MyClass(IntPtr hwnd) {
      this.hwnd = hwnd;
   }
   ...

   private void DoStuff()
   {
       //n.b. we don't necessarily know if the handle is still valid
       DoSomethingWithTheHandle(hwnd);
   }
}

另外还有一些其他的地方:

 private void DoOtherStuff() {
     Form f = new Form();
     mc = new MyClass(f.Handle);
 }

因为f已经超出了作用域,所以它的Dispose最终会被GC终结器调用。这就是为什么在这种情况下可能需要使用Gc.KeepAlive的原因。f必须保持活动状态,直到mc完成对句柄的操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接