访问托管阵列和固定
Accessing managed arrays and pinning
我目前正在为一些本机 C++ 代码编写一组包装器。在包装器中,我将一个托管数组作为输入,并打算使用该数组的内容来调用本机 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]);
}
}
}
这似乎也能正常工作。
我想知道的是为什么第一个示例不起作用,而其他两个似乎工作正常。另外我想知道首选的方法是什么。
Why does the first example fail?
它失败是因为您要求编译器获取托管变量的地址(double 数组中的单个 double)并将其传递给非托管代码,但这是非法的。
Why does the second example pass?
因为 d
不在托管内存中,所以它是包装代码中的局部变量。您可以从托管内存数组对其进行初始化,因为您只获取值(双精度值),而不是地址。
Why does the third example pass?
因为您将数组固定到固定的内存位置,现在它可以安全地传递到非托管代码中。
(P.S.,我想你的意思是 pin_ptr<double> pin_in = &in[0];
)
in
数组是托管对象,它没有 stable 地址。在任何可能的时刻,垃圾收集器都可以在压缩堆的同时启动并移动对象。这不太可能发生,毕竟您正在调用本机代码,因此 GC 没有理由触发收集。但是它不是零,程序中的其他线程可能同时分配。
当这种情况发生时,灾难就来了。它是一个 const double 所以至少本机代码不会破坏 GC 堆。然而,它读取的实际双精度值是随机的。
C++/CLI 编译器可以检测到这种可能的错误并发出警告。您必须为 double& 提供一个 stable 地址。将它复制到局部变量中当然是最简单的方法,它存储在堆栈中并且这些变量永远不会移动。使用 pin_ptr<> 也是一个很好的解决方法,它是一种 非常 廉价的固定托管对象的方法。它仅在抖动生成的 table 中设置一个位,帮助 GC 发现存储在局部变量和 CPU 寄存器中的引用。它只在实际收集发生时才花费任何费用,CLR 在执行堆栈遍历以查找引用时发现它。
传递 double& 很奇怪,查看本机代码非常重要。期望您会发现它存储引用而不是存储值。这是一个非常大的问题,您找到的变通方法仅适用于构造函数调用的生命周期。本机代码稍后使用引用时将读取垃圾。
然后您必须创建一个在构造函数调用之后保持有效的 stable 引用,使用本机 new 运算符你的包装器并将其存储在一个字段中。在你的终结器中清理它,在检查本机代码不能再取消引用它之后再次清理它。通常只有当您的终结器也销毁了本机 class 对象时才会有好的结果。
我目前正在为一些本机 C++ 代码编写一组包装器。在包装器中,我将一个托管数组作为输入,并打算使用该数组的内容来调用本机 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]);
}
}
}
这似乎也能正常工作。
我想知道的是为什么第一个示例不起作用,而其他两个似乎工作正常。另外我想知道首选的方法是什么。
Why does the first example fail?
它失败是因为您要求编译器获取托管变量的地址(double 数组中的单个 double)并将其传递给非托管代码,但这是非法的。
Why does the second example pass?
因为 d
不在托管内存中,所以它是包装代码中的局部变量。您可以从托管内存数组对其进行初始化,因为您只获取值(双精度值),而不是地址。
Why does the third example pass?
因为您将数组固定到固定的内存位置,现在它可以安全地传递到非托管代码中。
(P.S.,我想你的意思是 pin_ptr<double> pin_in = &in[0];
)
in
数组是托管对象,它没有 stable 地址。在任何可能的时刻,垃圾收集器都可以在压缩堆的同时启动并移动对象。这不太可能发生,毕竟您正在调用本机代码,因此 GC 没有理由触发收集。但是它不是零,程序中的其他线程可能同时分配。
当这种情况发生时,灾难就来了。它是一个 const double 所以至少本机代码不会破坏 GC 堆。然而,它读取的实际双精度值是随机的。
C++/CLI 编译器可以检测到这种可能的错误并发出警告。您必须为 double& 提供一个 stable 地址。将它复制到局部变量中当然是最简单的方法,它存储在堆栈中并且这些变量永远不会移动。使用 pin_ptr<> 也是一个很好的解决方法,它是一种 非常 廉价的固定托管对象的方法。它仅在抖动生成的 table 中设置一个位,帮助 GC 发现存储在局部变量和 CPU 寄存器中的引用。它只在实际收集发生时才花费任何费用,CLR 在执行堆栈遍历以查找引用时发现它。
传递 double& 很奇怪,查看本机代码非常重要。期望您会发现它存储引用而不是存储值。这是一个非常大的问题,您找到的变通方法仅适用于构造函数调用的生命周期。本机代码稍后使用引用时将读取垃圾。
然后您必须创建一个在构造函数调用之后保持有效的 stable 引用,使用本机 new 运算符你的包装器并将其存储在一个字段中。在你的终结器中清理它,在检查本机代码不能再取消引用它之后再次清理它。通常只有当您的终结器也销毁了本机 class 对象时才会有好的结果。