A new website about Visual Studio resources

I have launched a new website to help developers using Visual Studio / Visual Studio Code / Visual Studio Team Services / Team Foundation Server:

http://www.visualstudioresources.com/

visualstudioresources

And this is a cross post that explains my motivation to do it:

Back in 1995, when I started to work on a company, Internet didn’t exist as we know it today. At that time, Microsoft supplied its Microsoft Developer Network (MSDN) Library with technical information for developers in a couple of CDs that arrived to the office quarterly:

msdnlibrary

You could read the content of your interest in a few days, and then you didn’t receive more content until the next quarter. There were some printed magazines, but they were monthly and you read them in a few days. Content and resources were scarce. Today, more than 20 years later, the problem is the opposite: there is so many content in so many ways that we have to use search engines to find what we need. Also, the way we learn today is not the way we used to learn 20 years ago, at least not for every developer. Some people still prefer to read the official documentation from the manufacturer. Other people prefer to learn with thick, comprehensive, books. Other prefer small articles and posts. Others don’t like to read so much and prefer videos or courses. Yet others prefer to learn reading code of samples or other apps.

I’ve created this site with the goal of providing a huge directory of resources of any kind to learn about the Visual Studio family: Visual Studio, Visual Studio Code, Visual Studio Team Services and Team Foundation Server. It is not a website about .NET or about programming languages (C#, VB.NET, F#, etc.), but only about the “tooling”, the development environments and the application lifecycle management (ALM) systems that Microsoft provides to developers to become more productive.

This website starts small and modest, but hopefully it will grow in the next months and years to reach the depth of the Visual Studio Extensibility (VSX) that I created years ago for developers extending Visual Studio.

I hope you like it.

Another two strange cases of MZ-Tools 8.0 for VBA could not be loaded

It seems that the path to load a .NET-based add-in in the VBA editor of Office is full of mines. In the last couple of days I have discovered two new scenarios (apart from this and this) that can cause “<Add-In> could not be loaded”. I will document them here for future reference for me or other people:

This first one happens if you are not using a COM Shim, you are using .NET Framework 2.0 and the version of Office is 2000, 2002 (XP) or 2003. It is due to a lockback policy that “prevents the .NET Framework 2.0 common language runtime (CLR) from initializing when the .NET Framework 2.0 is hosted in either the Word process space or the Excel process space. The policy restriction limits Word and Excel from loading versions of the .NET Framework that are later than version 1.1. Therefore, the .NET Framework 2.0 assemblies cannot load”. It is documented in the Knowledge Base article You cannot load a .NET Framework 2.0 assembly from Visual Basic for Applications in Word 2003 and earlier versions, or in Excel 2003 and earlier versions and fortunately has a solution through a registry key HKEY_CLASSES_ROOT\Interface\ {000C0601-0000-0000-C000-000000000046} that can be created. I was aware of this problem since long time ago and my setup created that registry key if Office 2003 or 2002 was detected. However, I noticed that my add-in was failing for Office 2000 (yes, I have users running Office 2000 on old PCs) because I added support for Office 2000 later and I didn’t update the setup to check that version. This case is difficult to diagnose because the following script works perfectly (I mean, the problem only manifests loading the assembly in Office):

Dim o
Set o = CreateObject("MZTools8VBA")

The other problem was also hard to diagnose. It happened when using a COM Shim to load the CLR using code from the COM Shim wizards provided by Microsoft, which calls the CorBindToRuntimeEx function passing NULL to the first pwszVersion parameter. A NULL value means to load the highest CLR installed below CLR 4.0, which should be CLR 2.0. However, on Windows XP I got HRESULT 0x80131700. Fortunately I found on a comments of the post COM Shim CLR Loader Bug that the problem could be solved passing the specific version “v2.0.50727”, which certainly fixed the problem for me.

The strange case of System.InvalidCastException (“Unable to cast COM object of type ‘System.__ComObject’ to class type System.Windows.Forms.UserControl”) showing toolwindow

In my last post I explained a problem that can happen when a .NET-based add-in for the VBA/VB6 editor that uses a COM Shim tries to use any Common Language Runtime (CLR) that it’s installed on the target machine (CLR 2.0 or CLR 4.0), assuming that the add-in is built with .NET Framework 2.0/3.0/3.5 and therefore can use CLR 2.0 or CLR 4.0. This post is about other problem that can happen.

The ideal approach, that was the first that I implemented, is that the COM Shim uses at run-time the first detected CLR that it supports. In my case the logic was:

  • First it detects if CLR 2.0 is installed. If so, use it.
  • If not, it detects if CLR 4.0 is installed. If so, uses it.

This would be the best approach because at any given time, it uses the available(s) CLRs. The default CLRs installed by Windows are:

  • Windows XP: Neither CLR 2.0 nor CLR 4.0
  • Windows Vista, Windows 7: CLR 2.0
  • Windows 8, Windows 8.1, Windows 10: CLR 4.0

And life would be great if that were the whole picture. However, most add-ins need to create and show toolwindows. For a .NET-based add-in, this is somewhat tricky because toolwindows need ActiveX UserDocuments, and .NET doesn’t support UserDocuments. However, a UserControl can be used instead with some tricks as I explained in HOWTO: Create a toolwindow for the VBA editor of Office from an add-in with Visual Studio .NET. The UserControl, being .NET-based, needs to be registered as a COM component, and .NET-based COM components need to specify the required runtime version of the .NET Framework in the Windows registry, in the RuntimeVersion value inside the InProcServer32 key:

HKEY_CLASSES_ROOT\progid
   (default) = progId
HKEY_CLASSES_ROOT\progid\CLSID
   (default) = clsid
HKEY_CLASSES_ROOT\CLSID\{clsid}
   (default) = progid
HKEY_CLASSES_ROOT\CLSID\{clsid}\InProcServer32
   (default) = mscoree.dll
   Class = ClassName
   ThreadingModel = Both
   Assembly = Stringized_assembly_reference
   Codebase = path_of_private_assembly
   RuntimeVersion = version_of_the_runtime
...

toolwindowcontrolregistration

This registration of the usercontrol used by the toolwindow must be done by the installer at setup-time. And I used in the installer the same logic than in the COM Shim: first it detects CLR 2.0 and, if not available, then it detects CLR 4.0.

However, a couple of my users reported this exception when clicking a button that should show a toolwindow:

System.InvalidCastException: Unable to cast COM object of type ‘System.__ComObject’ to class type ‘System.Windows.Forms.UserControl’. Instances of types that represent COM components cannot be cast to types that do not represent COM components; however they can be cast to interfaces as long as the underlying COM component supports QueryInterface calls for the IID of the interface.

Toolwindows are created calling the CreateToolWindow function, that receives in the second parameter the ProgId of the ActiveX UserDocument that the function will create internally and return as output last parameter:

Windows.CreateToolWindow (AddInInst, ProgID, Caption, GuidPosition, DocObj) As Window

And that exception happened in a line of code that casts the usercontrol instance returned by the last output parameter (which should be a UserControl instance) to the System.Windows.Forms.UserControl type. Why?

It took me a while to discover the cause (the scenarios are so messy that often I need to go walking or swimming to get clarity thinking about the problem…). But at some point I realized that there were some scenarios where the runtime version of the usercontrol and the runtime version of the COM Shim would mismatch:

The first one is that you have Windows 10, which by default provides .NET Framework 4.0 (CLR 4.0) and not .NET Framework 2.0/3.0/3.5 (CLR 2.0). The user runs the setup of the add-in. The usercontrol is registered with the only runtime available (v4.0.30319). When the add-in is loaded the COM Shim uses the only runtime available, also v4.0.30319. So far so good. However, one day the user installs .NET Framework 2.0/3.0/3.5, which is an optional feature of Windows 10:

windowsoptionalfeatures

In this case, the usercontrol remains registered with runtime version v4.0.30319 (unless the user runs the installer again) but the COM Shim, that detects CLRs at runtime, not at setup time, would use CLR 2.0! (because its logic gives it preference over v4.0.30319). And this causes the System.InvalidCastException, because the CreateToolwindow returns an instance of a CLR 4.0 usercontrol. Since such thing cannot enter the CLR 2.0 of the COM Shim, a System.__ComObject wrapper is created, that cannot be cast to a UserControl type.

The second scenario is that you have Windows 10, which by default provides .NET Framework 4.0 (CLR 4.0) but the user has installed .NET Framework 2.0/3.0/3.5 (CLR 2.0). The user runs the setup of the add-in. The usercontrol is registered with the runtime v2.0.50727 (because the logic gives it preference over v4.0.30319). When the add-in is loaded the COM Shim uses also the preferred runtime v2.0.50727 over v4.0.30319. So far so good. However, one day the user uninstalls the optional .NET Framework 2.0/3.0/3.5. In this case, the usercontrol remains registered with runtime version v2.0.50727 (unless the user runs the installer again) but the COM Shim, that detects CLRs at runtime, not at setup time, would use CLR 4.0 instead of CLR 2.0! This causes a different exception, though.

So I thought a different approach: rather than using whatever CLR is installed on the machine (with preference for CLR 2.0 over CLR 4.0), I use the default CLR that should be installed on each Windows version:

  • Windows XP: none. But the setup enforces that .NET Framework 2.0 must be installed.
  • Windows Vista, Windows 7: .Net Framework 2.0
  • Windows 8, Windows 8.1, Windows 10: .Net Framework 4.0

This approach makes the COM Shim immune to installation/uninstallation of additional .NET Framework installations (because default .NET Frameworks are OS components and cannot be uninstalled, only optional .NET Frameworks can be uninstalled).

And while this approach seemed fine, another two users reported the same System.InvalidCastException, both cases on Windows 10 using VB6.  More walking and swimming :-). The reason is that VB6 can be set to run in compatibility mode with Windows XP:

