October 1, 2008
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 at 8:31 AM | Comments (5) | TrackBack
September 26, 2008
Displaying a Splash Screen with C++ (Part III)
This is Part III of a series on creating a splash screen application in native code. For more information, see the Introduction and the Code License.
Part III: Launching the Application
Now that the splash screen is displayed, we need to launch the actual WPF application. This part is fairly straightforward: we just need to build the path to the executable file, then call CreateProcess to execute it.
HANDLE LaunchWpfApplication()
{
// get folder of the current process
TCHAR szCurrentFolder[MAX_PATH] = { 0 };
GetModuleFileName(NULL, szCurrentFolder, MAX_PATH);
PathRemoveFileSpec(szCurrentFolder);
// add the application name to the path
TCHAR szApplicationPath[MAX_PATH];
PathCombine(szApplicationPath, szCurrentFolder, _T("App.exe"));
// start the application
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
PROCESS_INFORMATION pi = { 0 };
CreateProcess(szApplicationPath, NULL, NULL, NULL, FALSE, 0, NULL, szCurrentFolder, &si, &pi);
return pi.hProcess;
}
This method returns a handle to the launched process; we will use this in the next installment to quit the splash screen application if the WPF application exits early (presumably due to an unexpected failure). I'll also show how to create an event that the WPF application can use to signal to the splash screen application that the WPF UI has been displayed and that the splash screen should be closed.
The CreateProcess documentation says that the handles in the PROCESS_INFORMATION structure must be closed when they are no longer needed, but the PROCESS_INFORMATION documentation lets us know that they will automatically be closed when the splash screen process exits; thus, there is no need to specially track the thread handle just so that it can be closed.
Posted by Bradley Grainger at 5:09 PM | Comments (0) | TrackBack
September 25, 2008
Displaying a Splash Screen with C++ (Part II)
This is Part II of a series on creating a splash screen application in native code. For more information, see the Introduction and the Code License.
Part II: Displaying the Window
In Part I I showed how to create a HBITMAP with the splash screen image. This installment will show how to create a window that displays that image.
Each Win32 window needs a window class, and the splash screen window is no different. The window class for the splash screen window will be fairly standard, although the interesting thing is that we can use DefWindowProc as the WndProc because we don't actually need special processing for any window messages. The window class also specifies an icon because I prefer to have splash screens show up in the Alt+Tab list, and it looks better if we provide an icon. (Note that in this code, error handling has been omitted.)
// Window Class name
const TCHAR * c_szSplashClass = _T("SplashWindow");
// Registers a window class for the splash and splash owner windows.
void RegisterWindowClass()
{
WNDCLASS wc = { 0 };
wc.lpfnWndProc = DefWindowProc;
wc.hInstance = g_hInstance;
wc.hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_SPLASHICON));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = c_szSplashClass;
RegisterClass(&wc);
}
When creating the windows, I use an old trick (a hidden owner window) to make the splash screen appear in the Alt+Tab list, but not in the taskbar. If you wanted the splash screen to also appear in the taskbar, you could drop the hidden owner window. If you didn't want it to appear in the Alt+Tab list or the taskbar, you could use the WS_EX_TOOLWINDOW extended window style (and omit the owner window).
// Creates the splash owner window and the splash window.
HWND CreateSplashWindow()
{
HWND hwndOwner = CreateWindow(c_szSplashClass, NULL, WS_POPUP,
0, 0, 0, 0, NULL, NULL, g_hInstance, NULL);
return CreateWindowEx(WS_EX_LAYERED, c_szSplashClass, NULL, WS_POPUP | WS_VISIBLE,
0, 0, 0, 0, hwndOwner, NULL, g_hInstance, NULL);
}
The window now exists, but isn't sized or positioned correctly, and has no content. The UpdateLayeredWindow function can be used to correct all these problems at once. There are several steps to this process:
- Get the dimensions of the splash screen image
- Get the dimensions of the work area on the primary monitor
- Calculate the location of the splash screen (in order to centre it on the primary monitor)
- Create a memory DC that contains the splash image
- Specify a blend function that will use the per-pixel alpha of the splash image
- Pass all this information to UpdateLayeredWindow
// Calls UpdateLayeredWindow to set a bitmap (with alpha) as the content of the splash window.
void SetSplashImage(HWND hwndSplash, HBITMAP hbmpSplash)
{
// get the size of the bitmap
BITMAP bm;
GetObject(hbmpSplash, sizeof(bm), &bm);
SIZE sizeSplash = { bm.bmWidth, bm.bmHeight };
// get the primary monitor's info
POINT ptZero = { 0 };
HMONITOR hmonPrimary = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO monitorinfo = { 0 };
monitorinfo.cbSize = sizeof(monitorinfo);
GetMonitorInfo(hmonPrimary, &monitorinfo);
// center the splash screen in the middle of the primary work area
const RECT & rcWork = monitorinfo.rcWork;
POINT ptOrigin;
ptOrigin.x = rcWork.left + (rcWork.right - rcWork.left - sizeSplash.cx) / 2;
ptOrigin.y = rcWork.top + (rcWork.bottom - rcWork.top - sizeSplash.cy) / 2;
// create a memory DC holding the splash bitmap
HDC hdcScreen = GetDC(NULL);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpSplash);
// use the source image's alpha channel for blending
BLENDFUNCTION blend = { 0 };
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
// paint the window (in the right location) with the alpha-blended bitmap
UpdateLayeredWindow(hwndSplash, hdcScreen, &ptOrigin, &sizeSplash,
hdcMem, &ptZero, RGB(0, 0, 0), &blend, ULW_ALPHA);
// delete temporary objects
SelectObject(hdcMem, hbmpOld);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdcScreen);
}
The beauty of layered windows and the UpdateLayeredWindow function is that the splash window doesn't have to respond to WM_PAINT messages; Windows will paint it (and blend it correctly with the windows below it) by default.
Now that the splash screen is being displayed, we need to launch the actual application (and then dismiss the splash screen); the next installments will cover this.
Posted by Bradley Grainger at 7:53 AM | Comments (0) | TrackBack
September 23, 2008
Displaying a Splash Screen with C++ (Part I)
This is Part I of a series on creating a splash screen application in native code. For more information, see the Introduction and the Code License.
Part I: Creating a HBITMAP
In order to display an image on-screen, we need to have it available as a HBITMAP. The Windows Imaging Component allows us to decode a PNG image into a 32 bits-per-pixel bitmap (with an alpha channel) and extract its pixels into a DIB.
In this code, I'll assume the image is embedded in the resources of the splash screen EXE (using a statement similar to the following in the .RC file):
IDI_SPLASHIMAGE PNG splash.png
The first step is to create an IStream on the resource data. This involves loading the resource, copying its data into a memory buffer, then creating a stream on that buffer. (Note that the following code has been changed for pedagogical purposes; production code could be improved by using smart pointers for the COM interfaces and Windows handles, and by using exceptions to handle error conditions.) COM should have been initialised (by calling CoInitialize or CoInitializeEx) before calling this method.
// Creates a stream object initialized with the data from an executable resource.
IStream * CreateStreamOnResource(LPCTSTR lpName, LPCTSTR lpType)
{
// initialize return value
IStream * ipStream = NULL;
// find the resource
HRSRC hrsrc = FindResource(NULL, lpName, lpType);
if (hrsrc == NULL)
goto Return;
// load the resource
DWORD dwResourceSize = SizeofResource(NULL, hrsrc);
HGLOBAL hglbImage = LoadResource(NULL, hrsrc);
if (hglbImage == NULL)
goto Return;
// lock the resource, getting a pointer to its data
LPVOID pvSourceResourceData = LockResource(hglbImage);
if (pvSourceResourceData == NULL)
goto Return;
// allocate memory to hold the resource data
HGLOBAL hgblResourceData = GlobalAlloc(GMEM_MOVEABLE, dwResourceSize);
if (hgblResourceData == NULL)
goto Return;
// get a pointer to the allocated memory
LPVOID pvResourceData = GlobalLock(hgblResourceData);
if (pvResourceData == NULL)
goto FreeData;
// copy the data from the resource to the new memory block
CopyMemory(pvResourceData, pvSourceResourceData, dwResourceSize);
GlobalUnlock(hgblResourceData);
// create a stream on the HGLOBAL containing the data
if (SUCCEEDED(CreateStreamOnHGlobal(hgblResourceData, TRUE, &ipStream)))
goto Return;
FreeData:
// couldn't create stream; free the memory
GlobalFree(hgblResourceData);
Return:
// no need to unlock or free the resource
return ipStream;
}
Now that we have an IStream pointer to the data of the image, we can use WIC to load that image. An important step in this process is to use WICConvertBitmapSource to ensure that the image is in a 32bpp format suitable for direct conversion into a DIB. This method assumes that the input image is in the PNG format; for a splash screen, this is an excellent choice because it allows an alpha channel as well as lossless compression of the source image. (To make the splash screen image as small as possible, I highly recommend the PNGOUT compression utility.)
// Loads a PNG image from the specified stream (using Windows Imaging Component).
IWICBitmapSource * LoadBitmapFromStream(IStream * ipImageStream)
{
// initialize return value
IWICBitmapSource * ipBitmap = NULL;
// load WIC's PNG decoder
IWICBitmapDecoder * ipDecoder = NULL;
if (FAILED(CoCreateInstance(CLSID_WICPngDecoder, NULL, CLSCTX_INPROC_SERVER, __uuidof(ipDecoder), reinterpret_cast<void**>(&ipDecoder))))
goto Return;
// load the PNG
if (FAILED(ipDecoder->Initialize(ipImageStream, WICDecodeMetadataCacheOnLoad)))
goto ReleaseDecoder;
// check for the presence of the first frame in the bitmap
UINT nFrameCount = 0;
if (FAILED(ipDecoder->GetFrameCount(&nFrameCount)) || nFrameCount != 1)
goto ReleaseDecoder;
// load the first frame (i.e., the image)
IWICBitmapFrameDecode * ipFrame = NULL;
if (FAILED(ipDecoder->GetFrame(0, &ipFrame)))
goto ReleaseDecoder;
// convert the image to 32bpp BGRA format with pre-multiplied alpha
// (it may not be stored in that format natively in the PNG resource,
// but we need this format to create the DIB to use on-screen)
WICConvertBitmapSource(GUID_WICPixelFormat32bppPBGRA, ipFrame, &ipBitmap);
ipFrame->Release();
ReleaseDecoder:
ipDecoder->Release();
Return:
return ipBitmap;
}
Next is to use CreateDIBSection to allocate a DIB that can be written to directly. By setting up a BITMAPINFO structure with the right values, the DIB will be of the same format as the 32bpp BGRA image loaded by WIC, and the pixels can be copied directly from the WIC bitmap to the DIB.
// Creates a 32-bit DIB from the specified WIC bitmap.
HBITMAP CreateHBITMAP(IWICBitmapSource * ipBitmap)
{
// initialize return value
HBITMAP hbmp = NULL;
// get image attributes and check for valid image
UINT width = 0;
UINT height = 0;
if (FAILED(ipBitmap->GetSize(&width, &height)) || width == 0 || height == 0)
goto Return;
// prepare structure giving bitmap information (negative height indicates a top-down DIB)
BITMAPINFO bminfo;
ZeroMemory(&bminfo, sizeof(bminfo));
bminfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bminfo.bmiHeader.biWidth = width;
bminfo.bmiHeader.biHeight = -((LONG) height);
bminfo.bmiHeader.biPlanes = 1;
bminfo.bmiHeader.biBitCount = 32;
bminfo.bmiHeader.biCompression = BI_RGB;
// create a DIB section that can hold the image
void * pvImageBits = NULL;
HDC hdcScreen = GetDC(NULL);
hbmp = CreateDIBSection(hdcScreen, &bminfo, DIB_RGB_COLORS, &pvImageBits, NULL, 0);
ReleaseDC(NULL, hdcScreen);
if (hbmp == NULL)
goto Return;
// extract the image into the HBITMAP
const UINT cbStride = width * 4;
const UINT cbImage = cbStride * height;
if (FAILED(ipBitmap->CopyPixels(NULL, cbStride, cbImage, static_cast<BYTE *>(pvImageBits))))
{
// couldn't extract image; delete HBITMAP
DeleteObject(hbmp);
hbmp = NULL;
}
Return:
return hbmp;
}
Finally, these three functions can be put together to load the PNG image from the EXE’s resources and convert it to a HBITMAP:
// Loads the PNG containing the splash image into a HBITMAP.
HBITMAP LoadSplashImage()
{
HBITMAP hbmpSplash = NULL;
// load the PNG image data into a stream
IStream * ipImageStream = CreateStreamOnResource(MAKEINTRESOURCE(IDI_SPLASHIMAGE), _T("PNG"));
if (ipImageStream == NULL)
goto Return;
// load the bitmap with WIC
IWICBitmapSource * ipBitmap = LoadBitmapFromStream(ipImageStream);
if (ipBitmap == NULL)
goto ReleaseStream;
// create a HBITMAP containing the image
hbmpSplash = CreateHBITMAP(ipBitmap);
ipBitmap->Release();
ReleaseStream:
ipImageStream->Release();
Return:
return hbmpSplash;
}
The next installment will show how to create a layered window that can display this image.
Posted by Bradley Grainger at 7:07 AM | Comments (1) | TrackBack
September 22, 2008
Displaying a Splash Screen with C++ (Introduction)
Even the leanest and meanest WPF application has significant startup costs, especially in a cold start situation. There have been a number of posts describing how to display a splash screen as soon as possible (and a built-in method was introduced in .NET 3.5 SP1), but these solutions still incur the cost of loading a large part of the .NET Framework.
For pure speed, only native code will let you get a splash screen up as soon as the user double-clicks your application icon. (In fact, a splash screen application is so simple that you can further trim startup costs by avoiding the standard Visual C++ runtime libraries.) The following series of posts will detail the steps involved in using WIC and the UpdateLayeredWindow function to display a splash screen (with an alpha channel) using the Win32 API in C++.
Part I: Creating a HBITMAP
Part II: Displaying the Window
Part III: Launching the Application
Part IV: Dismissing the Splash Screen
Update: Stefan Olson posted a Visual C++ project containing all the code in this series (with a few extra features, including fading out the splash screen and the ability to load from any file, not just application resources).
Posted by Bradley Grainger at 4:49 PM | Comments (0) | TrackBack
August 15, 2008
Fix for error C2373 after upgrading to VS2008 SP1
After upgrading to Visual Studio 2008 Service Pack 1, you may receive the following error when compiling C++ code:
C:\Program Files\VStudio9\VC\include\intrin.h(204) : error C2373: '_InterlockedCompareExchange' : redefinition; different type modifiers
C:\Program Files\VStudio9\VC\include\memory(995) : see declaration of '_InterlockedCompareExchange'
This bug was first noted in the comments to the release notes for the Visual C++ 2008 Feature Pack. The Visual C++ team added a workaround in the final release of VC9 SP1; this workaround is buried (as point #13) in the TR1 fixes post.
To fix the problem, define the symbol _DO_NOT_DECLARE_INTERLOCKED_INTRINSICS_IN_MEMORY for the affected project. I found that it was easiest to do this by adding these two lines to StdAfx.h:
#define _DO_NOT_DECLARE_INTERLOCKED_INTRINSICS_IN_MEMORY
#include <intrin.h>
Posted by Bradley Grainger at 4:55 PM | Comments (0) | TrackBack
April 9, 2008
Exception 0xc0020001 in C++/CLI assembly
After reorganising some code in a C++/CLI assembly, I started getting exception "0xc0020001: The string binding is invalid" when shutting down the C# application that loaded that assembly.
When the program was run under the debugger, it would throw the exception from a function in crtdll.c that was processing the DLL_PROCESS_DETACH notification sent to DllMain. The error occurred when attempting to call the function pointer function_to_call (on line 444).
437 /* cache the function to call. */
438 function_to_call = (_PVFV)_decode_pointer(*onexitend);
439
440 /* mark the function pointer as visited. */
441 *onexitend = (_PVFV)_encoded_null();
442
443 /* call the function, which can eventually change __onexitbegin and __onexitend */
444 (*function_to_call)();
445
446 onexitbegin_new = (_PVFV *)_decode_pointer(__onexitbegin);
447 onexitend_new = (_PVFV *)_decode_pointer(__onexitend);
Here's where a feature of the Visual Studio debugger that I hadn't seen before came in very handy. If I set a breakpoint on line 444 and simply hovered my mouse over function_to_call, the debugger tooltip showed the full decorated name of the function, in this case, "_t2m@???__FstaticNativeObject@?1??NativeMethod@@YAHH@Z@YAXXZ@?A0x754dd9c9@@YAXXZ".
Chris Brumme explains error C0020001 and identifies one of the causes as "trying to call into managed code … after the runtime has started shutting down". According to a forum post (about this same error), "t2m" stands for "transition to managed". The information in the decorated function name ("staticNativeObject" and "NativeMethod") was enough to piece together the rest of the puzzle. I had written code much like the following:
#pragma unmanaged
class NativeClass
{
public:
NativeClass() { }
~NativeClass() { }
};
bool NativeMethod()
{
static NativeClass staticNativeObject;
return true;
}
Even though NativeMethod is emitted as native code, the disassembly showed that it registers a managed entry point for the NativeClass destructor (for staticNativeObject) with the atexit function. But by the time atexit ran this destructor (from DllMain when the C++/CLI assembly was unloaded), the CLR had already started shutting down, and the function call failed.
This problem can be solved by removing the static variable. Either make it non-static, or move it to class or file (or global!) scope. (Slightly more complex workarounds may, of course, be necessary depending on the expense or difficulty of initialising the object.)
It seems like the compiler is emitting incorrect code here—it should register a native entry point for the destructor (or call the managed version from AppDomain.DomainUnloaded) instead—so I filed a bug report with Microsoft Connect on this problem.
Posted by Bradley Grainger at 12:08 PM | Comments (0) | TrackBack