9

I have a situation where I'm starting a process in my code in order to set up an IPC channel. The process I'm starting is an MFC application with no CLR support. The application from which I am starting this process is a C# module in a WPF application (thought I don't think that that is consequential to my problem). This works with a version of the application that does support CLR, and it works on every computer except the deployment target, a touch screen computer with Windows 7. But for some reason, when I try it with this exact scenario, the Process object never resolves a main window handle (Process.MainWindowHandle). Is there another (perhaps even pinvoke) method of doing this? Is this a security thing? I'm the one staring the process. The process's main window handle does exist. I don't see what could be wrong.

If it helps, here is my code.

        _applicationProcess = new Process();
        _applicationProcess.StartInfo.FileName = _strProcessPath;
        _applicationProcess.StartInfo.Arguments = _strProcessArguments;
        _applicationProcess.Start();

        long nTicks = Environment.TickCount;
        if (_applicationProcess.WaitForInputIdle(1 /*minute(s)*/ * 60000))
        {
            try
            {
                do
                {
                    // Don't let total processing take more than 1 minute(s).
                    if (Environment.TickCount > nTicks + 1 /*minute(s)*/ * 60000)
                        throw new ApplicationException("MFCApplication.Startup failed! The main window handle is zero!");

                    _applicationProcess.Refresh();
                }
                while (_applicationProcess.MainWindowHandle.ToInt32() == 0);

                _applicationHandle = new IntPtr(_applicationProcess.MainWindowHandle.ToInt32());
            }
            catch (Exception ex)
            {
                //Do some stuff...
                throw;
            }
        }
        else
        {
            // Do exception handling.
        }

The ApplicationException is hit after a minute of trying to get a main window handle other than zero.

Tim Lloyd
  • 37,954
  • 10
  • 100
  • 130
Jordan
  • 9,642
  • 10
  • 71
  • 141
  • 1
    Have you tired using `WaitForInputIdle` as suggested by the MSDN docs? Also it's best practice to use `throw` instead of `throw ex`, as you will lose stack trace information with `throw ex` (`throw ex` restarts the exception throw and therefore stack trace). – Tim Lloyd Jan 18 '11 at 17:32
  • @jordan I see your edit. You need to include @username in your comments so that the other user you are replying to is notified. – Tim Lloyd Jan 18 '11 at 17:35
  • @jordan In your do\while loop I think you are confusing ticks with milliseconds. A tick is a 100 nanoseconds. It's probably more straightforward to use a `Stopwatch` for this. – Tim Lloyd Jan 18 '11 at 17:37
  • @jordan It's probably worth considering putting a `Thread.Sleep` in your loop to throttle your `Refresh` calls. – Tim Lloyd Jan 18 '11 at 17:43
  • @chibacity Nope none of that works. And I didn't think the @ was necessary since you were the only commenter. – Jordan Jan 18 '11 at 18:22
  • Is this a 64-bit version of Windows? – Hans Passant Jan 18 '11 at 19:17
  • I have a theory, I hide the Main window shortly after it is loaded. Could the act of hiding a window demote it from main window status? Just a thought. @Hans Yes it is. – Jordan Jan 19 '11 at 16:03
  • Related: http://stackoverflow.com/questions/3276439/reliable-way-to-wait-for-another-windows-process-to-redraw-its-main-window – finnw Jan 19 '11 at 19:50
  • @chibacity, for the record, according to the documentation `Environment.TickCount` is in milliseconds not ticks. Confusing name, but ticks used to be milliseconds. I do like `Stopwatch` though. – Jordan Feb 02 '11 at 14:19
  • @Jordon I stand corrected, `Environment.TickCount` is indeed in milliseconds and not ticks. That is confusing. Cheers. – Tim Lloyd Feb 02 '11 at 14:22

4 Answers4

6

The value you get out of Process.MainWindowHandle is unfortunately a guess. There is no API function available to a program that lets it tell Windows "this is my main window". The rule it uses is documented, it is the first window that's created by a process when it gets started. That causes trouble if that first window is, say, a login window or a splash screen.

Not much you can do about this, you have to know more about how the program behaves to find that real main window. Enumerating windows with EnumThreadWindows() could help you find it, as long as the first window was created on the same thread as the main window. A more elaborate EnumWindows() will be necessary if that is not the case.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
3

My habit is to call EnumWindows in a loop combined with GetWindowThreadProcessId to find the window handle.

C Code, adapt to your language


DWORD TargetHWND;

//...
    while (EnumWindows(EnumWndProc, (LPARAM)(DWORD)pid)) {
        Sleep(100);
    }


//...

BOOL EnumWndProc(HWND hWnd, LPARAM lParam) {
    DWORD pid = (DWORD)-1;
    GetWindowThreadProcessId(hWnd, &pid);
    if (pid == (DWORD)lParam) {
        TargetHWND = hWnd;
        return FALSE;
    }
    return TRUE;
}
Joshua
  • 40,822
  • 8
  • 72
  • 132
  • How does this help me exactly? I'm trying to get a window handle from another process. Can you provide me with some code? – Jordan Jan 19 '11 at 16:21
  • So basically this gets all of the windows for the given process ID, interesting. – Jordan Jan 20 '11 at 04:06
2

In order to get MainWindowHandle by means of your process, please make sure your WPF application is shown on the taskbar i.e ShowInTaskbar="True" and set Application.Current.MainWindow property to the window that you'd like to set as your main window.

If I execute code below in my WPF main window without setting ShowInTaskbar="True" I always got 0 as the MainWindowHandle because my WPF window was full screen and not displayed on the taskbar.

    Application.Current.MainWindow = this;
    var Query = System.Diagnostics.Process.GetProcessesByName("ProcessName");

    if (Query.Any())
    {
        Query.FirstOrDefault().Refresh();
        MessageBox.Show(Query.FirstOrDefault().MainWindowHandle.ToInt32().ToString());
    }
Demir
  • 1,787
  • 1
  • 29
  • 42
1

I don't know why it could be different, but after you create the process, try doing:

Process[] allProcesses = Process.GetProcessesByName("YourWindowTitle");

and see if any of the processes returned have a MainWindowHandle.

John McDonald
  • 1,790
  • 13
  • 20
  • It is different, but in the opposite way. Getting hold of a process this way, you don't have full trust and so can't do much of anything with it, and certainly not get its handle. Only when you start a process can you interact with it in any meaningful sense. – Jordan Jan 19 '11 at 16:05