windowsxpcompatibility

What happens if a user is running Windows 7 and sets VB6 with compatibility mode with Windows XP? The setup was run on Windows 7, so the usercontrol is registered with runtime version v2.0.50727 (the default for Windows 7). When VB6 in compatibility mode loads the COM Shim, it thinks that it is running on Windows XP and uses the .NET Framework 2.0 that the setup required. So nothing bad happens.

But what happens if the user is running Windows 10 and sets VB6 with compatibility mode with Windows XP? The setup was run on Windows 10 (not in compatibility mode), so the usercontrol is registered with runtime version v4.0.30319 (the default for Windows 10). When VB6 in compatibility mode loads the COM Shim, it thinks that it is running on Windows XP and uses the .NET Framework 2.0 that the setup required. So a System.InvalidCastException happens when performing the cast operation due to the mix of runtime versions.

The best solution, that I hope that fixes this problem for good, is to make the COM Shim to read the registered runtime version of the usercontrol of the toolwindow from the Windows registry, and use the matching CLR. With this approach, it doesn’t matter what Windows version the COM Shim thinks it is running on, because it won’t use the default CLR of that Windows version, but the one that was determined at setup-time.

Update (September 12, 2016): it happens that there is a way to register a .NET component as a COM component indicating that it can work with CLR 2.0 and CLR 4.0. Instead of using the RuntimeVersion name mentioned above, you can use the SupportedRuntimeVersions name and provide a list of CLRs in order of preference:

