Introduction

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.

Implementation

Overview


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

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.

Enabling and Disabling Key Stoke Event Recording

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.

Installing the Keyboard Hook

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.

Removing the Keyboard Hook

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

The Callback Function

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

Cleaning Up

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

The VEE Side

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.



How Many Key Strokes?

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.



Reading the Keystrokes

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.

Finding a Particular Keystroke

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.

A Small Demonstration Program

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.



Source Files

KeyBoard.cpp

KeyBoard.h

KeyBoard.def

Make file

VEE Import Library Definition File

KeyBoard.dll

HP VEE Demo Program

Using This DLL In Your Projects

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.

References