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

已固定的委托仍被GC回收致程序崩溃问题求助

委托被GC回收导致AutoHotkey回调崩溃解决方案

问题背景

使用AutoHotkeyEngine时,将固定后的AHKDelegate委托指针传入非托管AHK DLL作为回调,程序初始运行正常,但随机运行数小时后崩溃。事件查看器日志显示回调了已被垃圾回收(GC)的委托,尝试过封送处理和对象固定仍未解决。

相关代码

C# 代码

internal class Hotkey {
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void AHKDelegate();
    private readonly AutoHotkeyEngine ahk = AutoHotkeyEngine.Instance;
    private readonly List<GCHandle> handles = new();

    public Hotkey(AHKDelegate _onCapsLock) {
        SetVarOnSettings();

        handles.Add(GCHandle.Alloc(_onCapsLock));
        ahk.SetVar("onCapsLockPtr", GetActionDelegateStr(_onCapsLock));
        ahk.ExecRaw(System.Text.Encoding.Default.GetString(Properties.Resources.CapsLock));
    }

    ~Hotkey() {
        foreach(var handle in handles) {
            handle.Free();
        }
    }
}

非托管AHK代码

$CapsLock::
    If (GetKeyState("CapsLock", "T")) {
        SetCapsLockState Off
    } else {
        KeyWait, CapsLock, T0.5
        If (ErrorLevel) {
            SetCapsLockState On
            KeyWait, CapsLock
        } else {
            DllCall(onCapsLockPtr)
        }
    }
Return

崩溃日志摘要

Application: FruitLanguageSwitcher.exe
.NET Version: 7.0.2
Message: A callback was made on a garbage collected delegate of type 'FruitLanguageSwitcher!FruitLanguageSwitcher.Core.Hotkey+AHKDelegate::Invoke'.
Exception code: 0x80131623

问题根源

  1. Hotkey实例被GC回收:若Hotkey对象无强引用持有,会被GC回收,其终结器释放GCHandle后,委托失去固定最终被回收,但非托管AHK代码仍持有旧指针。
  2. 终结器执行时机不确定:依赖终结器释放GCHandle,无法保证在非托管代码停止使用委托前不释放,存在时序风险。
  3. 未强引用委托本身:仅通过GCHandle固定委托,无强引用指向委托对象时,GC仍可能在特殊场景下回收它。

解决方案

1. 强引用持有委托与Hotkey实例

修改Hotkey类,添加对委托的强引用,同时实现IDisposable接口手动控制资源释放;在应用生命周期内保持Hotkey实例的强引用。

修改后的Hotkey类

internal class Hotkey : IDisposable
{
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void AHKDelegate();
    
    private readonly AutoHotkeyEngine ahk = AutoHotkeyEngine.Instance;
    private readonly List<GCHandle> handles = new();
    // 强引用持有委托,避免被GC回收
    private readonly AHKDelegate _onCapsLock;
    private bool _disposed = false;

    public Hotkey(AHKDelegate onCapsLock)
    {
        SetVarOnSettings();
        
        _onCapsLock = onCapsLock;
        // 固定被强引用持有的委托
        handles.Add(GCHandle.Alloc(_onCapsLock));
        ahk.SetVar("onCapsLockPtr", GetActionDelegateStr(_onCapsLock));
        ahk.ExecRaw(System.Text.Encoding.Default.GetString(Properties.Resources.CapsLock));
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        
        // 释放GCHandle,确保委托不再被固定
        foreach (var handle in handles)
        {
            if (handle.IsAllocated)
            {
                handle.Free();
            }
        }
        
        _disposed = true;
    }

    ~Hotkey()
    {
        Dispose(false);
    }
}

应用层保持强引用

在主应用类中声明静态字段存储Hotkey实例,确保其在程序生命周期内不会被GC回收,并在退出时手动释放:

// 主App类中
private static Hotkey _languageSwitchHotkey;

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    // 创建实例并保持强引用
    _languageSwitchHotkey = new Hotkey(OnCapsLockPressed);
}

protected override void OnExit(ExitEventArgs e)
{
    // 程序退出时手动释放资源
    _languageSwitchHotkey?.Dispose();
    base.OnExit(e);
}

private void OnCapsLockPressed()
{
    // 你的回调业务逻辑
}

2. 关键逻辑说明

  • 强引用委托:_onCapsLock字段确保委托对象始终有活跃引用,GC不会将其标记为可回收。
  • 手动资源管理:IDisposable接口让我们可以精准控制GCHandle的释放时机,避免终结器的不确定性。
  • 长期持有Hotkey实例:静态字段_languageSwitchHotkey保证Hotkey对象在程序运行期间不会被GC回收,其内部的委托和GCHandle一直有效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 08:05:24