HKEY_CLASSES_ROOT\progid
   (default) = progId
HKEY_CLASSES_ROOT\progid\CLSID
   (default) = clsid
HKEY_CLASSES_ROOT\CLSID\{clsid}
   (default) = progid
HKEY_CLASSES_ROOT\CLSID\{clsid}\InProcServer32
   (default) = mscoree.dll
   Class = ClassName
   ThreadingModel = Both
   Assembly = Stringized_assembly_reference
   Codebase = path_of_private_assembly
   SupportedRuntimeVersions = v2.0.50727;v4.0
...

See the post In-Proc SxS and Migration Quick Start. So, original approach for the COM Shim would work:

  • First it detects if CLR 2.0 is installed. If so, use it.
  • If not, it detects if CLR 4.0 is installed. If so, uses it.

The strange case of mscoree.dll and the CLRCreateInstance function

Since I started three months ago to use a COM Shim for my .NET-based MZ-Tools 8.0 for VBA/VB6 to achieve isolation and .NET CLR independence, nobody had reported a single problem. However, last week two different problems were reported. This post is about the first one. The error was that on Windows XP, MZ-Tools 8.0 for VB/VBA failed to load. This dreaded error message:

MZTools8CouldNotBeLoaded

Fortunately, the user reported that installing .NET Framework 4.0 the problem was solved, but why?

