1

EDIT FOR THOSE FOLLOWING COMMENTS:

https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-setwindowshookexa

HHOOK SetWindowsHookExA(
  int       idHook,
  HOOKPROC  lpfn,
  HINSTANCE hmod,
  DWORD     dwThreadId
);

lpfn

Type: HOOKPROC

A pointer to the hook procedure. If the dwThreadId parameter is zero or specifies the identifier of a thread created by a different process, the lpfn parameter must point to a hook procedure in a DLL. Otherwise, lpfn can point to a hook procedure in the code associated with the current process.

hmod

Type: HINSTANCE

A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process.


I do not understand why this does not work. For this version I do not block any hooks. I simply make entry into txt file noting time that message was processed by my keyboard callback function. However when I step through my main program, the hook catches OK. But KeyboardCallBack never gets executed, I think, as my log is empty safe for its initial header.

CONSTANTS AND APIS:

Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" _
  Alias "SetWindowsHookExA" (ByVal idHook As Long, _
                             ByVal lpfn As Long, _
                             ByVal hmod As Long, _
                             ByVal dwThreadId As Long) As Long

Private Declare PtrSafe Sub CopyMemory Lib "kernel32" _
   Alias "RtlMoveMemory" _
  (pDest As Any, _
   pSource As Any, _
   ByVal cb As Long)

Private Declare PtrSafe Function CallNextHookEx Lib "user32" _
   (ByVal hHook As Long, _
   ByVal nCode As Long, _
   ByVal wParam As Long, _
   ByVal lParam As Long) As Long

Private Type KBDLLHOOKSTRUCT
  vkCode As Long
  scanCode As Long
  flags As Long
  time As Long
  dwExtraInfo As Long
End Type

Private Const HC_ACTION = 0
Private Const WH_KEYBOARD_LL = 13&
Private KeyboardHandle As Long

THE HOOK:

Public Sub HookKeyboard()
    KeyboardHandle = SetWindowsHookEx(WH_KEYBOARD_LL, AddressOf KeyboardCallback, Application.Hinstance, 0)
End Sub

KEYBOARD CALLBACK AND AUXILIARY FUNCTIONS:

Public Function KeyboardCallback(ByVal Code As Long, _
                                  ByVal wParam As Long, _
                                  ByVal lParam As Long) As Long
    Call TimeStamp
    Static Hookstruct As KBDLLHOOKSTRUCT

    If Code = HC_ACTION Then
        Call CopyMemory(Hookstruct, ByVal lParam, Len(Hookstruct))
        If BlockKey(Hookstruct) = True Then
            KeyboardCallback = 1
            Exit Function
        End If
    End If

  KeyboardCallback = CallNextHookEx(KeyboardHandle, _
    Code, wParam, lParam)
End Function

    Private Function BlockKey(ByRef Hookstruct As KBDLLHOOKSTRUCT) As Boolean
        BlockKey = False
    End Function

    Private Function TimeStamp()
        Dim FSO As Object
        Set FSO = CreateObject("Scripting.FileSystemObject")

        Dim TSO As Object
        Set TSO = FSO.OpenTextFile("C:\Users\evanm\Desktop\New Text Document.txt", 1)

        Dim FileString As String
        FileString = TSO.ReadAll
        TSO.Close

        FileString = FileString & " " & Now() & " "

        Set TSO = FSO.OpenTextFile("C:\Users\evanm\Desktop\New Text Document.txt", 2)
        TSO.WriteLine FileString
        TSO.Close

        Set FSO = Nothing
        Set TSO = Nothing
    End Function

TESTS:

Sub testHook()
    Hook.HookKeyboard
Stop
'write a bunch of stuff
Stop
    Hook.UnhookKeyboard
End Sub

Sub testWriteFile()
    Hook.TimeStamp
