使用ref代替P / Invoke的固定点

时间:2013-02-09 16:32:52

标签: c# c++ pointers dll unmanaged

我试图为用C ++编写的非托管库编写包装器。该库的编码器给了我一个C#包装器示例,其中包含DllImport这样的内容:

[DllImport(LibraryPath, CallingConvention = CallingConvention.Cdecl)]
internal static extern int doSomething(int inputArraySize, byte* inputArrayPointer, out int outputInteger, out int outputArraySize, out float* outputArrayPointer);

public static int DoSomething(byte[] inputArray, out int outputInteger, out float[] outputArray)
{
    int arraySize = inputArray.Length;

    int outputArraySize;
    float* outputArrayPointer;
    outputArray = null;

    fixed (byte* arrayPointer = inputArray)
    {
        int result = doSomething(arraySize, arrayPointer, out outputInteger, out outputArraySize, out outputArrayPointer);

        if (result == 0)
        {
            outputArray = new float[outputArraySize];

            for (int i = 0; i < outputArraySize; i++)
            {
                outputArray[i] = outputArrayPointer[i];
            }

            // This is another imported function.
            freePointer(outputArrayPointer);
        }
    }

    return result;
}

他在代码中使用了fixed,我想知道我们是否可以使用ref并丢失所有不安全的代码和指针。我可以把它重写为:

[DllImport(LibraryPath, CallingConvention = CallingConvention.Cdecl)]
internal static extern int doSomething(int arraySize, ref byte[] arrayPointer, out int someInteger, out int outputArraySize, out float[] someArrayPointer);

public static int DoSomething(byte[] inputArray, out int outputInteger, out float[] outputArray)
{
    int outputArraySize;

    int result = doSomething(inputArray.Length, ref inputArray, out outputInteger, out outputArraySize, out outputArray);

    return result;
}

上面的代码是否与第一个带指针的代码相同?

  1. 可以使用我的版本吗?
  2. 使用我的版本有什么缺点吗?
  3. 当我使用ref时,传递给非托管libraray函数的数组在整个函数运行中是否安全?
  4. 您认为freePointer()函数有什么作用?无论如何,垃圾收集将收集指针。为什么我们需要这样的功能?
  5. 我很欣赏有关此事的任何建议。

    编辑:我想我不能在没有额外代码的情况下将C ++指针作为C#数组。什么是最好的方法?有没有办法在不使用不安全的代码和指针的情况下完成所有这些工作?

    编辑2:我认为freePointer()函数释放了非托管代码分配的数组。但是如何在不将数组大小作为参数的情况下使用指向它的指针释放数组呢?

1 个答案:

答案 0 :(得分:2)

您的代码(经过一些小的修改)将用于将指针传递到数组托管代码本机代码。它不会在相反的方向上工作,因为本机代码不知道如何分配托管数组(float[]),所以你不能指望它在托管端被视为一个。

但是,您可以将其键入为float *,并使用Marshal.Copy来提取数据,而不是传回原始IntPtr

将它们放在一起,新的P / Invoke签名和代码将如下所示:

[DllImport(...)]
internal static extern int doSomething(int arraySize, byte[] arrayPointer,
    out int outputInteger, out int outputArraySize,
    out IntPtr outputArrayPointer);

[DllImport(...)]
internal static extern void freePointer(IntPtr pointer);

public static int DoSomething(byte[] inputArray, out int outputInteger, out float[] outputArray)
{
    outputArray = null;
    int outputArraySize;
    IntPtr outputArrayPointer;

    int result = doSomething(inputArray.Length, inputArray, out outputInteger, out outputArraySize, out outputArrayPointer);

    if (result == 0)
    {
        outputArray = new float[outputArraySize];
        Marshal.Copy(outputArrayPointer, outputArray, 0, outputArraySize);

        freePointer(outputArrayPointer);
    }

    return result;
}

当P / Invoke签名被写为采用数组时,编组层自动将数组固定在内存中(因此无法释放或移动它,使其可以安全地传递给本机代码),然后通过指向数组中第一个元素到本机代码的指针。相比之下,ref inputArray传递指向托管数组对象(指向内存中的不同位置)的指针。

这些更改可让您从调用代码中删除fixed (byte* arrayPointer,并且可以使用IntPtr将本机代码传回给您。由于本机代码分配了内存,因此.NET垃圾收集器不知道如何释放它,因此在复制数据后调用freePointer非常重要。它将知道要释放多少数据,因为分配内存的分配例程将在某处存储已分配块的大小(通常在分配的内存之前的头块中);这意味着您不需要将数组大小传递给freePointer