C++/CLI static object with type deduction causes unhandled runtime exception

2

Setup

While trying to create a static object at the translation unit scope in a C++/CLI wrapper for a native C++ lib, I ran into an issue with using the auto keyword. The C++/CLI wrapper is purely functional so I was in need of using a ConcurrentDictionary to hold some state between calls (to handle Managed <-> Native delegate translation). I first tried this (simplified):

static ConcurrentDictionary<String^, String^>^ GlobalData1 = gcnew ConcurrentDictionary<String^, String^>();

But this fails to compile with:

A variable with a static storage duration cannot have a handle or tracking reference type

For simplicity, I decided to go ahead and use type deduction while I figured out the issue:

static auto GlobalData3 = gcnew ConcurrentDictionary<String^, String^>();

And to my surprise this compiled and linked! I went on writing more code for awhile and then I ran my C# test app that uses the C++/CLI wrapper and received an unhandled runtime exception:

Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 'DotNetTestLibWrapper, Version=1.0.6111.33189, Culture=neutral, PublicKeyToken=null' or one of its dependencies. Could not find or load a type. (Exception from HRESULT: 0x80131522) ---> System.TypeLoadException: Type '' from assembly 'DotNetTestLibWrapper, Version=1.0.6111.33189, Culture=neutral, PublicKeyToken=null' has a field of an illegal type.

I didn't immediately know what the issue was since I had made other edits. I finally got more information by turning on fusion logs and using Fuslogvw.exe. The issue is with the type deduced static ConcurrentDictionary.

I created a SSCCE to demonstrate the issue: https://github.com/calebwherry/DotNetWrapperForNativeCppLibrary

The code described in this post is located here: https://github.com/calebwherry/DotNetWrapperForNativeCppLibrary/blob/master/DotNetTestLibWrapper/DotNetTestLibWrapper.cpp

MSVS version:

Microsoft Visual Studio Community 2015 Version 14.0.25431.01 Update 3 Microsoft .NET Framework Version 4.6.01038

Question

These both cause compiler errors:

static ConcurrentDictionary<String^, String^>^ GlobalData1 = gcnew ConcurrentDictionary<String^, String^>();
static auto^ GlobalData2 = gcnew ConcurrentDictionary<String^, String^>();

But this compiles+links but causes an unhandled runtime exception about illegal types:

static auto GlobalData3 = gcnew ConcurrentDictionary<String^, String^>();

What is going on and why does this happen? MSVS seems to think (read: IntelliSense says so) that the auto and auto^ definitions are the same. But they are obviously not since one compiles and the other does not.

Note: after reading some about the original compiler issue, the actual solution to the problem is this:

ref struct GlobalData
{
    static ConcurrentDictionary<String^, String^>^ GlobalData4 = gcnew ConcurrentDictionary<String^, String^>();
};

Which is fine, I'm just curious what is actually going on with the type deduction.

.net
c++-cli
asked on Stack Overflow Sep 25, 2016 by Caleb • edited Sep 25, 2016 by Deduplicator

1 Answer

5

Hmya, compiler bug, it should not have allowed you to declare it that way. No doubt induced by the C++11 changes, the extra check that is need to disallow this declaration doesn't work with auto in the statement.

The infamous SIOF (Static Initialization Order Fiasco) you are trying to invoke is not a .NET feature. But that's exactly what you got here, the initializer that the compiler generates (normally only used for unmanaged code) causes the constructor to run far too early. What exactly goes wrong is hard to see from the debugger trace, other than it isn't pretty. But certainly the most basic problem is that the module initializer needs to run first to setup the execution environment for C++/CLI. And before that any native C++ initializers. That didn't happen yet.

You need to do this correct way, static members are initialized by the type initializer (aka static constructor, aka .cctor). The CLR automatically invokes it before you use any of the class members. You don't have to write that constructor explicitly, the compiler automatically writes it for you from the field initialization expression.

ref class Globals {
public:
    static ConcurrentDictionary<String^, String^>^ Data1 = gcnew ConcurrentDictionary<String^, String^>();
    // etc...
};
answered on Stack Overflow Sep 25, 2016 by Hans Passant

User contributions licensed under CC BY-SA 3.0