我正在尝试将我的本机dll通过CoTaskMemAlloc分配的一些数据编组到我的c#应用程序中,并想知道我正在做的方式是否完全错误,或者我错过了一些sublte装饰方法c#side。
目前我有c ++方面。
extern "C" __declspec(dllexport) bool __stdcall CompressData( unsigned char* pInputData, unsigned int inSize, unsigned char*& pOutputBuffer, unsigned int& uOutputSize)
{ ...
pOutputBuffer = static_cast<unsigned char*>(CoTaskMemAlloc(60000));
uOutputSize = 60000;
在C#方面。
private const string dllName = "TestDll.dll";
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport(dllName)]
public static extern bool CompressData(byte[] inputData, uint inputSize, out byte[] outputData, out uint outputSize );
...
byte[] outputData;
uint outputSize;
bool ret = CompressData(packEntry.uncompressedData, (uint)packEntry.uncompressedData.Length, out outputData, out outputSize);
这里outputSize是预期的60000,但是outputData的大小为1,当我设置缓冲区c ++端时,它似乎只复制了1个字节,所以这只是错误的我需要编组外的数据使用IntPtr + outputSize进行调用,或者是否有一些我想要的工作变得无法实现?
感谢。
答案 0 :(得分:4)
有两件事。
首先,P / Invoke层不处理C ++中的引用参数,它只能用于指针。特别是最后两个参数(pOutputBuffer
和uOutputSize
)无法保证正确编组。
我建议您将C ++方法声明更改为(或创建表单的包装器):
extern "C" __declspec(dllexport) bool __stdcall CompressData(
unsigned char* pInputData, unsigned int inSize,
unsigned char** pOutputBuffer, unsigned int* uOutputSize)
那说,第二个问题来自P / Invoke层也不知道如何编组“原始”数组(相反,COM中的SAFEARRAY
知道它的情况大小)在非托管代码中分配。
这意味着在.NET端,你必须编组后面创建的指针,然后手动编组数组中的元素(以及处理它,如果这是你的责任,它看起来像是。)
您的.NET声明如下所示:
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport(dllName)]
public static extern bool CompressData(byte[] inputData, uint inputSize,
ref IntPtr outputData, ref uint outputSize);
将outputData
作为IntPtr
(这将指向非托管内存)后,您可以通过调用{{3}上的Copy
method转换为字节数组像这样:
var bytes = new byte[(int) outputSize];
// Copy.
Marshal.Copy(outputData, bytes, 0, (int) outputSize);
请注意,如果您有责任释放内存,可以拨打Marshal
class,如下所示:
Marshal.FreeCoTaskMem(outputData);
当然,你可以将它包装成更好的东西,如:
static byte[] CompressData(byte[] input, int size)
{
// The output buffer.
IntPtr output = IntPtr.Zero;
// Wrap in a try/finally, to make sure unmanaged array
// is cleaned up.
try
{
// Length.
uint length = 0;
// Make the call.
CompressData(input, size, ref output, ref length);
// Allocate the bytes.
var bytes = new byte[(int) length)];
// Copy.
Marshal.Copy(output, bytes, 0, bytes.Length);
// Return the byte array.
return bytes;
}
finally
{
// If the pointer is not zero, free.
if (output != IntPtr.Zero) Marshal.FreeCoTaskMem(output);
}
}
答案 1 :(得分:1)
pinvoke marshaller无法猜测返回的byte []可能有多大。 C ++中内存的原始指针没有指向内存块的可发现大小。这就是你添加uOutputSize参数的原因。对客户端程序有好处,但对于pinvoke marshaller来说还不够好。您必须帮助并将[MarshalAs]属性应用于pOutputBuffer,并指定 SizeParamIndex 属性。
请注意,阵列正在被编组器复制。这不是那么令人满意,您可以通过允许客户端代码传递数组来避免它。编组器会将其固定并将指针传递给托管阵列。唯一的麻烦是客户端代码没有合适的方法来猜测阵列的大小。典型的解决方案是允许客户端调用它两次,首先使用uOutputSize = 0,该函数返回所需的数组大小。这将使C ++函数看起来像这样:
extern "C" __declspec(dllexport)
int __stdcall CompressData(
const unsigned char* pInputData, unsigned int inSize,
[Out]unsigned char* pOutputBuffer, unsigned int uOutputSize)