0

我正在尝试为用 C++ 编写的非托管库编写一个包装器。该库的编码器给了我一个示例 C# 包装器,其中包括DllImport如下 s:

[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时,传递给非托管库函数的数组在整个函数运行期间是否安全?
  4. 你认为这个freePointer()函数有什么作用?无论如何,指针都会被垃圾收集器收集。为什么我们需要这样的功能?

我将不胜感激有关此事的任何建议。

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

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

4

1 回答 1

2

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

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

将这些放在一起,新的 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.

于 2013-02-09T21:36:48.203 回答