Callbackoncollecteddelegate in Globalkeyboardhook Was Detected

CallbackOnCollectedDelegate in globalKeyboardHook was detected

hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);

There's your problem. You are relying on C# syntax sugar to have it automatically create a delegate object to hookProc. Actual code generation look like this:

keyboardHookProc $temp = new keyboardHookProc(hookProc);
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, $temp, hInstance, 0);

There's only one reference to the delegate object, $temp. But it is local variable and disappears as soon as your hook() method stops executing and returns. The garbage collector is otherwise powerless to see that Windows has a 'reference' to it as well, it cannot probe unmanaged code for references. So the next time the garbage collector runs, the delegate object gets destroyed. And that's a kaboom when Windows makes the hook callback. The built-in MDA detects the problem and generates the helpful diagnostic before the program crashes with an AccessViolation.

You will need to create an additional reference to the delegate object that survives long enough. You could use GCHandle for example. Or easier, just store a reference yourself so the garbage collector can always see the reference. Add a field to your class. Making it static is a sure-fire way to ensure the object can't be collected:

    private static keyboardHookProc callbackDelegate;

public void hook()
{
if (callbackDelegate != null) throw new InvalidOperationException("Can't hook more than once");
IntPtr hInstance = LoadLibrary("User32");
callbackDelegate = new keyboardHookProc(hookProc);
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, callbackDelegate, hInstance, 0);
if (hhook == IntPtr.Zero) throw new Win32Exception();
}

public void unhook()
{
if (callbackDelegate == null) return;
bool ok = UnhookWindowsHookEx(hhook);
if (!ok) throw new Win32Exception();
callbackDelegate = null;
}

No need to pinvoke FreeLibrary, user32.dll is always loaded until your program terminates.

CallbackOnCollectedDelegate was detected even with static delegate

This requires psychic debugging and infer something you didn't document. The oddity is that the MDA is triggered when you call CallNextHookEx(). Which is somewhat strange, the callback to your hook procedure actually worked. That delegate object wasn't collected. It is the next hook procedure call that failed.

There's a simple explanation for that: you called SetWindowsHookEx() more than once. Now using a static variable to store the delegate object will seriously bite, as static variables usually do, it is capable of storing only one delegate object. The second time you call hook(), it will overwrite the delegate of the first hook and it is thus no longer prevented from getting garbage collected. Which indeed triggers the MDA on the CallNextHookEx() since that will call the hook procedure for the first hook.

So your hook() method needs to be improved to this:

public void hook()
{
if (callbackDelegate != null)
throw new InvalidOperationException("Cannot hook more than once");
// etc..
}

It is actually not illegal to hook more than once, Windows doesn't mind. Just don't declare the variable static.

CallbackOnCollectedDelegate was detected

The issue is the same as this post. The difference being that they're using C#. However, VB is doing the same thing; generating a delegate for you. For reference, this is what the decompiled code looks like in your form's Load event.

Private Sub Form1_Load(sender As Object, e As EventArgs)
Me.intLLKey = Form1.SetWindowsHookEx(13, New Form1.LowLevelKeyboardProcDelegate(Me.LowLevelKeyboardProc), IntPtr.Zero, 0)
End Sub

Note that it's creating a LowLevelKeyboardProcDelegate delegate for you. I won't completely rehash @HansPassant's answer here, as he does an excellent job describing the problem and solution; only say that you'll need to store your own reference to the LowLevelKeyboardProcDelegate delegate.

CallbackOnCollectedDelegate was detected

oldWndProc = SetWindowLong(hWnd, GWL_WNDPROC, MyWndProc);

That forces C# to create a delegate object on-the-fly. It translates the code to this:

oldWndProc = SetWindowLong(hWnd, GWL_WNDPROC, new WndProcDelegateType(MyWndProc));

which is a problem, that delegate object isn't referenced anywhere. The next garbage collection is going to destroy it, pulling the rug out from under the unmanaged code. You already did the proper thing in your code, you just forgot to use it. Fix:

oldWndProc = SetWindowLong(hWnd, GWL_WNDPROC, newWndProc);

Deriving your own class from NativeWindow and uses its AssignHandle() method is the better mousetrap btw. Call ReleaseHandle() when you see the WM_DESTROY message.

CallbackOnCollectedDelegate was detected on Invoke() between threads

As Hans Passant said, the problem was with the callback variable in the Start() method. I changed it to the following:

private GrabFrame_CallbackEx callback;

public void Start()
{

// Start grabbing frames
isGrabRunning = true;
callback = new GrabFrame_CallbackEx(GrabCallback);
StartGrabEx(callback);
}

Everything is working now!



Related Topics



Leave a reply



Submit