Using Overloaded Win32 API functions to interact with other applications


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.

Declarations – Constants
In 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:

Code:

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.

Declarations – Functions
Once our constants are declared, we can 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:

Code:

Declare Auto Function GetWindow Lib “user32” (ByVal hwnd As IntPtr, ByVal wCmd As Integer) As IntPtr

This declaration tells the runtime that the 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), 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:

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

Each function declaration will “trick” the framework runtime to coerce our parameters from their native format into one that is able to be read / written to by the Win32 API. The recipients of the window message that is sent via the SendMessage API call each expect the parameters to be formatted a very specific way, so a declaration that works for (say) WM_GETTEXT might not work for WM_LBUTTONDOWN, thus the need for three declarations.

Wrapper Functions
Now 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):

Code:

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:

Code:

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 buffer 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):

Code:

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

Using Our Function Wrappers
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:

Code:

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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s