You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

迁移代码至C#/Win32引发执行引擎异常求助

WinUI3迁移C#/Win32包后窗口子类化执行引擎异常排查与解决

问题背景

WinUI3项目原通过旧P/Invoke包调用底层窗口修改方法,因该包已被弃用,迁移至源代码生成的C#/Win32包后,Win32窗口子类化功能出现执行引擎异常。调试发现NewWindowProc能被触发,但后续执行报错。

原工作代码

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

重写后问题代码

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

怀疑的问题点

  • _oldWndProc = Marshal.GetDelegateForFunctionPointer<WNDPROC>(oldWndProc);
  • return PInvoke.CallWindowProc(_oldWndProc, hWnd, Msg, wParam, lParam);

排查思路与解决方法

1. 委托被GC回收(核心问题)

重写代码中WNDPROCDelegate del是局部变量,SubClassingWin32方法执行完毕后,该委托会被GC标记为可回收。当Win32回调NewWindowProc时,委托已被销毁,直接触发执行引擎异常。

解决:
将新窗口过程委托声明为类成员变量,保留强引用避免GC回收:

// 类成员变量,保留委托引用
private WNDPROC _newWndProc;
private IntPtr _oldWndProc = IntPtr.Zero;
private HWND _hwnd;

private void SubClassingWin32()
{
    _hwnd = (HWND)WinRT.Interop.WindowNative.GetWindowHandle(this);
    // 赋值给成员变量,避免GC回收
    _newWndProc = NewWindowProc;
    var newWndProcPtr = Marshal.GetFunctionPointerForDelegate(_newWndProc);
    // 直接调用C#/Win32的SetWindowLongPtr,无需自行封装
    _oldWndProc = PInvoke.SetWindowLongPtr(_hwnd, WindowLongIndexFlags.GWL_WNDPROC, newWndProcPtr);
}

2. 委托类型不匹配

自行定义的WNDPROCDelegate与C#/Win32生成的WNDPROC委托可能存在隐式不兼容,且转换旧窗口过程指针为委托时类型不匹配,导致调用CallWindowProc时出错。

解决:
直接使用C#/Win32生成的WNDPROC委托,无需自行定义;同时保留旧窗口过程的IntPtr指针,避免委托转换:

private LRESULT NewWindowProc(HWND hWnd, uint Msg, WPARAM wParam, LPARAM lParam)
{
    switch (Msg)
    {
        // 你的消息处理逻辑
    }
    // 直接传入旧窗口过程指针调用CallWindowProc
    return PInvoke.CallWindowProc(_oldWndProc, hWnd, Msg, wParam, lParam);
}

3. 无需自行封装SetWindowLongPtr

C#/Win32已自动处理32/64位兼容性,无需手动编写条件编译的封装方法,直接调用PInvoke.SetWindowLongPtr即可。

调试建议

  • 启用非托管代码调试:在项目属性的“调试”选项卡中勾选“启用非托管代码调试”,可跟踪Win32层面的错误调用栈。
  • 验证GC回收问题:在SubClassingWin32方法末尾添加GC.KeepAlive(del),若异常消失,则确认是委托被GC回收导致的问题。
  • 分步断点调试:在NewWindowProc开头添加Debugger.Break(),逐步执行到CallWindowProc,定位具体报错的代码行。
  • 检查参数类型匹配:对比C#/Win32生成的WNDPROC定义,确保NewWindowProc的参数类型、返回值完全一致。

内容的提问来源于stack exchange,提问作者JHBonarius

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 11:05:54