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.