2

精简版

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

我可以保证我持有对我的表单的引用(因此不会处理该表单)。这并不能保证表单的句柄将一直保持有效。

即使没有处理表单,表单的窗口句柄如何变得无效

因为窗体的底层 Windows 窗口被破坏并重新创建。

长版

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

IVMRWindowlessControl::SetVideoClippingWindow

 HRESULT SetVideoClippingWindow(
    HWND  hwnd
 );

发信息

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

设置剪贴板查看器

HWND SetClipboardViewer(
   HWND hWndNewViewer
);

设置定时器

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
);

动画窗口

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 封装是丑陋的,但这是唯一的方法吗?


更新一

处理表单的Close / OnClose事件是不够的,同样如此

  • 处理 IDisposable
  • GC 固定表单

因为我可以在不关闭或处置表单的情况下使表单的底层窗口句柄无效。例如:

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

注意:销毁了 Windows 窗口句柄,而不是处置它。.NET 中的对象是被处理掉的东西;如果它是一个 Form 对象,则很可能涉及破坏Windows 窗口句柄。

Windows 是 Microsoft 的产品。

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


更新二

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

  • 设置剪贴板查看器
  • 设置定时器
  • IProgressDialgo::StartProgressDialog
  • Shell_NotifyIcon

但不适用于

  • 动画窗口
  • 发信息
  • IVMRWindowlessControl::SetVideoClippingWindow
4

5 回答 5

1

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

于 2008-12-11T16:44:50.173 回答
1

你能忍受在你的表单上覆盖 OnHandleCreated 和 OnHandleDestroyed 并采取相应的行动吗?

于 2008-12-12T14:12:23.310 回答
0

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

显然,这在窗口实际被破坏然后重新创建的情况下没有用。

于 2008-12-11T16:16:48.170 回答
0

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

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

于 2008-12-11T15:46:54.410 回答
0

我可以说出表单句柄无效的一种方式:

this.RightToLeft = RightToLeft.Yes;

我知道另一种方式:

this.ShowInTaskbar = false;
于 2009-12-30T14:05:10.317 回答