Thursday, September 18, 2008

VistaBridge Considered Harmful; An Alternative

I've been playing with VistaBridge for a little while now, and the more I work with it, the more I realize that it's not ready for prime time. If you really look at the source, you'll see that it's filled with to-do's and comments along the lines of "This doesn't work". It tries to detect the presence of the correct version of comctl32.dll using an absolute path. The Visual Studio designer doesn't like some of its components: it won't display a window that uses task dialog progress bars and of the other VistaBridge controls.

Of course, VistaBridge is sample code; it was never intended to be used as-is in production code. Nevertheless, from web chatter it would appear that many developers have tried to use it. Developers should understand that it is unfinished--and apparently abandoned--code, so use it as inspiration only. [Update: not abandoned! See Kate Gregory's comment.]

The other problem with VistaBridge is that it just doesn't work very well.

There are a number of issues, but the biggest problem has been the dreaded "entry point not found" error when trying to call TaskDialog, TaskDialogIndirect, and the other Vista-only APIs. It was well understood that this probem had to do with side by side assemblies: these functions are present only in comctl32.dll version 6, but, for compatibility reasons, Vista will load an earlier version unless you tell it otherwise. The approach most people (including me) have been taking is to use a manifest. This has proven to be tricky, and may not be the right solution anyway, especially if what you're writing is a library: you don't necessarily want to force the entire application to use common controls 6.

The right solution is to push a new activation context when calling one of the Vista-only APIs. The activation context will use the correct version of comctl32.dll while leaving the rest of the application alone, and no manifest is required.

Fortunately, this is easy to do. Some very helpful people at Microsoft (thanks, Scott, Rob, and Anson) pointed me to some complete code that already exists in the MS Knowledgebase. The code from the article (KB 830033) does the trick as is. Even though the article is talking about a different issue (applying theming to Office add-ins), it does exactly what we want, and you can plop it into your application or library exactly as shown.

I've put together a C# class library that supports task dialogs using this technique and posted the Visual Studio 2008 solution here. The solution includes a simple demo app that displays a number of different styles of task dialogs, most of which are defined in XAML window resources. The library has not been through much testing, so use it to learn the techniques only. It's not intended for use in production code.

Tuesday, September 16, 2008

Which Version of a DLL Is Loaded?

While playing with VistaBridge (about which, more soon), I needed to verify that my .NET application was using version 6 of comctl32.dll, which is the first version that supports task dialogs. I found a number of posts about getting version information for unmanaged DLLs, but they all involved getting file version information from the physical file on disk. This isn't a good solution due to side-by-side considerations: Windows might not be using the version of comctl32.dll that lives in the system folder.

I got the answer from the brand new (and very promising) Stack Overflow programming Q&A site: System.Diagnostics.Process.GetCurrentProcess returns a Process object, which has a Modules collection. You can iterate the collection, find the module you're looking for, and check its FileVersionInfo property. This will work, of course, for any module, not just comctl32.

Here's a function that returns the FileVersionInfo for any loaded module:
using System.Diagnostics;
internal static FileVersionInfo GetLoadedModuleVersion(string name)
{
Process process = Process.GetCurrentProcess();
foreach (ProcessModule module in process.Modules)
{
if (module.ModuleName.ToLower() == name)
return module.FileVersionInfo;
}
return null;
}
So if I want to verify that I have comctl version 6:
internal static bool HaveComctl6()
{
FileVersionInfo verInfo = GetLoadedModuleVersion("comctl32.dll");
return verInfo != null && verInfo.FileMajorPart >= 6;
}

Thursday, September 11, 2008

VistaBridge, Task Dialogs, and WPF

Update: problem solved. See here.

Task dialogs and the other new Vista UI features are pretty nice, aren't they? Sure they are, unless you're using WPF, in which case you've probably been frustrated by the lack of a WPF-able managed wrapper. There was speculation that .NET 3.5 would include one, but, alas, it didn't. We'll see what happens at PDC 2008 next month.

There are quite a few .NET wrappers scattered about, but both WPF and .NET developers seem to have been plagued by "entry point not found" problems when the application tries to find the TaskDialog and TaskDialogIndirect functions in comctl32.dll at runtime.

The VistaBridge SDK sample was the one library that promised to be WPF-friendly, but many developers appear to have been having the same problem with entry points using VistaBridge.

Everyone understands, I think, the basic problem: task dialogs require version 6.0 of the common control library, but applications load earlier versions by default (for compatibility reasons, I suppose). Manifests are the answer, but even with a proper manifest the problem persists.

After a good deal of hair-pulling, I finally have VistaBridge working in an application, and I thought I'd pass along what I found. (The following assumes that you've been able to build VistaBridge.dll itself, which is the VistaBridgeLibrary project of the VistaBridge solution.)

