.NET Winforms:运行时是否可以从我的下方处理表单的句柄?

时间:2008-12-09 16:27:55

标签: c# .net windows winforms winapi

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 。给出了一个非常松散的变化解释:

  

您可以将“hWnd”替换为“IntPtr”   而不是“HandleRef”。但是,你   这样做有风险 - 它   可能会导致您的代码与种族崩溃   条件。 .NET运行时可以和   将把你的窗把手丢弃   从你的消息 - 导致所有   各种令人讨厌的问题!

有人维基后续问题:

  

问题:不能解决最后一个问题   编组,特别是钉扎?

有人回答:

  

答案:您可以使用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
}

这意味着窗口句柄在我使用它和方法结束的时间之间会变得无效吗?


更新一个

我理解一旦表单超出范围,它的句柄无效。 e.g:

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;

现在会阻止被垃圾回收。


更新两个

我无法想象周围有一个窗口处理会使表单不被垃圾收集。即:

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

1 个答案:

答案 0 :(得分: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.KeepAlivef必须保持活着,直到句柄完成mc