Displaying a Splash Screen with C++ (Part IV)

This is Part IV of a series on creating a splash screen application in native code. For more information, see the Introduction and the Code License.

Part IV: Dismissing the Splash Screen

When the WPF application has initialised and is ready to display its main window, the splash screen needs to be dismissed. The easiest way to accomplish this is by having a named event that the native application creates and the WPF application sets.

Firstly, we’ll create the named event that will dismiss the splash screen. This also has the beneficial side effect that we can prevent more than one splash screen application from running at once. (Note that the WPF application will also need to ensure that only one instance runs at a time, if that is desired. On the other hand, if you want to be able to launch multiple instances of the splash screen and WPF applications at once, each will need to have a unique event.)

// create the named close splash screen event, making sure we're the first process to create it

SetLastError(ERROR_SUCCESS);
HANDLE hCloseSplashEvent = CreateEvent(NULL, TRUE, FALSE, _T("CloseSplashScreenEvent"));
if (GetLastError() == ERROR_ALREADY_EXISTS)
    ExitProcess(0);

Once we know it’s safe to continue running, we can use the code shown in Parts I-III to display the splash screen and launch the WPF application, then wait for the “close splash screen” event to be set or the WPF application to exit; once either of these events happens, it’s time to dismiss the splash screen.

// call LoadSplashImage() etc.

// call SetSplashImage() etc.


// launch the WPF application

HANDLE hProcess = LaunchWpfApplication();
AllowSetForegroundWindow(GetProcessId(hProcess));

// display the splash screen for as long as it's needed

HANDLE aHandles[2] = { hProcess, hCloseSplashEvent };
PumpMsgWaitForMultipleObjects(2, &aHandles[0], INFINITE);

The PumpMsgWaitForMultipleObjects method has not yet been defined. It’s similar (in API) to the Win32 WaitForMultipleObjects function, but it also dispatches window messages as they arrive. Since we have created a window on this thread, running a message pump is essential. We use MsgWaitForMultipleObjects to wait for either of two HANDLEs while also being woken up when a window message arrives. (Note that this implementation is more generic than is necessary in this example: the timeout is always INFINITE, and there’s no outer message loop that would need to reprocess the WM_QUIT message.)

inline DWORD PumpMsgWaitForMultipleObjects(DWORD nCount, LPHANDLE pHandles, DWORD dwMilliseconds)
{
    // useful variables

    const DWORD dwStartTickCount = ::GetTickCount();
 
    // loop until done

    for (;;)
    {
        // calculate timeout

        const DWORD dwElapsed = GetTickCount() - dwStartTickCount;
        const DWORD dwTimeout = dwMilliseconds == INFINITE ? INFINITE :
            dwElapsed < dwMilliseconds ? dwMilliseconds - dwElapsed : 0;
 
        // wait for a handle to be signaled or a message

        const DWORD dwWaitResult = MsgWaitForMultipleObjects(nCount, pHandles, FALSE, dwTimeout, QS_ALLINPUT);
        if (dwWaitResult == WAIT_OBJECT_0 + nCount)
        {
            // pump messages

            MSG msg;
            while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) != FALSE)
            {
                // check for WM_QUIT

                if (msg.message == WM_QUIT)
                {
                    // repost quit message and return

                    PostQuitMessage((int) msg.wParam);
                    return WAIT_OBJECT_0 + nCount;
                }
 
                // dispatch thread message

                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        else
        {
            // timeout on actual wait or any other object

            return dwWaitResult;
        }
    }
}

Lastly, the WPF application needs to signal the splash screen application to close. (Finally, some C# code!)

private void CloseSplashScreen()
{
    // signal the native process (that launched us) to close the splash screen

    using (var closeSplashEvent = new EventWaitHandle(false,
        EventResetMode.ManualReset, "CloseSplashScreenEvent"))
    {
        closeSplashEvent.Set();
    }
}

As long as the same name is used, the event will be shared across the two processes and the splash screen application will exit when the event is set.

On my slow XP computer, the native application displays the splash screen in well under a second, even from a cold start. Sometimes it’s necessary to drop back to native code for small feature areas with strict performance demands. Displaying UI as soon as possible when the application is launched is one of those areas; a few hundred lines of native code can result in a perceived decrease in application startup time and a better experience for the end user.

Posted by Bradley Grainger on October 01, 2008