我有一个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 ;
..., unsigned char **outputBuf, ...
这个函数有一个非常严重的问题,它也不能从C程序可靠地调用。调用方需要在使用后释放输出缓冲区。这需要使用与C代码中使用的分配器完全相同的分配器。这在C程序中很难保证,而且经常会出错。就像DLL的用户未使用与您使用的完全相同的编译器版本一样。他将使用另一种版本的C运行时库,该版本使用自己的堆。因此,由于他没有使用的堆的句柄,因此无法释放缓冲区。
当您单击时,它几乎变成不可能,CLR当然完全不知道您使用的C运行时版本,并且由于它具有自己的私有副本,因此保证不会使用与您使用的相同的C运行时版本。
您仅得到一个字节的原因与此问题有关,pinvoke marshaller不知道数组有多大,因为它没有创建数组。这是可解决的,您需要将[MarshalAs(UnmanagedType.LPArray),SizeParamIndex = 3]属性应用于参数。这告诉pinvoke编组,第4个参数包含数组的大小。
您将需要解决内存管理问题,因此无法解决问题,因为在pinvoke编组尝试释放阵列时,XP上的内存泄漏严重,而Vista或更高版本上的硬盘崩溃严重。您需要更改C代码以将内存用于分配给已知堆的返回输出缓冲区。这需要使用CoTaskMemAlloc()。
另一个可能的解决方法是导出一个允许调用者释放缓冲区的函数。在这种情况下,您应该声明参数out IntPtr
并使用Marshal.Copy()进行编组,然后使用添加的函数释放缓冲区。
或者使用一种完全不同的方式使用该函数,例如让Compress()仅压缩而不返回数据。客户端代码可使用其他功能来发现所需的缓冲区大小并获取数据副本。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句