Occasionally, we have been asked to provide a mechanism to
allow a VEE program to detect when someone has struck a keyboard
key. VEE has the capability to wait for you to press a function
key or type characters into a text field, but those events are
synchronous with respect to the rest of your program. There are
situations where you might want to detect an asynchronous key
stroke event. To address this, we have developed a DLL that collects
the key code associated with each key stroke event and makes that
collection available to your VEE program. We have provided functions
to enable and disable the event logging and to read the collected
key stroke information into a VEE program.
The rest of this paper details the DLL implementation, illustrates the way you can use the DLL functions in your VEE program, and ends with a list of references.
Your VEE program will import a the DLL called 'KeyBoard.dll.' This DLL has functions to enable and disable key stroke event logging. After enabling key stroke logging, every time you hit a key while VEE is the active window, the Windows system will invoke a callback function in the DLL. That callback function will add the new key code to a list. When the VEE program wishes to retrieve any recorded key codes, It calls another function in the DLL. This latter function will copy some number of key codes into a buffer the VEE program provides, then remove those key codes from the DLL's list. You may read any number of stored key codes from zero to the number of entries in the key code list.
Disabling the key stroke event logging also causes the DLL
to erase any key codes it had stored. You might wish to retain
this information when disabling the event recording. Doing so
is easy, as we will see when we discuss the DLL implementation.
One point to mention is that the DLL is implemented in C++
to take advantage of a recent addition to the C++ standard library.
This addition is known as the 'Standard Template Library' or 'STL'.
Among other things, STL defines container classes which make it
very easy to use dynamically-sized arrays, stacks, queues, sets,
and maps. We will use a dynamic array, or vector, in the callback
function which records the key codes. This makes our task substantially
easier than it would have been had we needed to implement the
vector capability on our own.
Because STL is fairly new, some compilers require a particular
syntax when declaring or using containers. We have used Microsoft's
® Visual C++ version 4.2 to build this DLL. Use of
this compiler required us to declare the container variable in
an unusual way. STL makes use of a default template argument that
the Visual C++ compiler doesn't understand. To work around this,
we declared each container variable with an explicit template
argument. As compilers evolve, we expect that they will support
the default template arguments. The source for this DLL should
be easy to change, once we can compile default template arguments.
To get more information on Visual C++'s STL implementation, visit its web site.
In the followings, we will describe the DLL implementation. It has two primary facets: its interface to the window system; and the interface it exports to clients.
The system side of the DLL implements the window system interface. It is responsible for informing the system that its client (VEE in this case, though the client can be any application capable of linking to a DLL.) wants to be notified, or stop being notified, of key stroke events.
You enable key stroke recording by installing a 'hook' function which windows will call each time you press a key while the application window this DLL is acting on behalf of is the active window.
You install a hook by calling the Win32 function SetWindowsHookEx().
We have defined a DLL function which allows VEE to control the
installation of a keyboard hook.
long recordKeyStrokes(){ long returnVal=0; HMODULE hThisModule; if(!kbdHook){ hThisModule=GetModuleHandle("KeyBoard"); if(!hThisModule){ return -1; } kbdHook=SetWindowsHookEx(WH_KEYBOARD, MyKeyboardProc, hThisModule, GetCurrentThreadId()); if(!kbdHook){ returnVal = -1; } } return returnVal; }
Note the variable called hThisModule. This is a handle
to the DLL. Windows needs this to know which module contains the
function you wish to call with a key stroke event. Our callback
function is inside the DLL named KeyBoard. The call to
GetModuleHandle() finds the address where the KeyBoard
DLL is located within the enclosing application's address space.
With this, we also need to supply a pointer to the function
which will act as our callback. Our function is named MyKeyboardProc.
The operating system also needs to know which thread the keyboard hook should operate on behalf of. Specifying a thread identifier of 0 will cause the system to invoke our callback function whenever you press a key. Specifying the thread identifier returned from GetCurrentThreadId() causes the system to invoke our callback when you press a key and the instance of VEE which has attached the DLL is the active window. More than one instance of VEE can attach this DLL at the same time. Because the DLL maintains a separate data space foe each client that attaches to it, a particular instance of VEE will only receive key strokes that happened while it was the active window.
Our library allows to determine when you want to stop capturing
key strokes. A call to the following function will disable the
keyboard hook and erase any stored key strokes.
long stopRecording(){ long returnVal=0; if(kbdHook){ if(UnhookWindowsHookEx(kbdHook)){ kbdHook=(HHOOK)0; KeyStrokeVector.erase(KeyStrokeVector.begin(), KeyStrokeVector.end()); } else{ returnVal = -1; } } return returnVal; }
If you would rather not erase the contents of the key stroke buffer when you stop recording key strokes, you could remove the line:
KeyStrokeVector.erase(KeyStrokeVector.begin(), KeyStrokeVector.end());
Our callback function is made surprisingly easy through the
use of STL. We check to see if the event was caused by a key press
event, and, if so, we append the corresponding key code to our
list. Our final action is to pass this event along to any other
hook that might be installed by calling CallNextHookEx().
In most cases, ours will be the only registered hook, so the result
will be that the system delivers the key stoke event to VEE's
event processing loop.
LRESULT CALLBACK MyKeyboardProc(int code, WPARAM wParam, LPARAM lParam){ if(code >= 0){ if(!(lParam & (1L << 31))){ /* key is being pressed */ KeyStrokeVector.push_back(wParam); } } return CallNextHookEx(kbdHook, code, wParam, lParam); }
Every DLL has an entry point, called DllMain(), that
the system calls when one of the following events occurs:
Since VEE has only one primary thread, there is no need for
our DLL to pay attention to the thread-related reasons for the
system's calling the DLL entry point. But because the VEE program
may have made use of the key stroke handler, we might have key
codes in the buffer, and we might still have the keyboard hook
enabled, when the VEE process detaches the library. To reclaim
the resources we have allocated, the DLL entry point disables
any active keyboard hook and erases the key code buffer.
BOOL WINAPI DllMain(HMODULE hModule, DWORD reason, LPVOID reserved){ BOOL returnVal=TRUE; switch(reason){ case DLL_PROCESS_DETACH: KeyStrokeVector.erase(KeyStrokeVector.begin(), KeyStrokeVector.end()); if(kbdHook){ UnhookWindowsHookEx(kbdHook); } break; default: break; } return returnVal; }
To take advantage of the keystroke monitoring mechanism, we
will build two HP VEE User Functions: one to retrieve all of the
captured keystrokes; and another to test for the presence of a
particular keystroke in the array of captured values.
Use of these functions presupposes that you have previously
enabled key stroke recording by making a call the function 'recordKeyStrokes',
as depicted in the following illustration.
Because the act of retrieving the array of keystrokes involves
allocating an array in your HP VEE program, we will need to know
the number of keystrokes that have been recorded. Knowing this
allows us to build an array just large enough to hold the data
we have captured. As an alternative, we could allocate an array
of some arbitrary size and loop on the function that retrieves
our data, but the former is less complicated.
A call to the nKeyStrokes function will return the number
of keys currently buffered.
The following figure is a User Function to return to your HP
VEE program the key codes stored in the keystroke buffer.
We make a call to the nKeyStrokes function whose result is used as the input to an Allocate Integer object, if the number of captured keystrokes is greater than 0. The number of captured keystrokes and the newly-created array are the arguments passed to the getKeyStrokes function. The resultant array then propagates from the User Function's sole data out pin.
If you want to see whether the program user has struck a particular
key, you can call the BufferContains User Function shown
below.
Our User Function will return 0 if there are no key codes buffered or if the buffered keystrokes do not contain a key code matching the value supplied as an argument. We create this User Function by first calling our RetrieveStrokes User Function. If there are buffered key codes, we create a reference array having as many elements as there are buffered keystrokes, setting the value of each element to the key code value we wish to find. We do this to ensure that we supply an array of the correct size for the Comparator object's use. Both the reference and key code arrays are fed to the Comparator, whose operator is set to !=. We use this negative logic because we want to know if there is one or more occurrence of our test value in the recorded array. If the array contained values in addition to that which we were seeking, using an == operator would cause the overall test to fail. What we do then is see if any of the recorded values do not equal our test value. If the size of the array propagating from the Failures pin is greater than 0, we know our test array contains at least one instance of the value we are searching for.
We have developed a small demonstration program containing the two User Function we created in this section. It contains only a few objects but illustrates all of our library's capabilities.
VEE Import Library Definition File
You may feel free to use and alter the information in this DLL and the accompanying documentation in any way you wish, so long as you adhere to two simple rules. If you redistribute this DLL, or an altered form of it, you must also distribute the original source and documentation. If you redistribute this DLL, or an altered form of it, you must retain the copyright information.