精简版
当我无法保证窗口句柄将保持有效时,如何使用 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
);
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