知识库 KB0201
对 .NET 加载项进行编程时,如何使用 Windows 设置子类操作
问题
我为 Microsoft Office 开发了某 .NET 加载项,它使用 Windows 设置子类操作。在客户同时运行我的加载项和其他加载项(例如,think-cell)时,Office 出现故障。
解决方案
此问题通常由 NativeWindow.AssignHandle
/ NativeWindow.ReleaseHandle
的设置子类操作所致。
相反,请按照 Microsoft 的建议 P/调用至 Comctl32.dll's
SetWindowSubclass
和 RemoveWindowSubclass
,如该 Microsoft 文档页中所述。要对您的项目进行此更改,可以在使用 NativeWindow
设置子类操作的地方将 替换为 thinkcell.SubclassedWindow.cs
NativeWindow
。
using System;
using System.Diagnostics;
namespace thinkcell
{
internal enum BOOL : int
{
FALSE = 0,
TRUE = 1,
}
internal static partial class ComCtl32
{
public delegate IntPtr SUBCLASSPROC(
IntPtr hWnd,
int msg,
IntPtr wParam,
IntPtr lParam,
UIntPtr uIdSubclass,
UIntPtr dwRefData
);
[System.Runtime.InteropServices.DllImport("comctl32.dll", ExactSpelling = true)]
public static extern BOOL SetWindowSubclass(
IntPtr hWnd,
IntPtr pfnSubclass,
UIntPtr uIdSubclass,
UIntPtr dwRefData
);
[System.Runtime.InteropServices.DllImport("comctl32.dll", ExactSpelling = true)]
public static extern BOOL RemoveWindowSubclass(
IntPtr hWnd,
IntPtr pfnSubclass,
UIntPtr uIdSubclass
);
[System.Runtime.InteropServices.DllImport("comctl32.dll", ExactSpelling = true)]
public static extern IntPtr DefSubclassProc(
IntPtr hWnd,
int msg,
IntPtr wParam,
IntPtr lParam
);
}
class SubclassedWindow : MarshalByRefObject, System.Windows.Forms.IWin32Window
{
// prevents collection of SubclassedWindow that is still in use
static private System.Collections.Generic.HashSet<SubclassedWindow> _instancesInUse = new System.Collections.Generic.HashSet();
// The number of uses we still have for this instances:
// - some window attached, or
// - inside a window procedure
private int _uses = 0;
// Our window procedure delegate
private ComCtl32.SUBCLASSPROC _windowProc;
// The native handle for our delegate
private IntPtr _windowProcHandle;
static SubclassedWindow()
{
AppDomain.CurrentDomain.ProcessExit += OnShutdown;
}
public SubclassedWindow()
{
_windowProc = new ComCtl32.SUBCLASSPROC(Callback);
_windowProcHandle = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(_windowProc);
}
/// <summary>
/// Gets the handle for this window.
/// </summary>
public IntPtr Handle { get; private set; }
/// <summary>
/// Assigns a handle to this <see cref="NativeWindow"/> instance.
/// </summary>
public void AssignHandle(IntPtr handle)
{
CheckReleased();
Debug.Assert(handle != IntPtr.Zero, "handle is 0");
if (0 == _uses)
{
lock(_instancesInUse)
{
_instancesInUse.Add(this);
}
} // else may happen if handle gets reassigned inside WndProc.
// This is legal after any call to DefWndProc.
++_uses;
Handle = handle;
ComCtl32.SetWindowSubclass(handle, _windowProcHandle, UIntPtr.Zero, UIntPtr.Zero);
OnHandleChange();
}
/// <summary>
/// Window message callback method. Control arrives here when a window
/// message is sent to this Window. This method packages the window message
/// in a Message object and invokes the WndProc() method. A WM_NCDESTROY
/// message automatically causes the ReleaseHandle() method to be called.
/// </summary>
private IntPtr Callback(
IntPtr hWnd,
int msg,
IntPtr wParam,
IntPtr lParam,
UIntPtr uIdSubclass,
UIntPtr dwRefData
)
{
Debug.Assert(0 < _uses);
++_uses;
try
{
var m = System.Windows.Forms.Message.Create(hWnd, msg, wParam, lParam);
WndProc(ref m);
return m.Result;
}
catch (Exception e)
{
OnThreadException(e);
return IntPtr.Zero;
}
finally
{
if (msg == 0x82/*WM_NCDESTROY*/ && Handle != IntPtr.Zero) {
InternalReleaseHandle();
}
if (0 == --_uses)
{
lock (_instancesInUse)
{
_instancesInUse.Remove(this);
}
}
}
}
/// <summary>
/// Raises an exception if the window handle is not zero.
/// </summary>
private void CheckReleased()
{
if (Handle != IntPtr.Zero)
{
throw new InvalidOperationException("Window handle already exists.");
}
}
/// <summary>
/// Invokes the default window procedure associated with this Window. It is
/// an error to call this method when the Handle property is zero.
/// </summary>
public void DefWndProc(ref System.Windows.Forms.Message m)
{
Debug.Assert(m.HWnd==Handle, "SubclassedWindow is not attached to the window m is addressed to.");
m.Result = ComCtl32.DefSubclassProc(m.HWnd, m.Msg, m.WParam, m.LParam);
}
/// <summary>
/// Specifies a notification method that is called when the handle for a
/// window is changed.
/// </summary>
protected virtual void OnHandleChange()
{
}
/// <summary>
/// On class load, we connect an event to Application to let us know when
/// the process or domain terminates. When this happens, we attempt to
/// clear our window class cache. We cannot destroy windows (because we don't
/// have access to their thread), and we cannot unregister window classes
/// (because the classes are in use by the windows we can't destroy). Instead,
/// we move the class and window procs to DefWndProc
/// </summary>
[System.Runtime.ConstrainedExecution.PrePrepareMethod]
private static void OnShutdown(object sender, EventArgs e)
{
// No lock because access here should be race-free, no concurrent SubclassedWindow.AttachHandle/ReleaseHandle
// should happen while shutting down.
Debug.Assert(0 == _instancesInUse.Count);
}
/// <summary>
/// When overridden in a derived class, manages an unhandled thread exception.
/// </summary>
protected virtual void OnThreadException(Exception e)
{
}
private void InternalReleaseHandle()
{
Debug.Assert(Handle != IntPtr.Zero);
ComCtl32.RemoveWindowSubclass(Handle, _windowProcHandle, UIntPtr.Zero);
Handle = IntPtr.Zero;
OnHandleChange();
--_uses;
}
/// <summary>
/// Releases the handle associated with this window.
/// </summary>
public void ReleaseHandle()
{
if (Handle != IntPtr.Zero) {
InternalReleaseHandle();
if (0 == _uses)
{
lock (_instancesInUse)
{
_instancesInUse.Remove(this);
}
}
}
}
/// <summary>
/// Invokes the default window procedure associated with this window.
/// </summary>
protected virtual void WndProc(ref System.Windows.Forms.Message m)
{
DefWndProc(ref m);
}
}
}
限制:
- 原始
NativeWindow
也可以创建窗口,但是很少在NativeWindow
的同一个实例中将其与设置子类操作结合使用。 thinkcell.SubclassedWindow
并非线程安全,但是设置子类操作和消息处理通常在同一线程中进行。
如果您在使用 thinkcell.SubclassWindow
时遇到任何问题,请与我们联系。