Overloading Win32API Declarations
Published: 7/23/2005
Using the Win32 API can be a tricky process, one that can be fraught with frustrating trial-and-error sessions. Often, the declaration of the API function itself is key to getting it to work properly in whatever scenario you are using it, and the declarations for a single function can change depending on how you are using it.
A good example of this is the use of the SendMessage function to send window messages to process windows. SendMessage is an incredibly versatile function (and is used "under the hood" by just about everything you code related to UI), and as such can be used to send and receive information from windows within and without the current process. The following code encapsulates several API functions, illustrating the need for several overloaded function declarations depending on on the parameters being sent to the function. Ultimately, our example will use these encapsulated routines to enumerate another application's windows (controls), read the text from each, and send a button click message to the one it determines to be a button in order to simulate a user clicking it.
DeclarationsIn order to communicate with the other application's windows, we will need to define some constants. These constants represent the window messages that we will be sending to the application via the SendMessage API:
Const GW_CHILD As Integer = 5 Const GW_HWNDNEXT As Integer = 2 Const WM_GETTEXT As Integer = &HD Const WM_GETTEXTLENGTH As Integer = &HE Const BM_SETSTATE As Integer = &HF3 Const WM_LBUTTONUP As Integer = &H202 Const WM_LBUTTONDOWN As Integer = &H201
The constants with the prefix ";GW_"; above enable us to enumerate through a collection of child windows, given the handle to the parent window. In our example, we will be obtaining the main handle to the window we wish to interact with via the System.Diagnostics namespace in the .NET framework. The other constants are standard window message values, and will enable us to interact with specific windows (in this case, controls) to read from them, and indicate to them that they should react to our faux user input (WM stands for window message, BM stands for button message).
Now we have to declare our WIN32 API functions. Doing so instructs the .NET compiler what DLL in which to find the function, and indicates how it will be called by our code.
The first declaration is fairly straight-forward, it is a function that will return the handle to a window given its relation to another window:
Declare Auto Function GetWindow Lib "user32" (ByVal hwnd As IntPtr, ByVal wCmd As Long) As IntPtr
This function is located in User32.dll, accepts two parameters, and returns an IntPtr variable containing the handle to the window we are looking for.
Next, we will declare our SendMessage function, which is the real workhorse of this example. Through trial and error (and this is pretty much the only way to do it) working with our code, we have determined that we need three versions of this function, each with parameters declared differently according to how the function will be used in different places by our code:
Declare Auto Function SendMessage Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal Msg As Integer, _
ByVal wParam As IntPtr, ByRef lParam As IntPtr) As IntPtr
Declare Auto Function SendMessage Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal Msg As Integer, _
ByVal wParam As Integer, ByRef lParam As IntPtr) As Integer
Declare Auto Function SendMessage Lib "user32.dll" (ByVal hwnd As IntPtr, ByVal wMsg As Integer, _
ByVal wparam As Integer, ByVal lparam As System.Text.StringBuilder) As IntPtr
Again, this function is located in the user32.dll, but this time we have declared it in three different ways. Each function declaration will "trick" the framework runtime to co-erce our parameters from their native format into one that is able to be read / written to by the Win32 API.
Wrapper FunctionsNow that we have declared our Win32 API functions and constants, we can begin to consume them. The following .NET function "wraps" the GetWindow API function, enabling .NET code to enumerate the handles for all child windows, given the handle to the parent window:
Public Function GetWindows(ByVal ParentWindowHandle As IntPtr) As IntPtr() Dim ptrChild As IntPtr Dim ptrRet() As IntPtr Dim iCounter As Integer 'get first child handle... ptrChild = GetWindow(ParentWindowHandle, GW_CHILD) 'loop through and collect all child window handles... Do Until ptrChild.Equals(IntPtr.Zero) 'process child... ReDim Preserve ptrRet(iCounter) ptrRet(iCounter) = ptrChild 'get next child... ptrChild = GetWindow(ptrChild, GW_HWNDNEXT) iCounter += 1 Loop 'return... Return ptrRet End Function
The following .NET function wraps two SendMessage API calls, reading the text of a window (or the caption of a label or button) and returning the result:
Public Function GetWindowText(ByVal WindowHandle As IntPtr) As String Dim ptrRet As IntPtr Dim ptrLength As IntPtr 'get length for buffer... ptrLength = SendMessage(WindowHandle, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero) 'create buffor for return value... Dim sbText As New System.Text.StringBuilder(ptrLength.ToInt32 + 1) 'get window text... ptrRet = SendMessage(WindowHandle, WM_GETTEXT, ptrLength.ToInt32 + 1, sbText) 'get return value... Return sbText.ToString End Function
The following .NET function wraps 3 SendMessage API calls, and simulates the click of a button, given the window handle to the button:
Public Sub ClickButton(byval ButtonHandle as IntPtr) 'send the left mouse button ";down"; message to the button... Call SendMessage(ButtonHandle, WM_LBUTTONDOWN, 0, IntPtr.Zero) 'send the left mouse button ";up"; message to the button... Call SendMessage(ButtonHandle, WM_LBUTTONUP, 0, IntPtr.Zero) 'send the button state message to the button, telling it to handle its events... Call SendMessage(ButtonHandle, BM_SETSTATE, 1, IntPtr.Zero) End Sub
Now that we have encapsulated our API calls (all of the code to this point can be pasted into a code module in your application), we can write the consumer code that will launch RegSvr32 and then close it immediately:
Public Sub LaunchAndCloseRegSvr()
'launch our process, we will see a dialog box with an OK button...
Dim clsProcess As System.Diagnostics.Process = System.Diagnostics.Process.Start(";Regsvr32";, "; /?";)
'get an array containing the handles to each of the child windows of the dialog...
Dim ptrChildWindows() As IntPtr = GetWindows(clsProcess.MainWindowHandle)
'loop through each child window, looking for the OK button...
For iCounter As Integer = 0 To ptrChildWindows.Length - 1
'grab the current handle to process...
Dim ptrCurrent As IntPtr = ptrChildWindows(iCounter)
'get the window text...
Dim sText As String = GetWindowText(ptrCurrent)
'check to see if this is the button we are looking for...
If sText = ";OK"; Then
'click the button to close the dialog...
ClickButton(ptrCurrent)
'done deal...
Exit For
Else
Debug.WriteLine sText
End If
Next
End Sub
Of course, the LaunchAndCloseRegSvr routine above is not very useful, but serves to illustrate how to encapsulate WIn32 API functions to facilitate cross-process interaction.
NOTE: This entry has been turned into a FAQ at VBCity.com.
Written by Scott Waletzko for Skystone Software.
Copyright 2005-2007 by Echosoft Design Studios, LLC, All Rights Reserved.