我有一个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 ;
答案 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()仅压缩但不返回数据。使用额外的功能,客户端代码可以使用它来发现所需的缓冲区大小并获取数据的副本。