Search code examples
c#dllinteropunmanagedmanaged

Delegate getting GC even after pinning?


Related code here: https://github.com/AkazaRenn/FruitLanguageSwitcher/blob/main/Core/Hotkey.cs#L17

Callback code related class:

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

        }

Unmanaged code:

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

The actual delegate passed into Hotkey() can be found here: https://github.com/AkazaRenn/FruitLanguageSwitcher/blob/main/App.xaml.cs#L82

The pinned pointer address of the delegates are sent to AHKDLL (unmanaged) for callback. It will work fine at the beginning, but the program will crash after a random period of time (quite a few hours), with such log in Event Viewer:

Application: FruitLanguageSwitcher.exe
CoreCLR Version: 7.0.222.60605
.NET Version: 7.0.2
Description: The application requested process termination through System.Environment.FailFast.
Message: A callback was made on a garbage collected delegate of type 'FruitLanguageSwitcher!FruitLanguageSwitcher.Core.Hotkey+AHKDelegate::Invoke'.
Stack:
Faulting application name: FruitLanguageSwitcher.exe, version: 1.0.0.0, time stamp: 0x638f99ee
Faulting module name: coreclr.dll, version: 7.0.222.60605, time stamp: 0x638f9099
Exception code: 0x80131623
Fault offset: 0x00000000002662d9
Faulting process id: 0x0xFDC
Faulting application start time: 0x0x1D92F3E416B9F1A
Faulting application path: C:\Program Files\WindowsApps\AkazaRenn.82975CBC0BB1_1.2.2.0_x64__fhf2jh1qk9hx4\FruitLanguageSwitcher.exe
Faulting module path: C:\Program Files\WindowsApps\AkazaRenn.82975CBC0BB1_1.2.2.0_x64__fhf2jh1qk9hx4\coreclr.dll
Report Id: 69384484-bd77-4f32-b5f0-6c01c00fb673
Faulting package full name: AkazaRenn.82975CBC0BB1_1.2.2.0_x64__fhf2jh1qk9hx4
Faulting package-relative application ID: App

I've tried doing mashalling and object pinning, as suggested by some other questions in SOF, but none had solved my problem. I'm quite confused now on how to make my delegate actually pinned. Thanks in advance!


Solution

  • To conclude it simply, I don't even need a GCHandle, simply save the delegate as a static variable can do the job, like:

            private static readonly List<AHKDelegate> handlers = new();
    
            public Hotkey(AHKDelegate _onCapsLock, AHKDelegate _onLanguageChange, AHKDelegate _onRaltUp) {
    
                handlers.Add(_onCapsLock);
                ahk.SetVar("onCapsLockPtr", GetActionDelegateStr(_onCapsLock));
    
                handlers.Add(_onLanguageChange);
                ahk.SetVar("onLanguageChangePtr", GetActionDelegateStr(_onLanguageChange));
    
                handlers.Add(_onRaltUp);
                ahk.SetVar("onRaltUpPtr", GetActionDelegateStr(_onRaltUp));
            }
    

    Thanks to @HansPassant for pointing it out!