如何P / Invoke分配并返回指向新数组的指针的函数

时间:2014-02-14 18:33:34

标签: c# c pinvoke

我有一个C函数,我想从C#程序调用。函数压缩输入的字节数组并输出新的压缩数组。它看起来像这样:

extern __declspec(dllexport) int Compress(chandle handle, 
unsigned char *inputBuf, unsigned char **outputBuf, unsigned long *outputSize);

我已将其翻译成C#部分。但是output我得到的是带有一个项目的数组。

[DllImport("compresslib.dll", CallingConvention = CallingConvention.Cdecl)]
internal extern static int Compress(IntPtr handle, byte[] input, out byte[] output, out uint outputSize);

我该怎么做才能让它发挥作用?

以下是我在Hans Passant的帮助下编写的工作代码

[DllImport("compresslib.dll", CallingConvention = CallingConvention.Cdecl)]
internal extern static int Compress(IntPtr handle, byte[] input, out IntPtr output, out uint outputSize);

// and this is how i call it 
byte[] outputData;
int outputDataSize;
IntPtr outputDataP = IntPtr.Zero;
try
{
    int success = NativeMethods.Compress(handle,
    inputData, out outputDataP, out outputDataSize);
    if (success == -1)
    {
        throw new Exception("Compression failed.");
    }
    outputData = new byte[outputDataSize];
    Marshal.Copy(outputDataP , outputData , 0, (int)outputDataSize);
}
finally
{
    if (outputDataP != IntPtr.Zero)
    NativeMethods.tjFree(outputDataP);// release unmanaged buffer
}

return outputData ;

1 个答案:

答案 0 :(得分:2)

  ..., unsigned char **outputBuf, ...

这个函数存在一个非常严重的问题,它也无法从C程序中可靠地调用。调用者需要在使用后释放输出缓冲区。这需要使用与C代码中使用的完全相同的分配器。这在C程序中很难保证,并且经常出错。就像你的DLL的用户没有使用你使用的完全相同的编译器版本。他将使用不同版本的C运行时库,它使用自己的堆。所以不可能释放缓冲区,因为他没有你使用的堆的句柄。

当你进行pinvoke时,它几乎是不可能的,CLR当然完全不知道你使用的是什么C运行时版本,并且保证不使用你使用的同一个版本,因为它有自己的私有副本。

你刚刚得到一个字节的原因与这个问题有关,pinvoke marshaller不知道数组有多大,因为它没有创建数组。这是可以修复的,您需要将[MarshalAs(UnmanagedType.LPArray),SizeParamIndex = 3]属性应用于参数。这告诉pinvoke marshaller第四个参数包含数组的大小。

你需要解决内存管理问题,不能让它保持原样,因为你会在XP上严重泄漏内存并在Vista上发生硬崩溃,当pinvoke marshaller尝试释放阵列时会更高。您需要更改C代码以将内存用于已知堆上分配的返回输出缓冲区。这需要使用CoTaskMemAlloc()

另一种可能的解决方法是导出一个允许调用者释放缓冲区的函数。在这种情况下,您应该声明参数out IntPtr并使用Marshal.Copy()编组自己,然后使用添加的函数释放缓冲区。

或者使用该函数的完全不同的方式,例如让Compress()仅压缩但不返回数据。使用额外的功能,客户端代码可以使用它来发现所需的缓冲区大小并获取数据的副本。