Archive for August, 2008

Tools: Beyond Compare 3

With my roots in maintenance programming, I’ve used a wide assortment of programming tools. Maintaining legacy code is not much fun. Anything that makes it easier gets added to my personal collection of productivity applications. I make a point of learning them inside and out.

I carry the tools with me from project to project. I recommend them to coworkers. I’ll use them even if my employer provides alternate tools for free. I’ll complain loudly if the company I work for won’t allow me to use them — for policy reasons, consistency reasons, or security reasons. (Most of my bosses eventually concede the point. After all, they’re interfering with my ability to get things done, which is presumably why they hired me.)

Beyond Compare by Scooter Software is one of those tools. It’s the best file comparison tool I’ve ever used. And not just me - It’s also earned praise from other programmers.

When I heard that version 3 was soon to be released, I was both excited and a bit nervous. What new features would they add? Were they going to break any behavior I relied on?

The website lists the new features, but the best new feature is the polished user interface, which makes it easier to move blocks of text and make inline edits to files.

File Comparison in Beyond Compare 3

File Comparison in Beyond Compare 3

And no, they didn’t mess up anything in the process.

I played with the beta for several weeks, and recently bought the full version. If you’re not using it already, you should try it.

Handling COM Error Codes

Sometimes COM Objects return a HRESULT that is outside of the bounds of a C#  integer.

Unfortunately, the only way to correctly handle a COM Exception in .NET is to use the ComException Class. But if the HRESULT isn’t an Int, and the ErrorCode Property on the ComException class is an Int, how are you supposed to ever be able to catch that specific HRESULT?

Fear not! Here’s how to handle a COM ErrorCode that can’t be converted to an Int (in this example, it’s unsigned)

try
  1.  {
  2.   _PropertyHandler.Open(filePath, false, dsoFileOpenOptions.dsoOptionDefault);
  3.  }
  4.  catch (COMException comEx)
  5.  {
  6.   if (comEx.ErrorCode == unchecked((int)0×800300FC))
  7.    {
  8.     throw new FileNotFoundException("Could not find file:");
  9.    }
  10.  }

The magic happens in the ‘unchecked’ statement - which tells the compiler not to perform the overflow-checking context for integral-type arithmetic operations and conversions.

More on the Unchecked Operator over on MSDN.

Creating Dummy Targets for Configuration Objects

The ConfigurationManager class introduced in .NET 2.0 makes it easy to read application settings from an XML file. I especially like the ability to derive a class from ConfigurationSection to hold custom settings for your application. This MSDN tutorial on creating custom configuration sections can help you get started.

I used this to make the configuration files for several of our Infovark add-ins, but ran into a snag with our main API library. In order to interoperate with COM, we had to put out Infovark.Api.dll in the GAC.

This presents a big problem for using *.config files. If your assembly is in the GAC, your configuration file must live in the GAC as well. (By default, configuration files are sidecar files located in the same directory as your *.exe file.) Since the GAC lives in a special place on a Windows machine, it’s difficult to read and write from that location without special permissions. And you can forget about browsing to it using Windows Explorer. This makes it tough for folks to change configuration options, which defeats the whole point of XML-based configuration files.

It’d be nice if we could load the configuration file from an specific spot on the computer. But while the Configuration object has both Save() and SaveAs() methods, there’s no corresponding Load() method. Huh? According to MSDN, the “right” way to point your application at a different configuration file is to create a whole new app domain with the appropriate settings. Um… sure.

How about we just hack up a workaround instead?

Using a dummy target

You can fool the configuration object into loading settings from whatever .config file you want, if you don’t mind a hack or two. The Configuration object exposes an OpenExeConfiguration() method that takes a string. Despite its name, you don’t have to pass it an .exe file. Any file path will do, as long as the path exists.

Since my .dll was in the GAC, I didn’t have a target for the OpenExeConfiguration() to use. I could have pointed it at another .dll — or at a .txt file for that matter — but that wouldn’t be very intuitive. Instead, I created a temporary file without an extension in the location I wanted to save the configuration file. Then I can open a Configuration object using the dummy target. Saving the Configuration object will cause it to write a file named “[configurationTarget].config” to the path I specified. You can see the code I used below.

///
  1.         /// Loads a .NET configuration file using the specified target.
  2.         /// Since configuration files are normally sidecar files, you
  3.         /// normally provide the path to an .exe or .dll file. Unlike
  4.         /// ConfigurationManager.OpenExeConfiguration(), this method
  5.         /// creates a dummy file without an extension to use as its target
  6.         /// if the target file does not always exist.
  7.         ///
  8.         ///
  9. The path and name of the dummy file used as the target.
  10.         /// A Configuration object
  11.         public Configuration LoadConfiguration(string configurationTarget)
  12.         {
  13.             bool useDummyTarget=false;
  14.             try
  15.             {
  16.                 FileInfo fi = new FileInfo(configurationTarget);
  17.                 if (!fi.Exists)
  18.                 {
  19.                     useDummyTarget = true;
  20.                     using (StreamWriter sw = fi.CreateText())
  21.                     {
  22.                         sw.WriteLine("Hi! This file only exists to make the Microsoft .NET framework happy.");
  23.                         sw.WriteLine("It's important because Infovark can't load its configuration file without it.");
  24.                         sw.WriteLine("(Don't ask. It's a long, long story.)");
  25.                         sw.Flush();
  26.                         sw.Close();
  27.                     }
  28.                 }
  29.  
  30.                 return ConfigurationManager.OpenExeConfiguration(configurationTarget);
  31.             }
  32.             catch(Exception e)
  33.             {
  34.                 throw new ConfigurationErrorsException("Unable to load a configuration file using " + configurationTarget + " as a target. See inner exception for details.", e);
  35.             }
  36.             finally
  37.             {
  38.                 // Clean up our dummy file.
  39.                 if (useDummyTarget) File.Delete(configurationTarget);
  40.             }
  41.         }

Once I’ve opened the Configuration object, I don’t need the dummy file any more. I delete it to avoid have weird extension-less files hanging around.

It’s not pretty, but it gets the job done.

It Doesn’t Get Better Than This

Our latest revision got high marks from our source control tools.

We are totally 1337.