FindWindowEx from user32.dll is returning a handle of Zero and error code of 127 using dllimport

teenup picture teenup · Mar 9, 2011 · Viewed 58.6k times · Source

I need to handle another windows application programatically, searching google I found a sample which handles windows calculator using DLLImport Attribute and importing the user32.dll functions into managed ones in C#.

The application is running, I am getting the handle for the main window i.e. Calculator itself, but the afterwards code is not working. The FindWindowEx method is not returning the handles of the children of the Calculator like buttons and textbox.

I have tried using the SetLastError=True on DLLImport and found that I am getting an error code of 127 which is "Procedure not found".

This is the link from where I got sample application:

http://www.codeproject.com/script/Articles/ArticleVersion.aspx?aid=14519&av=34503

Please help if anyone knows how to solve it.

UPDATE: The DLLImport is:

[DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className,  string  windowTitle);

The Code that is not working is:

hwnd=FindWindow(null,"Calculator"); // This is working, I am getting handle of Calculator

// The following is not working, I am getting hwndChild=0 and err = 127
hwndChild = FindWindowEx((IntPtr)hwnd,IntPtr.Zero,"Button","1");

                Int32 err = Marshal.GetLastWin32Error();

Answer

Cody Gray picture Cody Gray · Mar 9, 2011

The code you're trying relies on the captions of the individual buttons to identify them. For example, it uses the following code to get a handle to the "1" button:

hwndChild = FindWindowEx((IntPtr)hwnd, IntPtr.Zero, "Button", "1");

Which specifies "Button" for the name of the window class, and "1" for the name of the window (in the case of a button, this is the same as the caption text displayed on the button itself).

This code worked fine under Windows XP (and previous versions), where the calculator buttons were identified with textual captions. The "1" button had a window name of "1", and thus "1" was displayed as the button's caption.

However, it looks like things have changed under Windows 7 (possibly under Vista as well, although I can't verify this because I don't have access to such a system). Using Spy++ to investigate the calculator window confirms that the "1" button no longer has a window name of "1". In fact, it doesn't have a window name at all; the caption is NULL. Presumably, the new fancy look of the calculator required that buttons be custom drawn, thus the captions are no longer necessary to indicate which button corresponds to which function. The custom painting routines take care of drawing the necessary captions.

Since no button can be found with the window text you specified, a value of 0 (NULL) is returned for the window handle.

The documentation for the FindWindowEx function indicates that you can specify NULL for the lpszWindow parameter, but that this will, of course, match all windows of the specified class. Probably not what you want in this case, as the calculator app has a bunch of buttons.

I don't know a good workaround. Calculator wasn't designed to be "automated" this way, and Microsoft never guaranteed that they wouldn't change its internal workings. That's a risk you take in using this approach to mess with the windows of other applications.


EDIT: The code you've linked to is also wrong in another fairly serious way, even on earlier versions of Windows. It declares the hwnd variable as type int, rather than as type IntPtr. Since a window handle is a pointer, you should always store it as an IntPtr type. That also fixes the ugly cast in the FindWindowEx function call that should have sent up red flags.

You'll also need to fix the declaration of SendMessage so that its first parameter is of type IntPtr.

The code should have been written like this:

IntPtr hwnd = IntPtr.Zero;
IntPtr hwndChild = IntPtr.Zero;

//Get a handle for the Calculator Application main window
hwnd = FindWindow(null, "Calculator");
if(hwnd == IntPtr.Zero)
{
    if(MessageBox.Show("Couldn't find the calculator" + 
                       " application. Do you want to start it?", 
                       "TestWinAPI", 
                       MessageBoxButtons.YesNo) == DialogResult.Yes)
    {
        System.Diagnostics.Process.Start("Calc");
    }
}
else
{
    //Get a handle for the "1" button
    hwndChild = FindWindowEx(hwnd, IntPtr.Zero, "Button", "1");

    //send BN_CLICKED message
    SendMessage(hwndChild, BN_CLICKED, 0, IntPtr.Zero);

    //Get a handle for the "+" button
    hwndChild = FindWindowEx(hwnd, IntPtr.Zero, "Button", "+");

    //send BN_CLICKED message
    SendMessage(hwndChild, BN_CLICKED, 0, IntPtr.Zero);

    //Get a handle for the "2" button
    hwndChild = FindWindowEx(hwnd, IntPtr.Zero, "Button", "2");

    //send BN_CLICKED message
    SendMessage(hwndChild, BN_CLICKED, 0, IntPtr.Zero);

    //Get a handle for the "=" button
    hwndChild = FindWindowEx(hwnd, IntPtr.Zero, "Button", "=");

    //send BN_CLICKED message
    SendMessage(hwndChild, BN_CLICKED, 0, IntPtr.Zero);
}