Unmanaged deployment is a true DLL Hell

Tags: .NET, C++CLI

By a staggering margin, most of my problems integrating C++/CLI code into my C#-based project has been deployment problems. Without fail, every integration or test deployment will be plagued with inexplicable problems. I'll try to list a few, with their causes and (probable) solutions:

1) .NET assemblies are usually very loose in what they'll bind to. If I compiled A.DLL against B.DLL version 1.0.0.0, it will still work if it can only find B.DLL version 1.2.0.0. I don't have to rebuild and reversion everything every time. However, if my C# A.DLL has a reference to my CPPCLI.DLL v1.0.0.0, and I then put CPPCLI.DLL v1.1.0.0, I'll get an exception saying the referenced DLL wasn't found. Annoying.

Solution: If you're changing the version number of a C++/CLI assembly, make sure to recompile any assembly that references it.

2) C++/CLI can be compiled as a pure .NET assembly, just like C# or VB. I don't really see the point in that, unless I'm a C++ coder and refuse to switch syntax. There isn't a great deal of benefit to that over writing in C#, because I can't reference unmanaged code easily. The standard mode, /clr, allows me to mix both managed and unmanaged code. The problem is that a mixed-mode assembly has a dependency on the Visual C++ Runtime libraries. This means that if the machine I am installing on doesn't have it, I'll crash. Furthermore, if I developed my code on a machine running VS2005SP1, my MSVCRT version will be 8.0.50727.762, but on a machine with vanilla .NET2.0 and the corresponding runtimes, I'll just find 8.0.50727.42. Result? An inexplicable "FileNotFoundException", not saying what's missing.

Solution: Make sure your deployment scenario includes installing the relevant MSVC++ Runtime. VS2005 comes with a Merge Module (msm) so you can add it to any MSI project.

3) The usual reason to add C++/CLI code to a C# project is to act as a bridge to an unmanaged API. Usually this API is composed of various DLLs. These DLLs can come in many forms - some are statically linked to their respective runtimes, some link dynamically. Some expect other DLLs in the same directory, others expect them in the PATH. Many times we'll get cryptic Module Load Failed errors or FileLoadFailedExceptions.

One tool we have for this is Filemon and Procmon, SysInternals' wonderful debugging tools. Open them up, add a filter to weed out the noise and see what files receive a FILE_NOT_FOUND error.

Another indispensible tool is the Dependency Walker (depends.exe) that is shipped with Visual Studio (C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\Bin). This tool will show you all the DLLs that our file has a dependency on. First thing to look for is any dependencies highlighted in red, underneath the tree. These are our missing buddies. Ignore anything that has a little hourglass next to it - these are delay-load modules and probably not the source of the problem.

One thing to note when opening our C++/CLI DLLs in Depends is the dependencies on the MSVC++ Runtime. Look for a dependency on "MSVCP80.DLL". You might find instead "MSVCP80D.DLL" - this means that we're bound to the Debug build of the runtime, which probably doesn't exist on our servers. This usually means that we've compiled the project in DEBUG build, rather than RELEASE.

One last indispensible tool is trusty old Reflector. Open our managed A.DLL that contains a reference to CPPCLI.DLL, and we can see what version it was referenced against. This can help us find problems like I mentioned in section 1.

 

There, that's all I can think of at the moment. Hope it gets some googlejuice and helps someone stuck as I am.

No Comments