将代码迁移到C#/Win32会导致异常

问题描述 投票:0回答:1

在我们(基于 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);

但是很难调试(也欢迎任何提示。)

有人做过吗?有人知道我做错了什么吗?

c# winapi pinvoke user32
1个回答
0
投票

好吧便便。答案是直视着我的眼睛。

我已经修改了原始代码,认为我很聪明,看到私有字段

_newWndProc
没有在任何地方使用。所以我把它设为局部变量。

然后会发生什么?您使用一个指向 local 委托对象的函数指针,一旦作用域结束,该对象就会被标记为终结……没有什么可以延长它的生命周期。 IE。一旦完成,互操作将使用指向无效对象的指针!

所以解决方案是撤消我的聪明才智优化并再次使

newWndProc
成为一个字段。 (这花了我几个小时来调试!)

© www.soinside.com 2019 - 2024. All rights reserved.