The error message is hiding the actual error code and description (Microsoft started to show that crucial piece of information when an add-in fails to load in Visual Studio .NET 2002). I was able to reproduce the problem so I didn’t need to contact the user for further information. An approach to know why an add-in is failing to load on any IDE is to use Process Monitor to capture disk and registry activity and then guess what is failing (some missing registry entry or file, etc.). Unfortunately the current version of Process Monitor doesn’t support Windows XP, and although I couldn’t find an earlier version to download, I found the Registry Monitor and File Monitor utilities (that later became unified in Process Monitor) and they worked on Windows XP.  However, I couldn’t guess the problem. I always forget that the easiest way to know the actual error is to create a VBScript with this code that creates a COM object with the ProgId of the add-in (typically “MyAddIn.Connect”):

Dim o
Set o = CreateObject("MZTools8VBA")

And executing that script shows the actual problem:

ProcedureEntryNotFound

ProcedureEntryNotFoundScript

So, the problem was that the CLRCreateInstance function (used by the COM Shim to load the Common Language Runtime, CLR) was not found in mscoree.dll. At this point I knew I needed another tool: depends.exe (Dependency Walker).

The CLRCreateInstance function was introduced in .NET Framework 4.0 to load the CLR 4.0. The CLR 2.0 used by .NET Frameworks 2.0, 3.0 and 3.5 is loaded with a different function. My COM Shim has this logic:

  • First it detects if .NET Framework 2.0 is installed. If so, it uses the old API to load CLR 2.0.
  • If not, it detects if .NET Framework 4.0 is installed. If so, it uses the new CLRCreateInstance API to load CLR 4.0.

Note: this logic that uses run-time detection of the CLR rather than setup-time detection is flawed by a different reason that caused the second reported problem (for another post).

Since my setup requires .NET Framework 2.0 on Windows XP, I knew that the CLRCreateInstance function was not called on Windows XP. However, since my COM Shim dll linked it at compile-time:

COMShimOld

the whole dll of the COM Shim failed to load if that function is missing in mscoree.dll (it seems that my knowledge of C/C++ linking is very limited, I thought I would get a runtime error only if the COM Shim tried to call that missing function…).

This explained why installing .NET Framework 4.0 solved the problem: even if the COM Shim still used CLR 2.0 (by the logic above), the mscoree.dll updated by .NET Framework 4.0 provided the missing CLRCreateInstance function and the COM Shim dll didn’t fail to load.

But a question remained that got me puzzled for several days: nobody had reported this problem on Windows 7, that lacks .NET Framework 4.0 (it installs only .NET Framework 3.5 / CLR 2.0), in these three months. And since my setup doesn’t require .NET Framework 4.0 on Windows 7, I was sure that not all users have .NET Framework 4.0 installed. I should have received tons of bug reports with the same problem. But not a single one… The first thing I did was to check the version of mscoree.dll of my virtual machines with Windows 7. First surprise: they showed 4.0.xxxx, although .NET Framework 4.0 was not installed:

mscoreeWindows7WithSP1

Then I used depends.exe to check that the mscoree.dll of Windows 7 certainly exports the CLRCreateInstance function:

mscoreeExportedFunctionsWindows7WithSP1

So, how is that Windows 7, lacking .NET Framework 4.0, has a mscoree.dll version 4.0.xxxx that provides the CLRCreateInstance method to load the CLR 4.0?

At this point I decided to start from scratch: I created a virtual machine with Windows 7 RTM, and suprise, surprise, mscoree.dll was version 2.0.xxxx:

mscoreeWindows7WithoutSP1

and certainly lacked the CLRCreateInstance method:

mscoreeExportedFunctionsWindows7WithoutSP1

And of course, installing VB6 or Office and MZ-Tools 8.0 reproduced the problem that happened on Windows XP.

It was only after I applied SP1 to Windows 7, that mscoree.dll was updated to version 4.0.xxxx. So, it seems that Microsoft decided to include the mscoree.dll version 4.0.xxxx of .NET Framework 4.0 in the service pack 1 of Windows 7, even if that service pack doesn’t include .NET Framework 4.0.

Of course the best solution is to use dynamic linking at run-time using GetProcAddress so that the COM Shim can be loaded even if the CLRCreateInstance function is missing:

COMShimNew

For the record, Windows 10 uses version 10.0.xxxx of mscoree.dll:

mscoreeWindows10

Case closed.