2

非托管和托管内存区域

我正在尝试从 C 库执行非托管代码。其中一种方法将 avoid*作为参数,但在幕后它被强制转换为类型的结构nc_vlen_t

nc_vlen_t 的 C 结构

/** 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。我编写了一个结构,它将nc_vlen_t在我执行 C 库方法调用时进行编组。

C# 结构

[StructLayout(LayoutKind.Sequential)]
public struct VlenStruct {
    public Int32 len;
    public IntPtr p; // Data 
}

该结构由size_t表示数组长度的 a 和void *数据的 a 组成。在库内部,它具有允许将 (void*) 转换为适当的数字类型的属性,到目前为止,我已经取得了巨大的成功。

我想了解的是处理内存区域的最佳方式。在阅读了一些文章和其他 SO 问题之后,这是我对如何处理它的最佳猜测。我有一个类充当创建和管理结构及其内存的仲裁者。我依靠析构函数来释放将取消固定数组的句柄,以便 GC 可以完成它的工作。

C# Vlen 助手

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;
}

C 方法声明

    //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);

如果我使用此类来创建结构,在这种情况下,GC 可能会在调用 C 库之前清理数组:

C# 用例

{ 
    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,这可能会在执行外部库方法时导致不可预测的行为?

4

1 回答 1

3

是的,你这里肯定有问题。就抖动而言,“数据”对象的生命周期在 ToStruct() 方法返回之前结束。检查这个答案的原因。这允许终结器在您的非托管代码运行时运行。这会取消您的阵列。它实际上需要另一个垃圾回收来破坏非托管代码使用的数据。确实非常罕见,但并非不可能。您也不太可能遇到异常,只是随机数据损坏。

一种解决方法是将 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很奇怪,你不会期望这个函数改变数据类型。

于 2013-07-12T19:38:55.130 回答