Achieving .NET CLR independence of .NET-based add-ins for the VBA editor of Office

Being quite a perfectionist, there was one issue in older versions of my MZ-Tools 8.0 add-in for VBA that was not right for me: it required .NET Framework 2.0, which is installed by default on Windows 7, but not on Windows 8, Windows 8.1 or Windows 10, it’s an optional feature:

NetFrameworkOptionalFeature

So, installing MZ-Tools 8.0 for VBA on Windows 8.0 or higher likely you would get this dialog:

NetFramework20Required

And then the user should download and install .NET Framework 2.0. Needless to say, this is mostly inconvenient and can hurt sales, so I thought it would be nice if MZ-Tools 8.0 for VBA could use whatever higher .NET Framework was installed on Windows (such as .NET Framework 4.0 on Windows 8 or higher).

Before digging on how this can be done, some basics: a .NET Framework is composed of a Common Language Runtime (CLR), which contains the virtual machine for .NET, with the JIT compiler, garbage collector, etc. and a set of libraries. So:

.NET Framework = CLR + libraries

There are three kinds of .NET Frameworks:

  1. .NET Frameworks that provide their own, new, CLR. This has been the case for:
    • .NET Framework 1.0 (provides CLR 1.0)
    • .NET Framework 1.1 (provides CLR 1.1)
    • .NET Framework 2.0 (provides CLR 2.0)
    • .NET Framework 4.0 (provides CLR 4.0)
  2. .NET Frameworks that don’t provide a new CLR, they just provide new libraries. This has been the case for:
    • .NET Framework 3.0 (uses CLR 2.0 of .NET Framework 2.0)
    • .NET Framework 3.5 (uses CLR 2.0 of .NET Framework 2.0)
  3. .NET Frameworks that provide an update of an existing CLR (backwards compatible). This has been the case for:
    • .NET Framework 4.5, 4.5.1, 4.5.2, 4.6, etc. (all of them update the CLR 4.0).

A native process (such as an Office app) must load a CLR in order to load a managed (.NET) add-in. CLRs 1.0, 1.1 and 2.0 cannot coexist in the same process, that is, once of those CLRs is loaded, no other CLR version can be loaded in the same process. However, CLR 4.0 can coexist with CLR 2.0 in the same process.

Note: from this point, I will talk only about CLR 2.0 / CLR 4.0 (.NET Framework 2.0 or higher).

So, there are two aspects that an add-in for VBA must consider:

  • The lowest .NET Framework that it requires, which depends on the libraries used by the add-in. MZ-Tools 8.0 uses WinForms (not WPF), so it can work on .NET Framework 2.0 (it doesn’t require libraries from .NET Framework 3.x or 4.x).
  • The lowest CLR that it requires, which depends on the lowest .NET Framework that it requires. Since MZ-Tools 8.0 requires .NET Framework 2.0, the lowest CLR is 2.0.

Now the question is: given a native process that must load a .NET-based add-in, which CLR version does it use?.

An .exe process can have an .exe.config file that specifies the supported/required runtimes of the .NET Framework. Visual Studio uses such .config files but Office apps don’t, so they don’t care about CLR versions (they can use CLR 2.0, CLR 4.0, or even both at the same time). By no means an add-in should install an .exe.config file to modify the behavior of an Office application regarding supported/required CLRs, because it can break other add-ins.

However, there are still two approaches that an add-in written against .NET 2.0 (or .NET 3.x, which also uses CLR 2.0) can use to work with CLR 2.0 on systems that have it installed (such as Windows 7) and with CLR 4.0 on systems that don’t have CLR 2.0 installed but have CLR 4.0 installed (such as Windows 8, Windows 8.1 or Windows 10):

The first approach is to make the setup to detect which CLR versions are installed on the system:

  • If CLR 2.0 is detected, register the .NET assembly for COM Interop setting the Runtimeversion value of the InprocServer32 key to v2.0.50727, as it would be the usual since it’s an assembly compiled against .NET 2.0.
  • If CLR 2.0 is not detected and CLR 4.0 is detected, set the Runtimeversion value to  v4.0.30319. This forces the assembly to use .NET 4.0 even if it was compiled against .NET 2.0:

ComInteropRegistration

Note: to register the assembly for COM Interop, the setup cannot use regasm.exe, not even the version of .NET 4.0 (C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe) because regasm.exe sets always the Runtimeversion value to the value of the CLR that the assembly was compiled against. Instead, the setup must create the registry entries for COM Interop directly (see the required entries here).

The second approach is to use a COM Shim, which I explained in Isolating .NET-based add-ins for the VBA editor with COM Shims. Since a COM Shim is a native C++ dll that loads the CLR to create a custom AppDomain and to load the assembly, it can decide which CLR version to load, depending on which CLRs are installed on the machine: if CLR 2.0 is installed, it loads it, and if CLR 2.0 is not installed but CLR 4.0 is installed, it loads CLR 4.0.