CLOG

A coder's BLOG from Todd S. Murchison

Recent posts

Disclaimer

The opinions expressed herein are my own and do not represent any other individual or organization.

© Copyright 2012

Windows Mobile 5 MSI Installers With CF2

I’ve recently jump through a number of hoops to put together an MSI install package for a Mobile 5 application that uses the Compact Framework 2.0. The goal was to provide a single file installation that includes the CF2 install CAB and the CAB for our application.

Microsoft actually provides a very helpful MSDN article on "Deploying .NET Compact Framework 2.0 Applications with .cab and .msi Files" ( http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetcomp/html/deploy_cf2_apps_cab_msi.asp). There are just a few things I’d like to add from my own experience.

1. Making your installer wait for ActiveSync to finish.

Part of the custom installation code you will be writing for the MSI is code to launch ActiveSync to have it deploy the various CAB files out to the connected device. The MSDN article provides good sample code for this. The only problem I see with the sample code is that it fires off ActiveSync and returns immediately. What this means is that your MSI desktop installer process will finish and say it’s done with the installation before ActiveSync has actually deployed to the device.

The fix for this is very simple. After launching ActiveSync simply wait for the process to exit. In C# this would look something like this:

System.Diagnostics.Process p = 
        System.Diagnostics.Process.Start({exe}, {args});
p.WaitForExit();

In C++ it takes a few more lines of code, but what you’d want to do is make a call to CreateProcess() filling in a PROCESS_INFORMATION structure. Then make a call to WaitForSingleObject(). As a note, I was unable to get CreateProcess() to work correctly until I gave it NULL for the exe and simply passed in the entire command line via the command line argument.

2. Getting the installer to correctly deploy CF2 and then your own CAB.

In the MSDN article we are told that the INI file used to register an application CAB with ActiveSync can contain multiple CAB files on the "CabFiles" line.

"Line 7 enumerates all of the .cab files that are used for this installation in a comma-separated list."

This may work for installations that do not require a reboot of the mobile device, but I was unable to get this to work for deploying CF2, rebooting, and the deploying our application CAB. The solution I found that works quite nicely is to actually launch the ActiveSync process once for each CAB to be installed. So, in my case I created two different INI files (one for each CAB) and then in my custom install code I simply sequentially launch ActiveSync twice (once for each INI file). So, my custom install code runs ActiveSync to deploy the CF2 CAB, waits for the process to finish, and the runs ActiveSync again to deploy our application CAB.

3. C#, C++, and desktop dependencies.

The last quark I had to muddle through involved making sure that our MSI installer did not create additional dependencies on the desktop computer. The MSDN article provides all sample code in C#. If you code your custom install actions DLL in C# you will be building a dependency on the .NET Framework into your installer. In order to run the installer the end user would be required to download all 20 some-odd megabytes of the .NET Framework first!

So, I coded my DLL in C++ using the C# sample code as a guide. Unfortunately, I built my C++ code using Visual Studio 2005. Why is this unfortunate, you ask? Because C++ built in VS 2K5 uses version 8 of the CRT. Most machines will not have version 8, so VS 2K5 auto-detects this as a dependency and auto-includes the CRT v8 DLLs in your installer. Cool... but wait.

The CRT v8 DLLs are considered "system components" and are installed into the Windows system directories. Well guess what, only administrators can do that. So, now, probably without even knowing it, you have an MSI installer that can only be run by administrators and can only be installed for "all users".

The solution is simple enough. Build your C++ DLL in an older IDE (Visual Studio 2003 or Visual Studio 6 would work). You can then take the already-built DLL and do the rest of the installer work in Visual Studio 2005.

Good luck with those MSIs!
-Todd

Currently rated 2.7 by 3 people

  • Currently 2.666667/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by toddm on Saturday, October 28, 2006 6:13 AM
Permalink | Comments (0) | Post RSSRSS comment feed

"Minimize Size" can be BAD

Here's a handy tidbit for anyone using Visual Studio 2005 to build C++ DLLs for Windows Mobile 5. I'm working on a DLL and I've written a small C++ test harness using LoadLibrary(), etc. I was seeing some VERY odd behavior while stepping through the code in the debugger. The symptoms seemed to suggest that the code I was seeing on the screen and that I was visually stepping through was different or out of sync with the code that was actually running.

I pulled my hair out over this for a bit, doing cleans, deleting everything in site, making SURE I was loading the version of the DLL I was building, etc. One earlier symptom was an inability to even step into the DLL calls at all! In the end it turned out to be a problem caused by a compiler flag I had changed. Because the DLL is targeted for download to Mobile devices I'm trying to make it very small. So I had right clicked on the project, selected "Properties", selected "Configuration Properties" -> "C/C++" -> "Optimizations", and changed the "Optimization" setting to "Minimize Size (/01)".

It would seem that this plays havoc with the debugger. It's a very handy option for cutting down on the size of your compiled C++, which can matter if you want to target an app for wireless download to mobile devices. For development and debugging I've set the option back to "Disabled (/0d)". This allows the debugger to correctly step through the code, hit breakpoints, etc.

-Todd

Currently rated 4.0 by 1 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by toddm on Thursday, September 28, 2006 7:01 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Unhandled Exceptions. The first code you should always write...

