访问托管阵列和固定

时间:2015-09-07 11:54:58

标签: arrays c++-cli

我目前正在为一些本机C ++代码编写一组Wrappers。在包装器中,我将托管数组作为输入,并打算使用数组的内容来调用本机c ++构造函数。出于某种原因,我似乎需要固定数组或从构造函数调用中单独提取值。以下是我的意思的一些例子。

本机类型的构造函数具有类似以下的类型签名:

NativeType(const double &d)

初步尝试:

public ref class ExampleWrapper
{
     ExampleWrapper(array<double> ^ in)
     {
        for(int i= 0; i< in->Length; ++i)
        {
            NativeType test(in[i]);
        }
     }
}

返回时出现错误,无法编译。接下来我尝试了这个

public ref class ExampleWrapper
{
     ExampleWrapper(array<double> ^ in)
     {
        for(int i= 0; i< in->Length; ++i)
        {
            double d = in[i];
            NativeType test(d);
        }
     }
}

似乎工作正常。最后我试着像这样固定数组:

public ref class ExampleWrapper
{
     ExampleWrapper(array<double> ^ in)
     {
        pin_ptr<double> pin_in = &in[0];
        for(int i= 0; i< in->Length; ++i)
        {
            NativeType test(pin_in[i]);
        }
     }
}

这似乎也很好。

我想知道的是为什么第一个例子不起作用,而另外两个似乎工作正常。另外我想知道什么是首选方法。

2 个答案:

答案 0 :(得分:2)

in数组是一个托管对象,它没有稳定的地址。在任何可能的时刻,垃圾收集器可以在压缩堆时启动并移动对象。在您调用本机代码之后,这种情况发生的可能性并不大,因此GC没有理由触发集合。然而,它不是零,程序中的其他线程可能同时分配。

当发生这种情况时,灾难就会发生。它是 const double ,因此至少本机代码不能破坏GC堆。它读取的实际双精度值是随机的。

C ++ / CLI编译器可以检测到这种可能的不幸事件和抱怨。你必须为double&amp;提供一个稳定的地址。将它复制到局部变量当然是最简单的方法,它存储在堆栈中,这些变量永远不会被移动。使用pin_ptr&lt;&gt;这是一个很好的解决方法,它是一种非常廉价的方式来固定托管对象。它只在表中设置了抖动生成的位,这有助于GC发现存储在局部变量和CPU寄存器中的引用。当实际集合发生时,它只需花费任何成本,CLR在执行堆栈遍历时发现它以查找引用。

传递双倍&amp;是非常奇怪的,非常重要的是你看看本机代码。期望您会发现它存储引用而不是存储值。这是一个非常大的问题,你找到的解决方法只对构造函数调用的生命周期足够好。本机代码在稍后使用引用时会读取垃圾。

然后必须创建一个在构造函数调用之外保持有效的稳定引用,在包装器中使用本机 new 运算符并将其存储在字段中。在检查本机代码无法再取消引用后,再次在终结器中清理它。当你的终结器也会破坏本机类对象时,通常只能达到一个好的结果。

答案 1 :(得分:1)

  

为什么第一个例子失败了?

它失败了,因为您要求编译器获取托管变量的地址(double数组中的单个double)并将其传递给非托管代码,但这是非法的。

  

为什么第二个例子通过?

因为d不在托管内存中,所以它是包装器代码中的局部变量。您可以从托管内存阵列初始化它,因为您只获取值(双精度值),而不是地址。

  

为什么第三个例子通过?

因为您将阵列固定到固定的内存位置,现在可以安全地将其传递到非托管代码中。 (P.S。,我认为你的意思是pin_ptr<double> pin_in = &in[0];