Don't Depend on SysInfo Control

by Karl E. Peterson


QLose Dependency on SysInfo Control
I'm writing a utility DLL to provide a wide range of functionality to all my applications, much of it system-related. I need access to the sorts of information provided by the Microsoft SysInfo control—in particular, notifications such as when the power mode, screen dimensions, system colors, or device settings change. It seems silly to have my DLL dependent on an OCX. How can I avoid this extra baggage?

A. Much of the information provided by SysInfo properties is available through simple API calls, but you've zeroed in on an area that is a tad bit trickier. The SysInfo-provided event notifications map almost directly to various window messages sent to either all windows or all top-level ones. For example, screen dimension changes trigger a WM_DISPLAYCHANGE to each window in the system, and system color changes cause WM_SYSCOLORCHANGE to be sent to all top-level windows.

Ask the VB Pro provides you with free advice on programming obstacles, techniques, and ideas. Read more answers from our crack VB pros. You can submit your questions, tips, or ideas on the site, or access a comprehensive database of previously answered questions.

The safest answer might be to keep the form on which SysInfo was sited within your DLL, and subclass the form to watch for these system messages. Another answer, more elegant in some ways, is to create your own hidden top-level window when some client first loads your DLL, and destroy the window when your DLL's client is no longer using it (see "Ask the VB Pro" in Resources). Although creating a hidden window might sound a bit daunting at first, it's actually simple. Also, with this method, your DLL gains access to far more system notifications than the SysInfo control provides.

Add a standard BAS module to your project, and add a new routine in which you'll create the custom message sink window. This window is simple and will never be seen, so creating a custom window class makes sense. Start by declaring a variable of the WNDCLASSEX type and set the cbSize element equal to the number of bytes in the structure. Apply CS_CLASSDC to the Style element, because there's no need for an invisible window to have its own device context. The hInstance element receives App.hInstance, and you can assign any string you want to the lpszClassName element. Finally, assign the address of your custom WndProc to the lpfnWndProc element (see Listing 1). The other class definition elements are irrelevant to an invisible window.

Pass the WNDCLASSEX variable to RegisterClassEx to register your new custom class with Windows. At this point, you can create new windows of this custom class using the CreateWindowEx API. You're concerned about only three parameters to CreateWindowEx in this case: the window class, the window title, and the hInstance. Use the same string you used to define the new class for both the lpClassName and lpWindowName parameters, and use App.hInstance for hInstance. Pass 0& for all other CreateWindowEx parameters.

CreateWindowEx returns the window handle (hWnd) of the created window if it's successful. VB's message loop handles dispatching of all incoming messages to your custom window procedure. Define the WndProc (pronounced "win prok") as receiving four incoming ByVal Long values:

Private Function WindowProc _
   (ByVal hWnd As Long, ByVal wMsg _
   As Long, ByVal wParam As Long, _
   ByVal lParam As Long) As Long

Within the WndProc, branch to whatever code you need to handle the messages of interest. Typically, an application returns zero from WndProc when it handles a message explicitly, but this rule of thumb doesn't work well with the sorts of system messages for which you wrote this sink. Always look up the message of interest to be sure you're handling it correctly. For instance, returning zero in response to WM_QUERYENDSESSION can prevent Windows from shutting down properly.

When you're through with the message sink—in your case, when the library is shutting down—you should perform two simple clean-up steps. First, send a WM_CLOSE message to your custom window. Then, call UnregisterClass to clear the custom class definition from memory. In other situations, such as in a form-based application, using the techniques shown here requires you to do some form of reference counting, because it makes sense to have only a single message sink and you don't want to destroy the message sink prematurely (see Listing A).

QReturn an Exit Code
I need to write a few utilities to be called from batch files, and—ideally—to return their results through what used to be called the "DOS errorlevel." Can I do this with VB?

A. Yes indeed, you can return an exit code—the Windows term for these return values—by calling the ExitProcess API. This tip submitted by Peter André was published in the 9th Edition of VBPJ's 101 Tech Tips for VB Developers. However, I need to point out a couple caveats.

ExitProcess is the API equivalent of End. Never call either except as the last line in your Sub Main. Considering that caution, it's clear End is useless. However, ExitProcess has the redeeming quality of offering you the ability to return an exit code. All good things come with a cost, though. ExitProcess is so efficient that it not only ends your application, but it also shuts down the VB IDE if your app isn't compiled. I play it safe by calling it like this:

If Compiled() Then
   Call ExitProcess(nMsg.wParam)
End If

What's that mysterious Compiled function? This function is another of the real beauts from the 6th Edition of 101 Tech Tips (by Jeffrey Sahol; see Resources). Compiled relies on the understanding that Debug.Print won't execute in an EXE. I've modified the original submission slightly here:

Private Function Compiled() As Boolean
   ' Determine if running from EXE/IDE.
   On Error Resume Next
   Debug.Print 1 / 0
   Compiled = (Err.Number = 0)
End Function

The final admonition on ExitProcess is one I learned the hard way while writing this column. The previous question prompted me to rewrite Charles Petzold's classic HELLOWIN program using pure VB. Naturally, I wanted to enhance it to also return an exit code. Long story, short: If you compile to p-code, the VB runtime GPFs, though the exit code does get through. Always compile to native code when you use ExitProcess to return an exit code.

QUse AddressOf Directly Within VB
I know I can pass a function address directly to an API using AddressOf, but how can I provide the same function address within a structure that's passed to an API?

A. It's little known, probably because the uses are so rare, that you can use the AddressOf operator directly within VB. You can create your own *Ptr function using the same design as the functions built directly into VB:

Private Function FcnPtr(ByVal Whatever As Long) As Long
   ' Return whatever was passed.
   FcnPtr = Whatever
End Function

Use the return value of FcnPtr to assign a procedure address to a structure element:

.lpfnProc = FcnPtr(AddressOf WndProc)

The situation you describe is the most common scenario where this technique can be useful, although it's possible to use the CallWindowProc API to call other routines by address within your application (see the column by Francesco Balena in Resources).

Karl E. Peterson is a GIS analyst with a regional transportation planning agency and serves as a member of the VBPJ Technical Review and Editorial Advisory Boards. Online, he's a Microsoft MVP and a section leader on several VBPJ forums. Find more of Karl's VB samples at

  Get the original code for this article here.

Updated samples based on this article:

• Ask the VB Pro, "Detect Library Startup and Shutdown" by Karl E. Peterson [VBPJ July 2000]
Programming Windows, Fifth Edition by Charles Petzold (Microsoft Press, 1998, ISBN: 157231995X)
• Black Belt Programming, "Sort Everything with Polymorphism" by Francesco Balena [VBPJ May 1998]
• Black Belt Programming, "Share Data Between Object Instances" by Karl E. Peterson [VBPJ August 1998]
101 Tech Tips for VB Developers, 6th editions
101 Tech Tips for VB Developers, 9th editions