【问题标题】:.NET Winforms: Can the runtime dispose a form's handle out from under me?.NET Winforms:运行时可以从我下面处理表单的句柄吗?
【发布时间】:2010-09-26 02:01:58
【问题描述】:

SendMessagePInvoke.net 上的当前声明是:

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, 
      IntPtr wParam, IntPtr lParam);

注意: hWnd 不再是 IntPtr,已被 HandleRef 取代。给出了一个非常松散的解释:

您可以将“hWnd”替换为“IntPtr” 而不是“HandleRef”。然而,你 这样做是有风险的——它 可能会导致您的代码因比赛而崩溃 状况。 .NET 运行时可以,并且 会,把你的窗户把手扔掉 从你的信息下 - 导致所有 各种讨厌的问题!

有人 wiki 提出了一个后续问题:

问题:能否解决最后一个问题 带编组,特别是固定?

有人回答:

答案:你可以使用 GC.KeepAlive() 在 SendMessage() 之后 表单对象作为参数 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 返回,表单的句柄就无效了。无论采用何种技术,情况都是如此 - 如果表单不在某个范围内,则使用无效。

我不同意 (todo link guy) 的观点,因为在收到所有发送消息之前,表单会一直存在。 CLR 不知道谁可能已经获得了我的窗体的窗口句柄,并且无法知道将来谁可能调用 SendMessage()。

换句话说,我无法想象这种呼唤:

IntPtr hWnd = this.Handle;

现在将阻止 this 被垃圾回收。


更新二

我无法想象有一个窗口句柄可以防止表单被垃圾收集。即:

Clipboard.AsText = this.Handle.ToString();
IntPtr theHandle = (IntPtr)(int)Clipboard.AsText;

回答

但这些都是无关紧要的 - 原来的问题仍然是:

运行时可以处理表单的 从我下面处理掉?

事实证明,答案是否定的。运行时不会从我下面处理表单。它处理未引用的表单-但未引用的表单不​​在我的范围内。 “在我之下”意味着我有对表格的引用。

另一方面,Form 对象的底层 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 的原始修饰符确实指出了一个问题,但他的解决方案不是解决方案。

【问题讨论】:

  • 这是一个非常好的问题,更新使这个问题变得完美。干得好!

标签: c# .net windows winforms winapi


【解决方案1】:

通常当您调用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 处理完句柄。

【讨论】:

    猜你喜欢
    • 2010-10-21
    • 1970-01-01
    • 2013-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多