Windows programming: The "it works on my machine" syndroma

Share/Bookmark

As a .NET programmers, you might feel free from OS
specific issues. After all, isn’t the CLR a runtime engine that
abstracts the OS? Well that’s the theory, in practice, when deploying
over several years a desktop application on tens of thousands of
Windows machines, we discovered (the hard way) that there can be some
significant environment diff from one machine to another. Here are
some issues we discovered the hard way while developing NDepend. This list is by no mean exhaustive but I hope this will help you anticipate these potential source of problems.

%ProgramFiles% folder protection

Since Vista, program configuration files shouldn’t live side-by-side under the folder %ProgramFiles%\MyProgram. This folder is protected by Windows and cannot be written seamlessly. Configuration files must exist in the dedicated %AppData% folder. While this seems obvious once you get the knowledge, transitioning configuration files from %ProgramFiles% to %AppData% can be painful. For NDepend we did the transition 2 years ago.

UAC protection (User Account Control)

When you programatically start a process or access network or do
anything else OS sensitive, you must verify that your program won’t enter
in a corrupted state because the Windows UAC requests made to the user might freeze it
temporarily.

File

If you are dealing with file system you must be prepared to ALL
sorts of problems. Internally, for NDepend, we had to develop a full
file handling library. For example before writing a file you should
make sure that it is not read-only and that you have Windows user
permissions. Some developers might say that this sort of issue can be
handled with exception management. Personally I prefer to forecast and
prevent problems instead remedy problems effect. If you don’t
rationalize file access issue with a custom library, you will be
reported bugs again and again because of read-only files, missing
files, protected files, blocked files etc.

Btw, concerning the debate exception vs. prevention putting
simply we favor the pattern bool XXX.TryParse() over the pattern try { XXX.Parse() } catch {}. I wrote my stance here: Exception Handling Best Practices.
In our team, we try to prevent all problems before they happen through
some custom libraries. If we forget a case, a user ends up reporting a
bug and we fix it as soon as possible. We then obtain rock-solid
libraries over time that treats properly all exception case. 

Path

IMHO one of the most flawed API (both win32 and .NET) is the path
API. With this API, it is very hard to handle basic operation involving
for example relative paths, or file to directory conversion, not even
mentioning problems that happens with path longer than 256 char. The
main problem is that for both win32 and .NET APIs, a path is
represented through a simple string. But in fact a path is a pretty
complex artifact that comes with forbidden char, absolute/relative
mode, numerous standard scheme, directory/file path, file name and file
extension, UNC path, normalization… Such complexity can only be handled
properly with a set of collaborative classes that consider paths as
they should be: something much more subtle than a raw string . Even
worth, through win32 and .NET API, some standard path operations needs
the underlying file or directory to exist while there is no valid
reason for that! We developed a library to handle common paths
operations and we made it free.  Its main flaw is that strict UNC path aren’t handled so far. We hope to fix that in the mid-term.

Windows 64bits

Most of .NET programmers thing that compiling with the AnyCPU
options will make their .NET program works on both 32bits and 64bits
Windows. This is true only if you are not using unsafe code (i.e
pointers). If you are using unsafe code the options are twofold:
1) Compile under x86 mode only or
2) Compile under x86 mode and x64, then you have 2 redistributables one 32bits one for 64bits
Option
1) forces Windows 64 bits to trigger Wow64 (Windows on Windows 64),
i.e, to run in 32 bits mode on a 64bits processor. For example
VisualStudio runs on Wow64 and this is why there is only one
installation that suits both 32bits and 64bits Windows.
With
option 2) the JIT compiler can target faster 64bits instructions. Thus
option 2) is recommended to harness the 64bits CPU architecture power.
Note that I am not a 64bits expert and what I wrote is my understanding
of the concern after crawling a lot of resources and doing plenty of
experiments on many machines.

Windows Compatibility Application Manifest

Windows Vista and 7 comes with a special compatibility mode,
execution compatibility to older versions of Window. Concretely
right-click an executable, click properties, click compatibility tab,
and choose your compatibility mode. The compatibility mode of each
executable is stored in the registry. Compatibility can be a source of
problems for .NET programmers that want to target XP, Vista and 7.
Windows might popup a pesky “This program might not have installed correctly
popup windows each time your program run. To avoid any problem, you can
declare your application compatible with Vista and 7. The declaration
is made in the app.manifest file as described here Windows 7 Application Compatibility Manifest Will Impact Windows 8. Notice that only executable assemblies can have an app.manifest file, library assemblies cannot have an app.manifest file. As explained, there is not yet any way to prevent the pesky popup on future versions of Windows.

Font

You shouldn’t rely on any font family in your code, unless you check
its availability and have code that can run well even if the font
family is not available. You can also install the targeted font family
at install time (beware here, many Font family are patented). One
problem is that most of developer machine have Office installed and
Office comes with a lot of installed font. We tend to forget that on a
naked Windows machine (without Office) most of font families are not
available. In our team, we used to rely on the very convenient Arial
Narrow that is only installed with Office. Arial Narrow is convenient
because it displays smoothly more text on less width. So far, using MS sans serif as default font family if another font family is N/A seems a seamless choice.

Environment Variable

