The HWInputMon sample application ("07 HWInputMon.exe"), shown in Listing 7-1 at the end of this section, exposes keystroke and mouse move performance information. It also includes a sample DLL ("07 HWInputMonPerfInfo.dll"), shown in Listing 7-2, that collects and returns the performance information. The source code for the sample application and DLL are in the 07-HWInputMon and the 07-HWInputMonPerfInfo directories on the companion CD.
HWInputMon creates a performance object named Hardware Input and exposes four counters: Keystrokes, Keystrokes/sec, Mouse moves, and Mouse moves/sec, as shown in Figure 7-12. I strongly suggest that you examine this sample application.
Figure 7-12. The Add Counters dialog box for the HWInputMon sample application
To simplify adding performance objects and counters to HWInputMon, I've created a C++ class named CPerfData. The CPerfData file (PerfData.h) and its supporting files (Optex.h and RegKey.h) are available on the companion CD. CPerfData does all of the really tedious work, such as shared memory management, data structure initialization, memory block construction, addition and removal of object instances, and registry handling. If you use the CPerfData class for your own applications or services, the most difficult part of adding performance information to your code will be deciding which objects and counters to expose.
By referencing the commented source code, you should have no trouble understanding the gritty details of the CPerfData's member functions. However, in order to use this class to add performance counters to your application, you don't need to understand how it works internally; you only need to know how to set up the class.
To create a service (or application) that exposes performance information, you will need two projects: a Win32 Application project that is an executable service or application and a Win32 DLL project that collects and returns the performance information.
Once you have these two projects set up, you create a header file that defines programmatic symbols for the specific objects and counters you want the application to expose. For the HWInputMon sample, this header file is named HWInputPerfDataMap.h and is shown in Listing 7-3.
You define an object's symbol by using the PERFDATA_DEFINE_OBJECT macro (defined in PerfData.h). This macro takes two parameters: a symbolic name that you can use in your application to refer to this object, and an ID that you also define. You must never repeat an ID number, and IDs must never be 0. You define a counter in exactly the same way, this time using the PERFDATA_DEFINE_COUNTER macro. Any source code modules that change or update a performance counter must include this header file.
Now you create a table indicating which objects and counters your service or application supports. For convenience, I also include this information in HWInputPerfDataMap.h. The table is easily created by using macros that are declared in the PerfData.h header file. These macros create what I call a performance data map. This map is similar to the message maps used by MFC programmers.
To create a performance data map, start with the PERFDATA_MAP_BEGIN macro, which instantiates an array of structures that defines your objects and counters. Follow this macro with one or more PERFDATA_MAP_OBJ or PERFDATA_MAP_CTR macros. Note that the order of the entries in the map is important. Declare the first object, followed by its counters, and then declare the next object, followed by its counters, and so forth.
To declare an object, use the PERFDATA_MAP_OBJ macro. This macro requires seven parameters:
After you've added an object to the map, use the PERFDATA_MAP_CTR macro to add one or more counters for the object. This macro requires six parameters:
After you've added all the objects and counters to the map, finish the map by using the PERFDATA_MAP_END macro. This macro takes just one parameter—the name of your service or application—and is used to create counter information in the following registry key:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ServiceName\ Performance |
This macro terminates the map and creates a global instance of the CPerfData class. This global instance is named g_PerfData, and you'll need to refer to this variable in your code when you want to manipulate the performance objects, instances, or counters.
Notice that the performance data map is placed in a source code file all by itself. This is necessary because both the application and the DLL will have to include this file in their projects.
Let's turn our attention now to some code that demonstrates exactly how to use the CPerfData class's public member functions. Specifically, let's look at the _tWinMain function inside HWInputMon.cpp. (See Listing 7-1.)
In order for the application to expose performance information, the registry must be configured as described earlier in this chapter. CPerfData has a static member function named Install:
void CPerfData::Install(PCWSTR pszDllPathname); |
This function must be passed the full pathname of the DLL that exposes your performance information. In the HWInputMon sample application, _tWinMain calls this function if the user clicks Yes in the Install Performance Counter Data Into Registry message box. To determine the DLL's full pathname, I get the full pathname of the executable file and replace the executable's filename with the DLL's filename—assuming, of course, that the executable and DLL files are both in the same directory.
To remove the performance counter information from the system's registry, call CPerfData's Uninstall function:
void CPerfData::Uninstall(); |
In _tWinMain, I call this function if the user clicks Yes in the Remove Performance Counter Data From The Registry message box. When you are debugging the counters, you might want to have your application install the registry information on startup and uninstall it during shutdown. That way, if you decide to add, delete, or move any of the entries in the performance data map, the registry won't get out of sync.
Once the registry is completely configured, call the CPerfData class's Activate method to tell the CPerfData object to start keeping track of the performance counter information:
DWORD CPerfData::Activate(); |
This function allocates the shared memory block and initializes it with the information contained in the performance data map. An application should call this function only if the performance information has already been installed.
NOTE
Internally, the CPerfData class implements this shared memory block by using a memory-mapped file kernel object. This memory-mapped file is going to be mapped into the service's address space and will also be mapped into the requesting application's address space (MMC.exe or WinLogon, for example). Because these two processes might very well be executing under different security contexts, steps must be taken so that they can communicate with each other using this kernel object.So that the two processes can communicate, CPerfData's internal kernel objects are created using a security descriptor that allows Everyone GENERIC_ALL access. Depending on your needs, you might want to alter this descriptor, but I suspect that most developers will find it sufficient.
Once the performance data has been activated, _tWinMain adds instances to one of its objects. Instances are added by simply calling the AddInstance function:
INSTID CPerfData::AddInstance(BOOL fIgnoreIfExists, OBJID ObjId, PCTSTR pszInstName, OBJID ObjIdParent = 0, INSTID InstIdParent = 0); |
The fIgnoreIfExists parameter tells the function whether or not to add this instance if an instance with the specified name already exists. The ObjId parameter is the programmatic symbol identifying the object that is to get the new instance. The pszInstName parameter is the string name of the instance. The last two parameters allow you to indicate whether this instance is the child of some other object's instance. Most instances do not have parent instances, so you will usually pass just the first three parameters to this function. If the function is successful, it returns an INSTID. This is my own data type and is simply a handle to the newly created instance. If the function fails, -1 is returned.
The CPerfData class has another version of AddInstance:
INSTID CPerfData::AddInstance(BOOL fIgnoreIfExists, OBJID ObjId, LONG lUniqueId, OBJID ObjIdParent = 0, INSTID InstIdParent = 0); |
This version is identical to the first with the exception that it allows you to identify the instance by using a unique ID instead of a string.
Because instances can come and go as your application executes, you should feel free to add new instances at any time. You can also remove instances by calling RemoveInstance:
void CPerfData::RemoveInstance(OBJID ObjId, INSTID InstId); |
Now we get to the fun stuff—changing a counter's value. Two functions exist that allow you to alter a counter's value:
LONG& CPerfData::GetCtr32(CTRID CtrId, int nInstId = 0) const; __int64& CPerfData::GetCtr64(CTRID CtrId, int nInstId = 0) const; |
If the counter value you want to change is 32 bits wide, call GetCtr32; if the counter value is 64 bits wide, call GetCtr64. In debug builds, the source code will raise an assertion if you accidentally call a function that doesn't match the counter value's width. To both of these functions, you pass the programmatic symbol for a counter that you've defined. If this counter is inside an object that doesn't support instances, omit the second parameter. If this counter is contained in an object that does support instances, you must pass the INSTID (returned from AddInstance) as the second parameter.
These functions return either a LONG reference or an __int64 reference that identifies the counter's value inside the shared memory block. With this reference, changing the value of a counter is a trivial undertaking. Here is an example:
LONG& lCounterValue = g_PerfData.GetCtr32(SOME_COUNTER_SYMBOL); lCounterValue = 5; // Make the counter's value 5 lCounterValue++; // Add 1 to the counter's value lCounterValue *= 13; // Multiply the counter's current value by 13 |
What could be simpler! Just sprinkle lines like these through the application's source code whenever you want to change the value of a counter. These lines of code execute very quickly and should not significantly hurt the performance of your application.
Listing 7-1. The HWInputMon sample application
|
Listing 7-2. The HWInputMonPerfInfo DLL
|
Listing 7-3. The HWInput data map
|