First, you need to create a manifest that specifies version 6.0 of the common control library. Here's one (fix the name and description!):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" processorArchitecture="msil"
name="bitsy.exe" type="win32" />
<description>Description of Application</description>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0"
processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
</dependentAssembly>
</dependency>
</assembly>
Call this "MyApp.exe.manifest" (using your app name, of course) and put it in the base folder of your project. Now follow these steps:

  1. If your solution is open in VisualStudio, close it.
  2. If you've previously built your project, delete the bin and obj folders, which you'll find immediately under your project folder. I've found that this step is critical--it won't work if you don't do this, even if you do a full rebuild.
  3. Copy VistaBridge.dll to your project folder.
  4. Open the project in VS.
  5. Add the manifest file to your project.
  6. In the Application tab of your project properties, select your manifest from the Manifest dropdown.
  7. Set the Copy to Output Directory property of the manifest file to "Copy always".
  8. Add a reference to VistaBridge.dll (right-click References, choose Add Reference, open the Browse tab).
At this point you should be able to use the library. There are examples of how to do this in the VistaBridgeDemoApp project. (The task dialog definitions are in the window resources in Window1.xaml.)

For completeness, I should add that there is one issue: the designer seems to have significant problems with some of the classes, including TaskDialogProgressBar and others. If you use these, the designer won't load the window, but it will still compile OK. That's been my experience, anyway. Maybe someone more experienced can take the next step and find out why this isn't working.

Monday, September 8, 2008

Where's My Command Line?

When I first looked for the command line in a WPF application, I couldn't find it anywhere. A web search found considerable discussion of this; some of the techniques worked, some didn't, and some were much more complicated than necessary. It's really not that hard.

To start with, we need someplace to put the command line parameters. I use a global static List<string> for this. I know, globals are bad, and that's true--but in my view, it is perfectly OK to use a very small number of global variables for things that you know you'll only need one of in an application. If you don't want to do this, that's fine. Use a field/property in your main window or add it to your Application class. But here's what I have in Globals.cs:
namespace MyApplication
{
public static class Globals
{
public static List<string> Args;
}
}
Now I can reference my arguments via Globals.Args.

Filling the list is simple. All you have to do is override Application.OnStartup in App.xaml.cs and look at the System.Windows.StartupEventArgs parameter. StartupEventArgs contains an Args member, which is an array of strings containing your command line parameters. Like this:
namespace MyApplication
{
public partial class App : Application
{
...
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Globals.Args = new List();
for (int ix = 0; ix < e.Args.GetLength(0); ++ix)
Globals.Args.Add(e.Args[ix]);
}
}
}
Now you have a nice list of parameters. The number of parameters is Globals.Args.Count, and the first argument is Globals.Args[0]. What could be easier?

Wednesday, September 3, 2008

What's All This?

Call me crazy.

I've spent the last month or so learning C#, .Net, and WPF all pretty much simultaneously. Now, I'm a very experienced C++ programmer, but this has been a lot to absorb in not much time.

Most of what I've needed has been pretty well covered in one or the other of the six tons of books that are now bending my shelves, but there have been things that I really had to dig for.

My aim here is to cover some of these so that maybe it will be easier for you young'uns.

And if you not-so-young'uns find something really stupid, add a comment. Like I said, this is all new to me.