tags:

views:

64

answers:

1

I am using this sample as a basis for a program I'm making. After approximately 618 keystrokes, the program throws this error:

CallbackOnCollectedDelegate was detected
Message: A callback was made on a garbage collected delegate of type 'KeyLogger!KeyLogger.CallBackFunction+DelegateCallBack::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.

The error is thrown most times the application is run, but not every time and not at the same keystroke count. From the error message, I think it sounds like the garbage collector is collecting the delegate, how can I prevent this?

The program I made is essentially a modified version of that vb.net project, but it does not actually store the keystrokes.

Thank you for your help!

Code within CallBack.vb:

Option Strict Off
Option Explicit On
Module CallBackFunction
    '******************************************************************************************
    '     Sample for retrieving keystrokes  by use of the "kbLog32.dll"
    '                      (c) 2004 by Nadeem Afanah.
    '******************************************************************************************

    'CallBack function
    Delegate Sub DelegateCallBack(ByVal msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer)

    Sub CallBack(ByVal msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer)
        'here we track only WM_CHAR and WM_KEYDOWN
        If msg = WM_KEYDOWN Then
             ...    
        End If
    End Sub
End Module

Code in Declarations.vb:

Option Strict Off
Option Explicit On
Module Declarations
    '******************************************************************************************
    '     Sample for retrieving keystrokes  by use of the "kbLog32.dll"
    '                      (c) 2004 by Nadeem Afanah.
    '******************************************************************************************
    '******************************************************************************************
    'DLL declarations
    Public Declare Function StartLog Lib "kbLog32" (ByVal hWnd As Integer, ByVal lpFuncAddress As DelegateCallBack) As Integer

    Public Declare Sub EndLog Lib "kbLog32" ()

    '----------------------------------------------------------------------------------------
    Declare Function SetWindowPos Lib "user32" (ByVal hWnd As Integer, ByVal hWndInsertAfter As Integer, ByVal x As Integer, ByVal y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal wFlags As Integer) As Integer
    Declare Function FindWindow Lib "user32"  Alias "FindWindowA"(ByVal lpClassName As String, ByVal lpWindowName As String) As Integer
    Declare Function FindWindowEx Lib "user32"  Alias "FindWindowExA"(ByVal hWnd1 As Integer, ByVal hWnd2 As Integer, ByVal lpsz1 As String, ByVal lpsz2 As String) As Integer
    '****************************************************************************************
    ' Keyboard messages
    Public Const WM_KEYUP As Short = &H101s
    Public Const WM_KEYDOWN As Short = &H100s
    Public Const WM_CHAR As Short = &H102s
    Public Const WM_SYSKEYDOWN As Short = &H104s
    Public Const WM_SYSKEYUP As Short = &H105s

    'SetWindowPos messages
    Public Const SWP_NOSIZE As Short = &H1s
    Public Const SWP_NOMOVE As Short = &H2s
    Public Const HWND_TOPMOST As Short = -1
    Public Const SWP_SHOWWINDOW As Short = &H40s
    '******************************************************************************************

End Module
+2  A: 

Look at the code in Form1.vb where it does this:

    StartLog(nhWnd_text, AddressOf CallBack)

This is where it's saying, take the location of the Callback function an use it to hanle messages I receive regarding keyboard events.

Try something like this:

Friend Class Form1
    Inherits System.Windows.Forms.Form
    ''Add this ----------------------------
    <MarshalAs(UnmanagedType.FunctionPtr)> _
    Private DelSub as New DelegateCallBack(AdressOf CallBack)
    ''-------------------------------------

    ''In the sub Sub Command1_Click
    ''Change this -------------------------
        StartLog(nhWnd_text, AddressOf CallBack)
    ''To this -----------------------------
        StartLog(nhWnd_text, DelSub)
    ''-------------------------------------

End Class

What we're doing here is creating a local "delegate sub" (think of it as a variable which points at a sub). We're pointing this at the Callback sub. We're then using this delegate sub instead of passing in a reference directly to the Callback sub.

The difference is that the .Net framework now knows that there is something pointing at that sub so won't garbage collect it (clear it from memory)

The MarshallAs bit is a little superfluous as that's the default marshalling but it simply means we're explicitly telling .Net that we're using the delegate to access unmanaged code (something outside the .Net framework)

Just for the record, I still had to download the code as it was actually the bit in Form1.vb that was relevant - But thanks for trying :)

Basiclife
My explanation has glossed over a couple of bits but you can get more info from the link I posted in the comments on the question
Basiclife
Working so far. Will run a series of tests and let you know the results.
Cyclone
That worked, thanks so much!
Cyclone
You're Welcome :)
Basiclife
An example of how the key hooking API can be dangerous - when you call the API directly to add your code for key logging, it becomes YOUR responsibility to pass on key events to whatever was listening before you came along - ie if you just listen but don't pass them on, no other application would receive ANY keyboard input. This is done this way so you can selectively choose what to pass on (ie block certain keys). When you've finished listening, it's your responsibility to restore the "chain" to its previous state - So just stopping your app, wouldn't restore the keyboard events. reboot time.
Basiclife
Yeah, I called the EndLog method.
Cyclone