We discovered the hard way that even the most basic environment
variable such as Application Data aren’t rock solid. Some of NDepend
users reported bugs about non-defined mainstream environment variable.
So don’t assert that such code will return you a non-empty string.

DPI

This one concerns GDI+ and WinForm developers. WPF handles
that nicely. Windows has a customizable DPI mode (Dot Per Inch). The
setting 96 DPI is the default one and all applications work fine with
96 DPI. However, some laptop users prefer having a higher DPI value,
120 typically. And here comes problem because then there are chances
that your Window Forms and GDI+ code displays poorly. So think about
testing your code. Unfortunately, you’ll figure out that some third
party Window Forms and GDI+ library doesn’t handle well higher DPI
mode, and then you’ll feel pretty stuck if you already invested too
much in such library! Btw, more info on DPI mode are available here Where does 96 DPI come from in Windows?

Blocked DLL and EXE

The last unexpected problems we discover is described here: Visual Studio Addin failed to load.
On Windows 7 if you download a zip file with Internet Explorer and
unzip it with Windows zip, dll and exe files unzipped will be marked as
blocked by Windows. Starting a blocked exe lead to a Windows popup
asking the user if she confirms that this exe is harmless. But when it comes
to a blocked VS addin DLL, VS 2010 just cannot load the addin and
display a completely non-informative error popup. Hopefully we found this
convenient library that shows how to unblock a dll or exe
programatically NtfsStream.

Share/Bookmark

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://www.best-registrycleaner.net Best Registry Cleaner

    Excellent post – this should really become a cheat sheet that can be printed out and put on the wall to remind people.

  • http://codebetter.com/members/Patrick-Smacchia/default.aspx Patrick Smacchia

    Link fixed, thanks Alan!

  • http://www.mariopareja.com/blog Mario Pareja

    Patrick,

    This is a great list of gotchas. I’m glad you took the time to recall and put up such valuable information.

    Mario

  • Alan McFarlane

    Very good list.

    BTW a space has crept into the fontblog hyperlink, it needs removed (s/%20″/”/) to not be 404.

  • http://codebetter.com/members/Patrick-Smacchia/default.aspx Patrick Smacchia

    Chris, indeed you are right. Our strategy is to have accurate controls over the TryXXX and we also have a coarse strategy for exception handling. This works well since we experiment that files changing in the middle of an operation is something that almost never happen (in the context of what NDepend does).

  • http://www.tavaresstudios.com Chris Tavares

    You have to be careful about the TryParse analogy when dealing with files. The file system is a global resource, and the state can and will change between the time you check and the time you try something. So you can do the File.Exists check first, of course, but if you really want to be bulletproof you need to handle the FileNotFoundException anyway – some other process could have deleted the file out from under you or locked it or something.

  • http://sergiopereira.com/blog Sergio Pereira

    Registry redirection can definitely be painful. It has bitten me at least twice: http://devlicio.us/blogs/sergio_pereira/archive/2010/03/12/dottrace-3-1-64-bit-disabled-inside-visual-studio-2008.aspx

  • http://codebetter.com/members/Patrick-Smacchia/default.aspx Patrick Smacchia

    Robert, so far we kept the file access library private, mostly because we haven’t taken the time yet to pack it nicely as we did for the path API. But this will certainly happen in the future.

    Mike, I didn’t talk about registry because we don’t use it and I would recommend to not use it to any .NET programmer. I am not surprised that Windows does odd redirections on registry.

  • Mike Lawton

    Excellent summation. I’d also add a hybrid of the folder protection and the 64 bit items: silent redirection by the OS.

    Sometimes files that purport to be in subdirectories of Program Files, don’t actually exist there… they just appear to. They actually exist under the user’s appdata folder, unless you physically move them.

    The OS lies to you, ostensibly for your own good. This caused me no end of pain in dealing with storing application-wide settings for windows services, until I finally got wise and moved the file locations.

    There are also some registry redirections too…. but I’ve mostly seen them in x64 scenarios.

  • http://www.sadev.co.za Robert MacLean

    Excellent post – this should really become a cheat sheet that can be printed out and put on the wall to remind people.

    The file library you mentioned, will that ever be made publicly available?

  • http://codebetter.com/members/Patrick-Smacchia/default.aspx Patrick Smacchia

    Krzysztof , sure localization is also a big pain relative to ‘it works on my machine’. I avoid mentionning it since:

    1) it is a vast topic in itself that I hope most of .NET programmer are somehow aware of

    2) .NEt Fx has (IMHO) pretty good support for localization, this was taken account since its inception

  • http://kozmic.pl Krzysztof Kozmic

    Very nice post, touching very important issues.

    I would also mention all kinds of l10n/i18n issues. As someone living in Poland, I often encounter issues with software that does not take input in correct format (for example we use comma as decimal separator, not a dot) or does not accept standard Polish date format etc. Thing is .NET support for l10n is not that bad – awareness on the other hand, is very low.

  • http://codebetter.com/members/JeremyJeanson/default.aspx JeremyJeanson

    100% agree with you,

    Same combat with “it works on my network”

  • http://www.partario.com/blog/ Tim Robinson

    “Since Vista, program configuration files shouldn’t live side-by-side under the folder %ProgramFiles%\MyProgram” – the rule was the same on NT/2000/XP. What changed in Vista was that users stopped running as admins by default.