End Sub
learnAsWeGo
  • 2,252
  • 2
  • 13
  • 19
  • 2
    Per [docs](https://learn.microsoft.com/en-us/office/vba/api/excel.application.hinstance), `Application.Hinstance` only returns a valid pointer in a 32-bit host. Have you tried `Application.HinstancePtr`? That said I would have tried `Application.Hwnd` (the mainwindow handle) first though. – Mathieu Guindon Feb 19 '19 at 03:38
  • 3
    Low level keyboard hooks might not work in Office hosts. IIR Office messes with the keyboard state, so if you don't get your hook in the chain before Office, it's hit or miss as to whether the KBDLLHOOKSTRUCT you end up with in the callback is valid. – Comintern Feb 19 '19 at 03:42
  • .Hwnd and the hook does not catch. Both HinstancePTR and HInstance and the hook is caught, but my keyboardcallback does not execute – learnAsWeGo Feb 19 '19 at 03:43
  • @Comintern is this "chance" everytime i load the application or run the setkeyboardhook function? also given that i am not trapping any messages, it is sort of irrelevant whether my KBDLLHOOKSTRUCT points to a valid message....my basic test, aka log time to txt file, as to whether the callback is being executed fails – learnAsWeGo Feb 19 '19 at 03:44
  • It's been a couple years since we pulled it out of the Rubberduck code base, so I'm having a difficult time remembering the particulars. It wasn't the callback though - that should just work. – Comintern Feb 19 '19 at 03:46
  • Does the thread that calls `SetWindowsHookEx` have an active message loop? That is a requirement for the callback to be called. If you are not sure, you can spawn your own thread to call `SetWindowsHookEx` and run your own message loop. – Remy Lebeau Feb 19 '19 at 03:53
  • @RemyLebeau The VBA run-time maintains a message loop. If it's 64 bit Excel, the callback is probably just getting the wrong `HINSTANCE` as noted above. – Comintern Feb 19 '19 at 03:57
  • 2
    Also, VBA being single-threaded makes it a wee bit hard to do any multithreading in VBA. – this Feb 19 '19 at 03:58
  • @this think that when you call this API you alter the file that handles messages, such that it calls the function you describe everytime it handles message. I think you can point to a data structure outside of VBA. Like I could put this structure somewhere in memory and pass a pointer to it in the function and it would be OK. Maybe i need something like this getmodulehandle function? – learnAsWeGo Feb 19 '19 at 04:07
  • 2
    It's not a *file*, you just register a callback proc. If you use a stuct defined outside of VBA, you're going to get an access violation when you try to unhook it or the run-time stops executing. – Comintern Feb 19 '19 at 04:30
  • @Comintern the documentation leads me to believe that you can pass a pointer to a DLL outside of your application to the message handler. But VBA does not create a DLL file (I unzipped it to look for it), so the function stays within the run-time environment? And so you must tell the message handler where the application is in order to find the function inside of it? Sorry I am trying to connect but do not know the verbiage or processes LOL. See edit to OP for look at the documentation. – learnAsWeGo Feb 19 '19 at 04:45
  • That wasn't my point. My point was that the callback should be in the same apartment that manages the memory for the struct. Otherwise you're going to have timing problems when you release from the hook because they're using different synchronization contexts. Did you try using the API that @MathieuGuindon suggested for getting the hinstance? – Comintern Feb 19 '19 at 04:47
  • tried all three and no dice. going to work more in the morning. fun! ty for time & patience – learnAsWeGo Feb 19 '19 at 04:48
  • See [this answer](https://stackoverflow.com/a/29761436/4088852) for the basic gist. Start with a global hook to get the callback functioning properly, *then* work on getting the correct instance. – Comintern Feb 19 '19 at 04:53
  • @MathieuGuindon all of the scripts that I have see use Application.Hinstance. I have gotten to this to work before. But I juggled some stuff around and included the call to timestamp and now its broken :-x – learnAsWeGo Feb 20 '19 at 04:21
  • @MathieuGuindon you were right thank you! – learnAsWeGo May 08 '19 at 03:54

0 Answers0