PocketPC Toolkit.com
PocketPC Toolkit.com

Pocket PC Installer Professional - v3.4605
June 2009

Problems using the "wceload.exe" application

Note: these are technical notes for more advanced users.
You don't need to know any of this information to use PocketPC Installer Professional !

Let's start with the basics.

If you want to install a .cab file on a PocketPC device, you simply need to call Microsoft's "wceload.exe" application, and pass it the filename of the .cab file to install. What could be simpler !!

So, to install the SQLCE library file, we could use this C++ code:

LPTSTR CabToInstall = _T("\"\\Temp\\sqlce.ppc.wce4.armv4.CAB\"");
memset(&pi, 0, sizeof(pi));
BOOL bSuccess = (CreateProcess(_T("\\Windows\\wceload.exe"), CabToInstall, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &pi) != 0);


The problem now is that our installer files typically want use "wceload.exe" to to install a .cab file, wait for it to finish installing, then move on to the next file to install. So we need to know when "wceload.exe" has finished running.

So, no problem, we just need to add some code like this:

if (bSuccess)
  if (pi.hProcess != NULL)

Okay advanced C++ users, it's time for a test !

Question: What is wrong with the C++ code above ?

Answer: The answer, unfortunately, is absolutely nothing. The code works absolutely fine 99.9% of the time on all types of Pocket PC devices & Smartphones. But then, sometimes, on some devices, things will go horribly wrong.

Sometimes, "wceload.exe" will install the .cab file, report everything's run fine, but give us no "X" or "OK" button to close the final screen. The user is left with a device which is hung, and needs to be soft-reset.

Microsoft SQL has installed, but, look.. there's no OK button or X to close the window. We're stuck !!

Sometimes, that WaitForSingleObject() call will wait indefinitely. And, again, the user will need to soft-reset their device.


What causes these problems ?
We wish we knew. The internet is full of "wceload.exe" newsgroup articles reporting faults like this, and numerous suggestions to get around this problem, such as running "wceload.exe" on a seperate thread, or using a number rather than the INFINITE parameter on the WaitForSingleObject() call and calling it until it doesn't return a WAIT_TIMEOUT value:

if (bSuccess)
  if (pi.hProcess != NULL)
    const int TIMEOUT = 120;         // Give up waiting after 2 minutes
    const int ONE_SECOND = 1000;

    int counter = 0;
    DWORD ret;
    do {
      ret = WaitForSingleObject(pi.hProcess,ONE_SECOND);
      counter ++;
    while ((ret == WAIT_TIMEOUT) && (counter < TIMEOUT));

Again, perfectly valid code, but after many many hours of investigation (and with help from our patient users) we can miserably report that none of these suggestions are guaranteed to work all of the time.

Using ShellExecuteEx() to run "wceload.exe"

You can read more here about the problems using the INFINITE parameter with "wceload.exe". They recommend that you should use the ShellExecuteEx() command, rather than CreateProcess() to run the "wecload.exe" application.

And they say that if you do this, you will always have a valid Process ID to the "wceload.exe", and will be able to successfully know when "wceload.exe" has finished running.

Once again, we found a scenario where INFINITE was hanging a device, replaced all of the code with ShellExecuteEx(), we even tried to put the whole lot into a separate thread (spot the desperation..) and we were still seeing exactly the same behaviour.

What a waste of an hour, 5 coffees and a bag of crisps.

Close, but no cigar

Actually, the closest solution we'd found was to kick off the "wecload.exe" process, and periodically check whether the wceload.exe process was running using the CreateToolhelp32Snapshot() functions.

So, okay, let's have a look at some code to do this:

BOOL IsWceloadExeRunning()
  BOOL bWceloadExeIsRunning = FALSE;
  const int TH32CS_SNAPNOHEAPS = 0x40000000;
  HANDLE hand = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPNOHEAPS , 0);
  if (hand == NULL)
    // Failed to open CreateToolhelp32Snapshot()
    return FALSE;

  memset(&pe, 0, sizeof(PROCESSENTRY32));
  pe.dwSize = sizeof(PROCESSENTRY32);
  // Search through all of the processes to see if an instance of WCELOAD.EXE
  // is running.
  if (Process32First(hand, &pe))
    do {
      if ( _tcsicmp(pe.szExeFile, _T("wceload.exe")) == 0)
        // Wonderful ! We've found a "wceload.exe" process !!
        bWceloadExeIsRunning = TRUE;
    } while (Process32Next(hand, &pe));
  return bWceloadExeIsRunning;

Okay advanced users, time for another test.

Question: What's wrong with this code ?

Answer: Again, depressingly, the answer is nothing.

99% of the time, this code runs perfectly. But during that other 1%, these Microsoft ToolHelp functions also cause the device to hang. During our lengthy investigations, we have found many instances where using these functions can actually cause even more instability and problems on the device.

Reluctantly, we've stopped the ToolHelp.lib functions in our code.
They simply aren't stable in some circumstances, and the problems are very poorly documented (if at all).


For example, it worries us that when you use the CreateToolhelp32Snapshot() function, you're meant to prevent memory problems by calling it using the "secret" TH32CS_SNAPNOHEAPS parameter, yet Microsoft's own MSDN pages still don't mention this option.

Why ?!

const int TH32CS_SNAPNOHEAPS = 0x40000000;

Ridiculously, you can find this information on the MSDN Newsgroup pages, in response to a desperate user's plea for help:

..but the main CreateToolhelp32Snapshot page on MSDN makes no mention of it (except for our recent posting, asking why this information is missing !)

We find this incredibly sloppy. It's as though Microsoft is waiting for you to waste hours of development time, and bang your head against a brick wall, trying to trace memory leak problems, rather than giving you the information you need to use their libraries safely.

What other "secret" flags aren't they telling us about ?


Our solution

Below is the only solution that we've found, which solves all of these issues. At the time of writing (June 2009), this is the first time this method has been suggested.

1. Kick off wceload.exe in the usual way.

2. Periodically, loop through the list of which windows (rather than processes) are running on the device, using the EnumWindows function.

3. For each window, find out it's process ID using the GetWindowThreadProcessId function, then use GetModuleFileName to find out the filename of that process, and see if it's the "wceload.exe" appication.

4. When this function is unable to find the "wceload.exe" anymore, then "wceload.exe" has finished running, and you can safely move onto installing the next .cab file.


The embedded Visual C++ v4 source code is provided below.
(We choose not to compile our PocketPC C++ code using Visual Studio 2008. Life's too short.)

Note: as a software company, we should keep this information to ourselves, and proudly announce that we're the only installer package which will run on any device/Smartphone. But, given the mountain of unresolved "wceload.exe" issues out there, and with Microsoft seeming to ignore the problem, we are publishing this code to save our fellow developers as many wasted hours as we've suffered.

We'll expect to be added to your Christmas card list, in return.

// Full source code will appear here shortly.

Please do let us know how you get on using this code, and if you have any suggestions/feedback.