具有类型推导的 C++/CLI 静态对象导致未处理的运行时异常

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

设置

在尝试在 C++/CLI 包装器中为本机 C++ 库的 t运行slation 单元范围创建静态对象时,我 运行 遇到了使用 auto 关键字的问题。 C++/CLI 包装器是纯函数式的,因此我需要使用 ConcurrentDictionary 来保存调用之间的一些状态(以处理托管 <-> 本机委托 t运行slation)。我第一次尝试这个(简化):

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

但是编译失败:

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

为简单起见,我决定在解决问题时继续使用类型推导:

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

令我惊讶的是这个编译和链接!我继续编写了一段时间的更多代码,然后我 运行 我的 C# 测试应用程序使用 C++/CLI 包装器并收到未处理的运行时异常:

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.

我没有立即知道问题出在哪里,因为我进行了其他编辑。我终于通过打开融合日志并使用 Fuslogvw.exe 获得了更多信息。问题在于类型推导的静态 ConcurrentDictionary。

我创建了一个SSCCE to demonstrate the issue: https://github.com/calebwherry/DotNetWrapperForNativeCppLibrary

此 post 中描述的代码位于此处:https://github.com/calebwherry/DotNetWrapperForNativeCppLibrary/blob/master/DotNetTestLibWrapper/DotNetTestLibWrapper.cpp

MSVS 版本:

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

问题

这些都会导致编译器错误:

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

但这会编译+链接但会导致有关非法类型的未处理运行时异常:

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

这是怎么回事,为什么会这样? MSVS 似乎认为(阅读:IntelliSense 如此说)autoauto^ 定义是相同的。但显然不是,一个编译,一个不编译。

注意:在阅读了一些关于原始编译器问题的内容后,实际解决问题的方法是:

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

很好,我只是好奇类型推导到底发生了什么。

Hmya,编译器错误,它不应该允许您以这种方式声明它。毫无疑问,由于 C++11 的变化,禁止此声明所需的额外检查不适用于语句中的 auto

您尝试调用的臭名昭著的 SIOF(静态初始化顺序失败)不是 .NET 功能。但这正是您在这里得到的,编译器生成的初始化程序(通常 用于非托管代码)导致构造函数 运行 过早。 确切地 出了什么问题很难从调试器跟踪中看出,除了它不漂亮。但当然最基本的问题是模块初始化程序需要先 运行 来设置 C++/CLI 的执行环境。在此之前,任何本机 C++ 初始化器。那还没有发生。

您需要以正确的方式执行此操作,静态成员由类型初始化程序(又名静态构造函数,又名 .cctor)初始化。在您使用任何 class 成员之前,CLR 会自动调用它。您不必显式编写该构造​​函数,编译器会自动从字段初始化表达式中为您编写它。

ref class Globals {
public:
    static ConcurrentDictionary<String^, String^>^ Data1 = gcnew ConcurrentDictionary<String^, String^>();
    // etc...
};