.NET WinForms:如何使用需要窗口句柄的API调用?

时间:2008-12-11 15:41:55

标签: .net winforms pinvoke

短版

当我无法保证窗口时,如何使用API​​调用 句柄仍然有效?

我可以保证我提到我的表格(因此表格没有被处理)。这并不能保证表单的句柄在所有时间都保持有效。

即使表单未被放置,表单的窗口句柄如何变得无效?

因为表单的底层Windows窗口已被销毁并重新创建。

长版

我想P / Invoke一个需要hwnd(窗口句柄)的API。需要hWnd的API调用的一些示例是:

IVMRWindowlessControl::SetVideoClippingWindow

 HRESULT SetVideoClippingWindow(
    HWND  hwnd
 );

SendMessage

SendMessage(          
   HWND hWnd,
   UINT Msg,
   WPARAM wParam,
   LPARAM lParam
);

SetClipboardViewer

HWND SetClipboardViewer(
   HWND hWndNewViewer
);

SetTimer

UINT_PTR SetTimer(
   HWND hWnd,
   UINT_PTR nIDEvent,
   UINT uElapse,
   TIMERPROC lpTimerFunc
);

IProgressDialog::StartProgressDialog

HRESULT StartProgressDialog(          
   HWND hwndParent,
   IUnknown *punkEnableModless,
   DWORD dwFlags,
   LPCVOID pvReserved
);

Shell_NotifyIcon

BOOL Shell_NotifyIcon(          
   DWORD dwMessage,
   PNOTIFYICONDATA lpdata  //<--hWnd in there
);

AnimateWindow

BOOL AnimateWindow(   
   HWND hwnd,
   DWORD dwTime,
   DWORD dwFlags
);

注意:其中一些API调用具有托管等效项,有些则没有 - 但这个事实对我的问题来说是无关紧要的。

说明

我可以调用其中一个API函数,这需要长期窗口处理,例如:

private void TellTheGuyToDoTheThing()
{
   SendMessage(this.Handle, 
      WM_MyCustomMessage, 
      paramOneForTheThing, 
      paramTwoForTheThing);
}

有人建议上面调用SendMessage是危险的,因为窗口句柄不受管理。他们建议你将hwnd包装在HandleRef对象中:

private void TellTheGuyToDoTheThing()
{
   SendMessage(new HandleRef(this, this.Handle),
      WM_MyCustomMessage, 
      paramOneForTheThing, paramTwoForTheThing);

这样:在调用SendMessage期间,窗口句柄保证保持有效。但它并不总是这样。以下API调用需要长期访问窗口句柄:

private void RegisterWithTheThing()
{
   this.nextClipboardViewerInChain = SetClipboardViewer(
       new HandleRef(this, this.Handle));
}

即使我将句柄包裹在HandleRef中,仍然可以(在子请求的秒,分钟,小时,天,周,月或年中)使窗体的窗口句柄无效。当窗体的底层Windows窗口被销毁并创建一个新窗口时,就会发生这种情况。尽管事实上我在HandleRef中保护了表单的句柄。

我可以命名一种方式,其中表单的句柄变为无效:

this.RightToLeft = RightToLeft.Yes;

重新创建表单的窗口,旧的hwnd现在无效。

所以问题是:如何使用需要窗口句柄的API调用?

无法完成?

我期待答案:你不能这样做。只要您需要保留手柄,就无法保护表单的句柄以确保其有效。

这意味着我需要知道手柄什么时候被销毁,所以我可以让Windows告诉它,例如:

protected override void TheHandleIsAboutToBeDestroyed()
{
    ChangeClipboardChain(this.Handle, this.nextClipboardViewerInChain);
}

然后在创建新句柄时被告知:

protected override void TheHandleWasJustCreated()
{
   RegisterTheThing();
}

除非没有这样的祖先方法。

替代问题:我是否可以覆盖哪些方法,以便我知道什么时候窗口的句柄即将被销毁,什么时候它刚被创建?

必须打破重新创建句柄的.NET WinForms封装很难看,但这是唯一的方法吗?


更新一个

处理表单的关闭 / OnClose 事件是不够的,

也是如此
  • 处理IDisposable
  • GC固定表格

因为我可以使表单的底层窗口句柄无效而不关闭或处理表单。 e.g:

private void InvalidThisFormsWindowHandleForFun()
{
   this.RightToLeft = RightToLeft.Yes;
}

注意:销毁 Windows窗口句柄,您不会将其丢弃。 .NET中的对象是被处理掉的东西;如果它是Form对象,则很可能涉及销毁 Windows窗口句柄。

Windows是Microsoft的产品。

窗口是带有消息循环的东西,有时可以在屏幕上显示内容。


更新两个

me.yahoo.com/a/BrYwg有一个很好的建议,即使用NativeWindow对象充当需要hWnd用于侦听消息的项目的监听器。这可以用来解决一些问题,例如:

  • SetClipboardViewer
  • SetTimer的
  • IProgressDialgo :: StartProgressDialog
  • Shell_NotifyIcon

但不适用于

  • AnimateWindow
  • 的SendMessage
  • IVMRWindowlessControl :: SetVideoClippingWindow

5 个答案:

答案 0 :(得分:1)

您是否查看过NativeWindow类中的功能?

答案 1 :(得分:1)

您是否可以覆盖表单上的OnHandleCreated和OnHandleDestroyed,并采取相应的行动?

答案 2 :(得分:0)

我认为应该覆盖表单的OnClose函数(或相关事件)。

替代方案 - 每个表单都实现了IDisposable - 为什么不将代码添加到属于该句柄的表单的Dispose方法中。

答案 3 :(得分:0)

您可能希望为此使用GCHandle(System.Runtime.InteropServices.GCHandle)。 GCHandle可用于“固定”一个对象,以便.NET将其保存在一个内存位置。我广泛使用它来与waveOut API进行交互;没有固定,应用程序偶尔会神秘地崩溃。

显然,在窗口被实际销毁然后重新创建的情况下,这不会有用。

答案 4 :(得分:0)

  

我可以用一种方式来命名表格   句柄变为无效:

this.RightToLeft = RightToLeft.Yes;

我知道另一种方式:

this.ShowInTaskbar = false;