Calling BindAsLegacyV2Runtime when creating CLR

Apr 15, 2011 at 12:43 AM

I am writing a .NET4 addin that makes use of a mixed-mode assembly (i.e. contains both managed and unmanaged code). When using such an assembly in a stand alone application, one has to create an app.config file with the setting "useLegacyV2RuntimeActivationPolicy" set to true.

Since Excel-DNA creates its own CLR, one would need to call the "BindAsLegacyV2Runtime" function to enable this. This method is (apparently) equivalent to the app.config setting.

Being quite limited with C++, I am not sure when/where/how this is supposed to be called. Does anyone know when/where this method is supposed to be called?

It would also be quite good if Excel-DNA had a setting (similar to the runtime version setting) that allowed you to call this method (thus, enabling the use of mixed-mode assemblies within an add-in).

Coordinator
Apr 15, 2011 at 8:56 AM

Hi Charlie,

Can you elaborate a bit on what problems you've had that require this? As I understand it, the main issue is when managed COM classes are activated - the loader has to decide in which version of the runtime a class should be activated. AFAIK it is not required in general to 'enable the use of mixed-mode assemblies within an add-in'.

In general we can't be sure that the Excel-DNA add-in is in charge of loading and starting the CLR, or is the only managed add-in in the process. If another add-in has already loaded the CLR, the Excel-DNA add-in will have no chance to change settings on the runtime. On the other hand, Excel-DNA does set up an AppDomain for each add-in, and will load as configuration file for the AppDomain a file <MyAddin>.xll.config. But I don't think adding this setting in the AppDomain configuration will help you.

The only reliable approach for you would be to put it in a configuration file for the Excel process, as Excel.exe.config, which would be next to the Excel process in C:\Program Files\Microsoft Office\Office12\.

However, it looks like the BindAsLegacyV2Runtime function can be called after the CLR has started, and should succeed as long as no other call has set the binding to another runtime version. So I think you can call this in C# from you add-in code: this post suggests how you would do it in F#: http://blogs.msdn.com/b/jomo_fisher/archive/2009/11/17/f-scripting-net-4-0-and-mixed-mode-assemblies.aspx.

For me to be more helpful in getting this to work in your add-in, you'd need to give a bit more detail on how I can test and reproduce the problem you've found.

Regards,

Govert

Apr 16, 2011 at 3:35 AM

Hi Govert,

I have created an example solution which uses SQLite to demonstrate the problem. SQLite is compiled against .NET2 and is mixed-mode.

The CLR will not load the SQLite assembly when called from my add-in:

"System.IO.FileLoadException: Mixed mode assembly is built against version 'v2.0.50727' of the runtime and cannot be loaded in the 4.0 runtime without additional configuration information."

The solution can be found here: https://rapidshare.com/files/457638192/MixedModeProblem.zip

Creating an Excel.exe.config file solves the problem, but I would prefer not to make changes to the Office installation folder.

This is getting beyond my knowledge of how the CLR works, but I suspect that only one runtime (either .NET2 or .NET4) can be bound as the legacy V2 runtime. If there is only a .NET4 add-in installed, the .NET4 runtime can take this honour (but how to do this without using Excel.exe.config, I am not sure?). However if there are both .NET2 and .NET4 add-ins enabled, is it possible for them both to be bound as the legacy v2 runtime - I suspect not!

In which case we cannot guarantee that the .NET4 runtime will be able to load .NET2 mixed-mode assemblies without using the excel.exe.config trick.

Coordinator
Apr 16, 2011 at 2:13 PM

Hi Charlie,

Thank you. I have downloaded your sample and get the same error.

I found a good description of this issue here: http://www.marklio.com/marklio/PermaLink,guid,ecc34c3c-be44-4422-86b7-900900e451f9.aspx

Looking at the FSharp post, and a few other on Google, I expected this code to work:

using System.Runtime.InteropServices;
using System.Runtime.CompilerServices; 

// .....
        private static void SetBindingMode()
        {
            ICLRRuntimeInfo rtInfo = (ICLRRuntimeInfo)RuntimeEnvironment.GetRuntimeInterfaceAsObject(Guid.Empty, typeof(ICLRRuntimeInfo).GUID);
            rtInfo.BindAsLegacyV2Runtime();
            
         }
// .....

    // WARNING - this is not the real interface - it just has enough slots to get to BindAsLegacyV2Runtime correctly.
    [ComImport] 
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 
    [Guid("BD39D1D2-BA2F-486A-89B0-B4B0CB466891")] 
    interface ICLRRuntimeInfo 
    { 
        void xGetVersionString(); 
        void xGetRuntimeDirectory(); 
        void xIsLoaded(); 
        void xIsLoadable(); 
        void xLoadErrorString(); 
        void xLoadLibrary(); 
        void xGetProcAddress(); 
        void xGetInterface(); 
        void xSetDefaultStartupFlags(); 
        void xGetDefaultStartupFlags(); 
        [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] 
        void BindAsLegacyV2Runtime();
        void xIsStarted();
    } 

