Tuesday, September 11, 2007

[VC++] CreateProcess out of Focus - The Mess is on

Problem: After launching an external application using CreateProcess, the application goes out of focus instead of displaying as foreground app as desired.
Resolution: Either the new application is not allowed to set itself as foreground process (so use AllowSetForegroundWindow to solve it) or something in your main app (the one that is launching the external) is setting the focus back to itself after the "slave" app is launched (if your app is big the shit is on).

We obviously talking about "slave" applications that are supposed to display in foreground.

I am trying to get into a new project which is huge and old (VC++6 rocks) and fixing some minor bugs in the meanwhile.
Desired behaviour was the following: Click on a button, a third-party application comes up.
real world behaviour: click on button, app comes up, then it goes out of focus; click again, everything works fine and so on.
In first instance I thought something was happening after the first click, something that was telling the app not to come back on focus from the second click on, maybe a global variable being set as a flag or something like that; but everything in the OnClick handler was fine and the trace of our third-party app having been launched was not being stored anywhere.

void CmyFrm::OnBtnClick()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
memset(&pi, 0, sizeof(pi));

if (!CreateProcess(m_strPath, NULL, NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE CREATE_SEPARATE_WOW_VDM, NULL, NULL, &si, &pi))
{
AfxMessageBox("Could not start the application.");
}
else
{
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
}

As once the application was lanuched that was it, it had to be something in my code that was bringin back my main app on focus. Nasty situation in a huge project you just started working on. Rambling on some forums I made acquaintance with a guy called AllowSetForegroundWindow, a User32.dll function that allows a process to set the foreground window. This should be the default condition, so I wasn't too sure about this, but some buddies on a forum tried to convince me that it must be the reason: my third-party app for some reason didn't have the right to seto the foreground stuff. Let's go for it, I said, but unfortunately I didn't have the last VS6 SDK compatible with Win2K, so I had to dowload it(http://www.microsoft.com/msdownload/platformsdk/sdkupdate/psdk-full.htm - how the hell I found this is a mistery, noway to google it) and install it before using the AllowSetForegroundWindow function. Useless to say I lost hours before discovering I could've used that function directly from the dll:

DWORD (WINAPI * AllowSetForegroundWindow)(DWORD pid) = (DWORD(WINAPI*)(DWORD))
GetProcAddress(GetModuleHandle(TEXT("user32.dll")), "AllowSetForegroundWindow");
if(AllowSetForegroundWindow)

AllowSetForegroundWindow(pi.dwProcessId);

Anyway, it didn't solve anything (even if it appears to be a common cause of this kind of behaviour). Every thing was still messed up.
I started roaming through the code and looking for some clue or idea, but nothing. I ended up with the idea that somewhere in the frame or in the mainWindow the focus was being set generating this crappy issue.
I was just banging my head and then it came, out of nothing: I noticed that when the mainwindow was coming back on focus the button just clicked in order to run the other application was no more on focus, another button was instead: the devilish help button.
From that point on it was eventually easy to set up 100 breakpoints (what a mess when I had to take them off - no idea about how to remove all breakpoints in VS6, so if someone knows you're welcome!) on every occurrence of m_HelpButton.SetFocus().
With my surprise the guitly method was the override of the Main Window OnPaint handler, where for some reason the focus was being set on the help button if lost and some other strange duties were being performed (font settings and stuff like that).

void CMainWindow::OnPaint()
{
CPaintDC dc(this); // device context for painting
if(GetFocus() == NULL)
{
if(&m_BtnHelp != NULL)
{
m_BtnHelp.SetFocus();//commeting this line solves the problem
}

}
SetWindowText(m_strVersion);
m_AMember.SetFont(&m_fontAMember);

}

Quoting one of the forum buddies (God bless them) "Messing with the focus in OnPaint is the ultimate stupid thing... The same is to setting there a font to anything. And setting a window text there doesn't look like being smart either. Doing the things where they don't belong must result in hardly predictable behavior. I even don't try to guess why that happens. Or why did that even work." Now, I wouldn't blame the guy who did this, and I cannot complain either, because I do myself shit like that and worse all the time, but I'd sure take a note on this.

Anyway, I solved it (after messing a while with the FindWindow function) with this line:

WaitForSingleObject( pi.hProcess, INFINITE );

inserted before closing the handlers for the new process.
This is basically setting a delay (second parameter) giving time to CreateProcess to do all its stuff (another thing I learned from this is that apparently CreateProcess does a shitload of operations before actually executing your guy). if you set the parameter to INFINITE as I did the function will return only when the application quits. For instance, you could set it to 10000 to give ten seconds to CreateProcess to set up and run what it needs to (one could do the same with Sleep(10000), but then there would be no relation with the process).

Unsolved: the focus was being set on the OnPaint Event Handler, so why the second time you clicked the button it was displaying fine (I can witness the breakpoint was being hit)? No idea.

4 comments:

Anonymous said...

I bet you a Nerd and you proud of it

Anonymous said...

I bet you a Nerd and you proud of it

Unknown said...
This comment has been removed by the author.
Unknown said...

I am trying to write a program which opens a notepad application on windows XP machine. I have used the createprocess() function to acheive this. If i open the command prompt and run my program, the note pad application opens up without any problem.

I tried run the same application as a service it displays a part of the GUI for note pad. I have passed on "winsta0\default" as the desktop with the startup parameters to the createprocess application. The user who is running the service is same as the loggedon user.

I tried to run the same executable on a win2k machine as a service, the notepad application opens without any problems. Can anyone help me out to solve this problem.

The code looks like this and the input to this would be the application I would be launching ie notepad.exe

I would be thankful to anyone who would helpme out with this.
..................


I also post it at: http://www.developertags.com/devarchived/549/400549-create-process-not-working-correctly.html

Post here or post at developertags, don't email. If you feel you have to mail, revert my....