by Ken Knudsen
Reprinted with permission from Visual Basic Programmer's Journal, May 1999, Volume 9, Issue 5, Copyright 1999, Fawcette Technical Publications, Palo Alto, CA, USA. To subscribe, call 1-800-848-5523, 650-833-7100, visit www.vbpj.com, or visit The Development Exchange.com.
Debugging and monitoring the performance of single- or two-tier applications can be tough. The introduction of one or more additional tiers increases this difficulty many times over.
|
The task is even harder if you program solely in VB. The low-level setup required to add performance-monitoring support to COM components has largely limited their use to Visual C++. C++ has long included a debugging tool for system administrators (SAs), developers, and technical support staff: The Windows NT Performance Monitor (perfmon.exe or PM). This tool runs only on NT, but it lets you analyze the performance of your system or application. For example, it lets you check for potential bottlenecks in several key areas such as CPU usage, disk I/O activity, memory statistics, and network traffic. It also enables an administrator, developer, manager, or anyone else with the proper security rights to monitor a computer locally or from a remote computer.
In this article, I'll show you how to use COM components to connect your VB apps to the PM, enabling your support team, system administrators, and developers to assist one another in debugging and fine-tuning VB apps distributed over many computers and locations. This is a complex and multistep process. I'll begin by outlining the functions of the two main VB components you must use: the performance object (PO) and the counter object (CO). Next, I'll explain how these objects tie into the Windows NT Registry. Unfortunately, you must employ a pair of C++ binaries to create the POs, as well as to facilitate the role of POs later in the process. The first of these binaries is a typelib that supports two critical interfaces; you must implement the methods of these interfaces in the VB POs you create. You must simultaneously employ a Win32 DLL that enables your POs to communicate with the PM. You can create your own POs once you understand this part of the process. The code to implement the solution described is extensive, but I'll discuss only its main aspects in this article.
The approach used for hooking into COM objects builds on a technique demonstrated by Steven Pratschner in an article on MSDN (see the Resources sidebar). Steven has graciously allowed me to use some base DLLs from his C++ example; this enables me to concentrate on the specifics of my implementation rather than what's going on under the hood. You could write your own base DLLs, but this would require considerable effort on your part, and you would need to be familiar with creating IDL filesa relatively advanced technique that requires more in-depth COM knowledge than this article assumes.
Figure 1 Monitor Components Across the Enterprise. Click here |
Figure 2 Register and Unregister Your Components. Click here |
The sample PO (the ActiveX EXE) contains five methods. These methods return the name, help string, data type, and index value of the object. The fifth method, GetCounters(), returns a Collection object and enumerates all the COs this particular PO handles. The PO also serves as the main component when registering a new PO/CO pair with the PM. You typically use multiple COs with a given PO, but the sample app keeps things simple by using only one CO. The CO contains five methods that return the name, help string, current value, data type, and index value of the object. Your VB components implement the methods for the PO and CO, which are available from a typelib called vbpm.tlb (see the sidebar, "What is a Type Library?").
A handful of components make it possible for VB programmers to hook apps up to the PM, including an external typelib, vbpm.tlb; and a Proxy/Stub DLL (P/S DLL) called VBPM.dll, which accompanies the type library. This DLL marshals information back and forth between components. This setup differs from what many VB programmers might be familiar with because type libraries are usually stored within the COM components themselves. Fortunately, all you must do to use the P/S DLL is register it on your system with the Regsvr32.exe. You can find both the typelib and the P/S DLL in the online code that accompanies this article.
Viewing the vbpm.tlb type library with VB's object browser reveals two interfaces: IPerfmonObject and IPerfmonCounter. The PO implements IPerfmonObject, while the CO im-plements IPerfmonCounter. IPerfmonObject and IPerfmonCounter share three methods: GetName, GetHelp, and GetIndex.
The GetName method returns the name for either your performance object or your counter object:
Private Function _
IPerfmonObject_GetName() As String
'Performance Object Name
IPerfmonObject_GetName = _
"VBPerfCounterCreate"
'Or you would return your
'Counter Object name
IPerfmonObject_GetName = _
"MyCounterNameHere"
End Function
This name appears in the Object drop-down box for the performance object and in the Counter drop-down box for the counter object. The GetName method helps you distinguish your components from one another visually. The second method, GetHelp, returns a string that describes what this object does:
Private Function _
IPerfmonObject_GetHelp() As String
IPerfmonObject_GetHelp = _
"Insert your help for " & _
"this counter here"
End Function
Exercise Caution When Indexing
The last interface, GetIndex, might be the most interesting:
Private Function _
IPerfmonObject_GetIndex() As Long
'If this is a Performance Object
'return 0 always
IPerfmonObject_GetIndex = 0
'If this is a Counter Object
'return in increments of 2
IPerfmonObject_GetIndex = 2
'So your next Counter Object Project
'would return 4 from this same
'method and so on.
End Function
This method returns a value of 2 for the first CO object and returns values in even increments of two for each additional CO (see the sidebar, "Define the GetIndex Method").
IPerfmonObject includes two other interfaces: GetCounters and GetNum- Counters. The GetCounters method returns a Collection object to the PM:
Private Function _
IPerfmonObject_GetCounters() As _
stdole.Iunknown
'NOTE: Don't tamper with this
'function
Set IPerfmonObject_GetCounters = _
m_collection.[_NewEnum]
End Function
This code enables IPerfmonObject to enumerate all the counters the current PO controls. The PM can use GetCounters to query your VB CO DLLs and obtain any values from their five methods to update itself. The GetNumCounters method is trivial; it returns the number of COs the PO handles:
Private Function _
IPerfmonObject_GetNumCounters() _
As Long
IPerfmonObject_GetNumCounters = _
m_collection.Count
End Function
IPerfmonCounter handles two other interfaces: Collect and GetDataType. The Collect method retrieves the state of the counter value; the PM uses this value to adjust the graph that represents the current state of your CO:
Private Function _
IPerfmonCounter_Collect() As _
Variant
Dim dataValue As Long
dataValue = 50
IPerfmonCounter_Collect = dataValue
End Function
Finally, the GetDataType method returns the data type of the CO:
Private Function _
IPerfmonCounter_GetDataType() _
As Integer
IPerfmonCounter_GetDataType = _
vbLong
End Function
This method supports only the vbLong data type, but this shouldn't present a problem because you can use the vbLong data type to perform all your monitoring needs.
So far, you've learned what components you need to implement performance monitoring, as well as how and which methods you must use to implement a pair of components you create in VB. You still need to perform a couple tasks before you're ready to tackle the code needed for monitoring your performance objects.
First, back up a key section of the Registry in case something goes wrong while experimenting with your PM components (see the sidebar, "Save and Restore Sections of the Registry"). Second, register the PO component with the PM registering program (VBPerfRegister.exe). Space doesn't permit me to give the complete details for how to do this here, but you can find this app, its source, and a more detailed explanation of how the VBPerfRegister program works here.
The registration process creates an INI file and a header file, naming these files so they correspond with your PO's name. For example, the registration process names the sample's INI and header files VBPerfCounterCreate.ini and VBPerfCounterCreate.h, respectively. The INI file inserts counter object information into the Registry, while the header file lays out structures in memory that are read during the collection processes. You should have only one set of INI and header files for each PO. Fortunately, you never need to modify them manually.
Instantiate Your Objects
The online source includes one more DLL you must address before you can implement the PO and CO objects: VBPerfCounterCreate.dll. This DLL runs in the Perfmon.exe address space. The PM uses this DLL as an intermediary step to gather information from your CO components. This DLL performs all the instantiating and information collecting for the PM. Note that the name of the DLL matches the name of this article's example PO ActiveX executable. This isn't a coincidence.
I set up the registration procedure to look for a DLL named after a PO because every PO on your system requires a unique C++ DLL that the PM loads and uses to request information from your PO/CO pairs. The easiest way to do this without resorting to C is to rename a unique C++ DLL the PM looks for after the name of your PO component. Simply copy the article's default C++ DLL (VBPerfCounterCreate.dll) from the command line, giving it a name appropriate to your new PO:
copy VBPerfCounterCreate.dll
<your new PO name>.dll
Next, move the VBPerfCounterCreate.dlland other unique DLLs you create that correspond to your PO object-into the <system>\system32 directory of your O/S. This DLL is a regular Win32 DLL, not a component, so running regsvr32 on it won't accomplish anything.
You're almost home. All that remains is to create the performance and counter objects themselves (see the sidebar, "Common Elements for Making a PO/CO Project"). As is often the case, the coding itself proves to be the easiest part.
Create the performance object first. Start by including the Sub procedure Class_ Initialize() in your project:
Private Sub Class_Initialize()
Set m_collection = New Collection
'NOTE: You must instantiate all
'Counter objects with CreateObject
'(or Late Binding). You can't use
'Early binding to create the
'Counter Objects.
Dim aCounterOne As Object
Set aCounterOne = CreateObject( _
"VBCounter.VBCounterObject")
m_collection.Add aCounterOne
'Here is an example if you wish to
'add new counters to the CO object
'Dim aCounterTwo As Object
'Set aCounterTwo = CreateObject( _
' "VBCounterOther.VBCounterTwo")
'm_collection.Add aCounterTwo
End Sub
Instantiate all your counter components in this procedure, adding them to the Collection object the PO maintains locally. Note that you must use late binding to create all COs; using early binding causes the CO program to freeze because VB expects to use type-library marshalling if a component is instantiated with early binding.
Next, fill in the methods of IPerfmonObject (see Table 1). Note that the CLSID in the Registry changes every time you recompile your ActiveX EXE. Unfortunately, marking the component for "Binary Compatibility" has no discernible effect. Creating a counter object requires a similar approach. Start by filling in the methods of the IPerfmonCounter (see Table 2).
Figure 3 Monitor Your VB Objects. Click here |