But it doesn't. The result from the BindAsLegacyV2Runtime() call is OK, but under the debugger I see that the GetRuntimeInterfaceAsObject call actually loads the .NET 2.0 runtime .dll... not sure why. And when loading the SQLite library, the original error is still present.

From the marklio article, I thought that this means we are calling the BindAs... function too late. [Or the GetRuntimeInterfaceAsObject is returning the wrong Runtime interface?]

Next I tried to change the unmanaged loader to make the call as early as possible. I added this line:

HRESULT hrbind = pRuntimeInfo->BindAsLegacyV2Runtime();

into the LoadClrMeta procedure in the file ExcelDnaLoader.cpp. Again I see this suspicious entry in the debugger during this call:

'excel.exe': Loaded 'C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll'

and eventually loading the SQLite library will fail with the same error as before.

 

I've suspected for many years that Excel registers to be called when .NET is loaded (maybe with a call to LockClrVersion http://msdn.microsoft.com/en-us/library/ms230241.aspx). This interferes with Excel-DNA's attempts to host the runtime, (and would likewise interfere with VSTO or a COM Shim).

As a result, I don't think you can change this binding setting for Excel anywhere but in the exel.exe.config file. Of course I would love to be proven wrong (I could have made a mistake in where the unmanaged call fits in). To pursue this further, you'd have to make an unmanaged host to emulate Excel loading the code and try to make the call to compare with the Excel call, or perhaps investigate whether Excel is really interfering with the .NET loading process.

 

For now it seems you have to choose between:

- Adding the excel.exe.config file.

- Rebuilding the mixed assemblies to target .NET 4. (I see the System.Data.SQLite driver is progressing under control of the SQLite guys now).

- Using another way to talk to SQLite (or your other native libraries) - perhaps using P/Invoke (like http://code.google.com/p/sqlite-net/) or with a pure managed implementation: http://code.google.com/p/csharp-sqlite/.

- Retargeting your add-in for the .NET 2.0 runtime.

 

Sorry I can't give a nicer answer.

Regards,

Govert

 

Apr 17, 2011 at 3:44 AM
Edited Apr 17, 2011 at 5:50 AM

Before I made my last post, I tried the exact same thing you did above in C# and also noticed that BindAsLegacyV2Runtime() returned 0 (success) but made no difference when loading SQLite.

Just after I created the original post, I tried calling BindAsLegacyV2Runtime() from the unmanaged code directly before the call to GetInterface (though I'm not 100% sure it was before, it might have been after). This worked for me however! I'm not sure why it didn't work for you?

Hence I thought this problem could be solved simply by asking Excel-DNA to make this call whenever it creates the CLR. And thus why I said "It would also be quite good if Excel-DNA had a setting (similar to the runtime version setting) that allowed you to call this method".

However, whilst that was exciting I noticed that when I added a .NET2 add-in into Excel, then my .NET4 add-in was no longer able to call BindAsLegacyV2Runtime(), and it would return CLR_E_SHIM_LEGACYRUNTIMEALREADYBOUND ("A different runtime was already bound to the legacy CLR version 2 activation policy.").

So ultimately I think this means although it is possible to get the Excel-DNA created CLR to make this call, there is no guarantee that another CLR within the process has not done this. I suspect only one CLR within a process can be "bound to the legacy CLR version 2 activation policy" and if there is already a .NET2 add-in installed (especially if it doesn't use Excel-DNA) then you are out of luck.

This is getting quite complex an issue to solve. I think we'll just pursue the excel.exe.config trick or attempt to migrate all referenced assemblies to .NET4.

Thanks for your help. Again, I hope this is useful to anyone else who might run into the same problem.

Coordinator
Apr 17, 2011 at 8:44 AM

Hi Charlie, 

OK, I'm not sure why setting it in the unmanaged loader didn't work for me.

Otherwise I agree with everything you say. I see this in the documentation for the config file setting here: http://msdn.microsoft.com/en-us/library/bbx34a2h(VS.100).aspx

"Setting this value prevents CLR version 1.1 or CLR version 2.0 from loading into the same process, effectively disabling the in-process side-by-side feature." 

This says even more than just that only one runtime can be bound, it suggest that if you have set the bindingmode from CLR 4, then CLR 2 won't be able to load in the process at all.

So I'm not planning to make any changes to Excel-DNA for this.

 

Thank you very much for raising and exploring this issue.

 

-Govert