In an unmanaged environment, using Windows performance monitors may become tricky. This article proposes an alternative which is both easy and efficient: a Windows messaging based performance monitor.
Windows messaging is a technology that goes back to the early days of Windows, the ability to post messages to/between windows is exposed as part of the basic APIs (Win32 APIs), and plays a major part in the Windows Operating System. This article does not intend to cover the concepts of Windows messaging or how to use it, but rather to suggest how it can be harnessed to help with performance monitoring. More general information on Windows messaging can be found in the "Additional Reading" section below.
Application Defined Message
In addition to standard Windows messages that are used to control various window states like resizing, mouse and keyboard actions, Windows messaging allows the use of "application defined messages". The latter go between the values of
WM_APP through 0xBFFF, and are guaranteed to be unique at any given time; such uniqueness is obtained by a registration mechanism exposed by the
RegisterWindowMessage API. Application defined messages can be used for various objectives, and are especially useful when a lightweight communication channel between multiple applications is of need. There are many advantages for using such a communication channel over a Remoting/DCOM one:
- It has a tiny foot print (no negative effect on performance).
- It does not require any special setup or configuration.
- It does not require that both the client and server are present.
- It is asynchronous.
That being said, there are some limitations too:
- You cannot carry much data on a Windows message; in fact, you can carry very little: the numeric
wParam are all that you can carry. Having said that, that is good enough for performance reporting.
- It is asynchronous, thus, unidirectional.
How Does It Work?
The idea behind a Windows messaging based performance monitor is that applications can post performance meters over the Windows messaging framework, and a client can listen and record relevant messages. As messages are within an application defined range, they will be ignored by both other applications and the Operating System, and if you combine this fact with the fact that they are very lightweight, you get a pretty good performance monitoring solution: it does not require a setup or configuration, does not take disk space, and does not slow your application down.
At the technical level, the solution exploits the two open slots available on the Windows message structure (MSG) to carry the performance meter information; the idea is that every performance meter has a unique, predefined name which is registered using
RegisterWindowMessage and used to identify the meter on both the monitor and application side, while the actual performance "results" are carried on the
wParam members of the MSG struct, which means that every meter can carry up to two results.
The solution can be implemented in any Microsoft based language. However, given the ease of integrating with the Windows performance monitor in .NET based solutions, it is more useful to implement this solution in the older generation languages in which the Windows performance monitor is more challenging to integrate with. The code below demonstrates how this can be used in all three major platforms: C++, VB6, and .NET.
The attached code contains an example of a rudimentary monitor coded in C# and three sample apps coded in CPP, VB, and C#.
Example 1, posting messages in CPP:
void CCPPDlg::LogPerformance(LPCWSTR bsMeterName, int iResult1, int iResult2)
int iMessageID = RegisterWindowMessage(bsMeterName);
PostMessageA(HWND_BROADCAST, iMessageID, iResult1, iResult2);
Example 2, posting messages in VB:
Private Declare Function RegisterWindowMessage Lib "user32.dll" _
Alias "RegisterWindowMessageA" (ByVal lpString As String) As Long
Private Declare Function PostMessage Lib "user32.dll" _
Alias "PostMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
Private Const HWND_BROADCAST = &HFFFF&
Private Sub LogPerformance(ByVal MeterName As String, ByVal Result1 As Integer, _
ByVal Result2 As Integer)
Dim lMessageID As Long
lMessageID = RegisterWindowMessage(MeterName)
Call PostMessage(HWND_BROADCAST, lMessageID, Result1, Result2)
Example 3, posting messages in C#:
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern uint RegisterWindowMessage(string lpString);
[DllImport("user32.dll", EntryPoint = "PostMessageA", SetLastError = true,
CharSet = CharSet.Auto)]
private static extern uint PostMessage(int hWnd, uint Msg, int wParam, int lParam);
private const int HWND_BROADCAST = 0xffff;
private void LogPerformance(string MeterName, int Result1, int Result2)
uint iMessageID = RegisterWindowMessage(MeterName);
PostMessage(HWND_BROADCAST, iMessageID, Result1, Result2);