我正在尝试从C库中执行非托管代码。其中一个方法将void*
作为参数,但在引擎盖下它被强制转换为nc_vlen_t
类型的结构
/** This is the type of arrays of vlens. */
typedef struct {
size_t len; /**< Length of VL data (in base type units) */
void *p; /**< Pointer to VL data */
} nc_vlen_t;
执行该方法是正确的并且它有效,我更关心托管和非托管内存区域的固定和安全处理。我希望尽可能确定我不会导致内存泄漏或SEGFAULT。我编写了一个结构,当我执行C库方法调用时,它将与nc_vlen_t
进行编组。
[StructLayout(LayoutKind.Sequential)]
public struct VlenStruct {
public Int32 len;
public IntPtr p; // Data
}
结构由size_t
组成,表示数组长度,数据为void *
。在库内部,它具有允许它将(void *)转换为适当的数字类型的属性,到目前为止我已经取得了很大的成功。
我想要了解的是处理内存区域的最佳方法。在阅读了一些文章和其他SO问题之后,这是我最好的 guess 以了解如何处理它。我有一个类作为仲裁器,用于创建和管理结构及其内存。我依靠析构函数来释放句柄,这将解锁数组,以便GC可以完成它的工作。
public class Vlen {
private GCHandle handle;
private VlenStruct vlen_t;
public Vlen() {
isNull = true;
}
public Vlen(Array t) {
isNull = false;
handle = GCHandle.Alloc(t, GCHandleType.Pinned); // Pin the array
vlen_t.len = t.Length;
vlen_t.p = Marshal.UnsafeAddrOfPinnedArrayElement(t, 0); // Get the pointer for &t[0]
}
~Vlen() {
if(!isNull) {
handle.Free(); // Unpin the array
}
}
public VlenStruct ToStruct() {
VlenStruct retval = new VlenStruct();
retval.len = vlen_t.len;
retval.p = vlen_t.p;
return retval;
}
private bool isNull;
}
//int cmethod(const int* typep, void *data)
// cmethod copies the array contents of the vlen struct to a file
// returns 0 after successful write
// returns -1 on fail
[DllImport("somelib.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true, CallingConvention=CallingConvention.Cdecl)]
public static extern Int32 cmethod(ref Int32 typep, ref VlenStruct data);
如果我使用这个类来创建结构,那么在这种情况下调用C库之前GC可能会清理数组:
{
double[] buffer vlenBuffer = new double[] { 0, 12, 4};
Vlen data = new Vlen(vlenBuffer); // The instance now pins buffer
VlenStruct s = data.ToStruct()
Int32 type = VLEN_TYPE;
cmethod(ref type, ref s);
}
是否可以清除data
实例,从而取消buffer
,这可能会在执行外部库方法时导致不可预测的行为?
答案 0 :(得分:3)
是的,你肯定有问题。就抖动而言,您的数据的生命周期为&#34;对象在ToStruct()方法返回之前结束。检查this answer原因。这允许终结器在您的非托管代码运行时运行。哪个取消你的阵列。它实际上需要另一个垃圾收集来破坏非托管代码使用的数据。非常罕见,但并非不可能。您也不可能得到例外,只是随机数据损坏。
一种解决方法是将Vlen对象的生命周期延长到调用之外,如下所示:
Vlen data = ...
...
cmethod(ref type, ref s);
GC.KeepAlive(data);
哪个有效,但没有赢得任何奖品,容易忘记。我会这样做:
public static void CallHelper<T>(int type, T[] data) {
var hdl = GCHandle.Alloc(data, GCHandleType.Pinned);
try {
var vlen = new nc_vlen();
vlen.len = data.Length;
vlen.data = hdl.AddrOfPinnedObject();
cmethod(ref type, ref vlen);
}
finally {
hdl.Free();
}
}
用法:
var arr = new int[] { 1, 2, 3 };
CallHelper(42, arr);
除了避免早期收集问题之外,还要尽可能缩短阵列固定。请注意,此函数的第一个参数上的 ref 非常奇怪,您不希望此函数改变数据类型。