You can find a large number of references on line talking about unhandled exceptions in the .NET Framework 2.0 and registering for the UnhandledException event. I would like to first, point out that this event is available in the Compact Framework 2.0 as well and second, recommend that the first thing you should do in any project is register for it.

In a standard Windows app the code would look something like this:

namespace YourNamespace {
    static class Program {
        [MTAThread]
        static void Main() {
            AppDomain.CurrentDomain.UnhandledException += 
                new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
            Application.Run(new YourAppForm());
        }
        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
            // Log the exception here
            MessageBox.Show(((Exception)e.ExceptionObject).Message, "ERROR");
        }
    }
}

In my opinion, it is a good idea to do this early on in a project (if not first thing). Eventually most projects of any size will end up with situations that muddle the normal chain of exception propagation. Dialog windows, multi-threaded behavior, exceptions not derived from Exception, or just "incorrect" code on your part are just a few things that can confuse exception cases. By handling the UnhandledException event you can guarantee that you maintain an iron grip on exception awareness and ensure that you are logging valuable failure information. As we all know "silent death" sucks.

-Todd

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by toddm on Monday, August 28, 2006 6:39 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Windows Mobile 5, CF1, and the camera dialog

So, you're coding a Mobile 5 app that uses Microsoft's canned camera dialog (because let's face it, DirectShow is not much fun), but you want to do most of your work in Compact Framework version 1 (let's all thank Microsoft for not shipping CF2 in time to be included with most mobile devices). Well, this can easily be done with a small amount of C++ code and a bit of interop.

The first thing you need to know is that, although Microsoft didn't get CF2 onto most Mobile 5 devices, they DID get that Camera Dialog in there. So the camera dialog is actually available on all Mobile 5 devices (as far as I know). The next thing you need to know is that the Win32 function you'll need is called SHCameraCapture(). This function is well documented in the MSDN libraries, etc.

The general approach to take is to code up a small C++ DLL to make the SHCameraCapture() call. You'll likely want to wrap the call in your own C++ DLL to simplify the interop calls from CF1. Beware the limitations of interop marshaling in CF1, CF2 interop is MUCH nicer. Here is a bit of sample code that might resemble a couple of the entry points in such a DLL:

//---------------------------------------------------------------------------------------------
extern "C" SCANRCAMERA_API WCHAR* fnCameraCapture(
    WCHAR* szInitialDir, WCHAR* szDefaultFileName, WCHAR* szTitle, int iWidth, int iHeight)
{
    WCHAR* szResultImagePath = new WCHAR[MAX_PATH];
    memset(szResultImagePath, '\0', MAX_PATH);

    HRESULT hResult;
    SHCAMERACAPTURE shcc;
    ZeroMemory(&shcc, sizeof(shcc));

    shcc.cbSize = sizeof(shcc);
    shcc.hwndOwner = GetActiveWindow();
    shcc.pszInitialDir = szInitialDir;
    shcc.pszDefaultFileName = szDefaultFileName;
    shcc.pszTitle = szTitle;
    shcc.nResolutionWidth = iWidth;
    shcc.nResolutionHeight = iHeight;
    shcc.Mode = CAMERACAPTURE_MODE_STILL;
    shcc.StillQuality = CAMERACAPTURE_STILLQUALITY_HIGH;

    // Display the Camera Capture dialog.
    hResult = SHCameraCapture(&shcc);

    if(hResult != S_OK) { return(NULL); }
    wcscpy(szResultImagePath, shcc.szFile);
    return(szResultImagePath);
}

//---------------------------------------------------------------------------------------------
extern "C" SCANRCAMERA_API void fnFreeMemory(void *p) {
    delete(p);
}

You will likely notice the odd "free memory" method my DLL is exposing. This is my favored way to make sure I'm dealing with allocated memory correctly while interoping from CF1. It's best to try not to leak too much memory while haphazardly bouncing back and forth between managed and unmanaged code. This will make more sense after having a look at the sample C# code that uses this DLL.

// Import the DLL entry points
[DllImport(@"CF1Camera.dll")]
public static extern IntPtr fnCameraCapture(
    String initialDir, String defaultFileName, String windowTitle, int imageWidth, int imageHeight);

[DllImport(@"CF1Camera.dll")]
public static extern void fnFreeMemory(IntPtr p);

// At some point later use your DLL
IntPtr imagePathPtr = fnCameraCapture(@"\Temp", "test.jpg", "Camera DLL Test", 800, 600);
if(imagePathPtr == IntPtr.Zero) {
    int win32ErrorCode = Marshal.GetLastWin32Error();
    throw (new ApplicationException("fnCameraCapture() failed. Error code: " + win32ErrorCode.ToString()));
}
string imagePath = Marshal.PtrToStringUni(imagePathPtr);
fnFreeMemory(imagePathPtr);

And there you go. You've just used the pre-packaged camera dialog to capture an image from CF1. Like I say, PInvoking from CF2 is a lot nicer. You could work your way through doing a direct PInvoke to SHCameraCapture() from CF1, but I found this approach to be much easier.

-Todd

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by toddm on Friday, July 28, 2006 6:56 AM
Permalink | Comments (0) | Post RSSRSS comment feed