Get text from external app VB

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:
Const GW_CHILD As Integer = 5
Const GW_HWNDNEXT As Integer = 2

Const WM_GETTEXT As Integer = &HD

Const WM_SETTEXT As Long = &HC

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:

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:

Public Declare Function SendMessage Lib “user32.dll” Alias “SendMessageA” (ByVal hwnd As Int32, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As Int32) As Int32
Public Declare Function SendMessage Lib “user32.dll” Alias “SendMessageA” (ByVal hwnd As Int32, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As String) As Int32
Public Declare Function SendMessage Lib “user32.dll” Alias “SendMessageA” (ByVal hwnd As Int32, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As System.Text.StringBuilder) As Int32
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):

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

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 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


SendMessage(HandleToFind1, WM_SETTEXT, 0, txtsend.Text)

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


Leave a Reply

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

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

Google+ photo

You are commenting using your Google+ 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 )


Connecting to %s