在我们(基于 WinUI3)的项目中,我们一直在使用 P/Invoke 包来调用修改窗口的低级方法。但最近所有这些包都已被弃用,取而代之的是源代码生成的C#/win32 包。
但是,我在将所有代码迁移到这个新库时遇到了一些困难。 我遇到的最大问题是 win32 子类。在我们当前的代码中,我们的工作方式如下(由一年前离开公司的同事编写):
private delegate IntPtr WinProc(IntPtr hWnd, PInvoke.User32.WindowMessage Msg, IntPtr wParam, IntPtr lParam);
private IntPtr _oldWndProc = IntPtr.Zero;
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
private static extern IntPtr SetWindowLongPtr32(IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, WinProc newProc);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, WinProc newProc);
// This static method is required because Win32 does not support GetWindowLongPtr directly
private static IntPtr SetWindowLongPtr(IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, WinProc newProc)
{
if (IntPtr.Size == 8)
return SetWindowLongPtr64(hWnd, nIndex, newProc);
else
return SetWindowLongPtr32(hWnd, nIndex, newProc);
}
[DllImport("user32.dll")]
static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, PInvoke.User32.WindowMessage Msg, IntPtr wParam, IntPtr lParam);
private void SubClassingWin32()
{
_hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
var newWndProc = new WinProc(NewWindowProc);
_oldWndProc = SetWindowLongPtr(_hwnd, PInvoke.User32.WindowLongIndexFlags.GWL_WNDPROC, newWndProc);
}
private IntPtr NewWindowProc(IntPtr hWnd, PInvoke.User32.WindowMessage Msg, IntPtr wParam, IntPtr lParam)
{
switch (Msg)
{
[...]
}
return CallWindowProc(_oldWndProc, hWnd, Msg, wParam, lParam);
}
我尝试将其重写为 C#/Win32,但出现执行引擎异常。
private delegate LRESULT WNDPROCDelegate(HWND hWnd, uint Msg, WPARAM wParam, LPARAM lParam);
// This static method is required because Win32 does not support GetWindowLongPtr directly
private static IntPtr SetWindowLongPtr(HWND hWnd, WindowLongIndexFlags nIndex, IntPtr dwNewLong) =>
#if x64
PInvoke.SetWindowLongPtr(hWnd, nIndex, dwNewLong);
#else
PInvoke.SetWindowLong(hWnd, nIndex, dwNewLong.ToInt32());
#endif
private void SubClassingWin32()
{
_hwnd = (HWND)WinRT.Interop.WindowNative.GetWindowHandle(this);
WNDPROCDelegate del = NewWindowProc;
var newWndProc = Marshal.GetFunctionPointerForDelegate(del);
var oldWndProc = SetWindowLongPtr(_hwnd, WindowLongIndexFlags.GWL_WNDPROC, newWndProc);
_oldWndProc = Marshal.GetDelegateForFunctionPointer<WNDPROC>(oldWndProc);
}
private LRESULT NewWindowProc(HWND hWnd, uint Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
[...]
}
return PInvoke.CallWindowProc(_oldWndProc, hWnd, Msg, wParam, lParam);
}
原始代码似乎使用了一个技巧,
SetWindowLongPtr
的第三个参数传递了一个隐式转换为函数指针的委托。对于 C#/Win32,这似乎不可能,所以我使用 Marshal
。
调试器显示 NewWindowProc
被击中,所以第一步似乎有效,所以我怀疑线路出了问题
_oldWndProc = Marshal.GetDelegateForFunctionPointer<WNDPROC>(oldWndProc);
或
PInvoke.CallWindowProc(_oldWndProc, hWnd, Msg, wParam, lParam);
但是很难调试(也欢迎任何提示。)
有人做过吗?有人知道我做错了什么吗?
好吧便便。答案是直视着我的眼睛。
我已经修改了原始代码,认为我很聪明,看到私有字段
_newWndProc
没有在任何地方使用。所以我把它设为局部变量。
然后会发生什么?您使用一个指向 local 委托对象的函数指针,一旦作用域结束,该对象就会被标记为终结……没有什么可以延长它的生命周期。 IE。一旦完成,互操作将使用指向无效对象的指针!
所以解决方案是撤消我的聪明才智优化并再次使
newWndProc
成为一个字段。 (这花了我几个小时来调试!)