如何将托管值类型成员添加到非托管class?

How to add a managed value type member to an unmanaged class?

我正在尝试创建一个名为 MyNativeClass 的本机 C++ class,它可以被非托管代码使用。 MyNativeClass 的成员函数是使用托管代码实现的。此外,托管代码需要一个 System::Numerics::BigInteger 对象,但是当我尝试将 System::Numerics::BigInteger bi_ 字段添加到 MyNativeClass 时,我得到

error C3265: cannot declare a managed 'bi_' in an unmanaged 'MyNativeClass'

这是一个简化的代码清单,它演示了我正在努力实现的目标:

mynativeclass.h

class MyNativeClass
{
    //...

public:
    MyNativeClass();
    //...

private:
    System::Numerics::BigInteger bi_;

    //...
};

mynativeclass.cc

MyNativeClass::MyNativeClass()
    : bi_(BigInteger::Zero)
{
    //...
}

我不确定为什么不允许这样做。

有办法吗?

您不能在本机类型中包含托管数据。原因是本机类型的对象在垃圾收集器的范围之外,并且不会阻止托管对象死亡。

"I know that",你说。 "But value types aren't kept on the managed heap and don't need to have their lifetime tracked by the garbage collector!" 没错。但是托管值类型可能包含引用类型的句柄。如果垃圾收集器看不到它们,它就无法保持它们的引用(或在分代垃圾收集器压缩堆/将对象提升到更高的世代时调整它们)。

可以将可 blittable 数据(不包含句柄)直接存储在本机内存中。事实上,这对于具有双重标识的原始类型是允许的(例如,原生 int == System.Int32)。但它不允许用于任何复合类型,大概是为了保持语言规则简单。而且 BitInteger 无论如何都不会被允许,因为它确实需要保留一个可变大小的内容区域的句柄(使用 dotPeek 或参考源,它显示为 array<unsigned>^)以便支持任意精度。

解决方法是使用垃圾收集器的 GCHandle 功能,使垃圾收集器范围之外的对象保持活动状态。但是用 GCHandles 替换值 class 内的所有句柄会导致不兼容的内存布局,因此它实际上不再是相同的类型。最简单的解决方案是使用 gcroot<>(这是 GCHandle 的一个很好的 C++/CLI 接口)引用 class 类型,并将您的托管值类型粘贴在其中。

C++/CLI 团队最初尝试允许混合类型,但事情变得复杂并以您现在看到的分离规则告终。有一篇关于它的相当不错的博客文章,但我现在找不到了。