The correct way to marshall uchar [] from native dll to byte [] in C #

I am trying to collect some of the data that my native dll allocates through CoTaskMemAlloc to my C # application, and wonder if I just do it, or if I am missing some kind of gimmicky C # method decoration.

I currently have a C ++ side.

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; 

And on the side of 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); 

here outputSize is 60,000, as expected, but outputData is 1, and when I memset the C ++ buffer, it seems to copy only 1 byte, so this is just wrong and I need to collect data outside of the call using IntPtr + outputSize, or is there something upbeat that I don’t have enough to work that I already have?

Thanks.

+2
source share
2 answers

There are two things.

At first, the P / Invoke layer does not process reference parameters in C ++; it can only work with pointers. The last two parameters ( pOutputBuffer and uOutputSize ), in particular, are not guaranteed for the correct marshal.

I suggest you change your C ++ method declaration (or create a form wrapper):

 extern "C" __declspec(dllexport) bool __stdcall CompressData( unsigned char* pInputData, unsigned int inSize, unsigned char** pOutputBuffer, unsigned int* uOutputSize) 

However, the second problem arises because the P / Invoke layer also does not know how to redirect raw arrays (unlike, say, SAFEARRAY in COM, which knows about this size) that are allocated in unmanaged code .

This means that on the .NET side, you must marshal the created pointer, and then manually output the elements in the array (and also get rid of it if this is your responsibility, which it seems to have).

Your .NET declaration will look like this:

 [System.Security.SuppressUnmanagedCodeSecurity] [DllImport(dllName)] public static extern bool CompressData(byte[] inputData, uint inputSize, ref IntPtr outputData, ref uint outputSize); 

When you have outputData as IntPtr (this will indicate unmanaged memory), you can convert to an array of bytes by calling Copy in the Marshal class like this:

 var bytes = new byte[(int) outputSize]; // Copy. Marshal.Copy(outputData, bytes, 0, (int) outputSize); 

Please note that if your responsibility frees up memory, you can call the FreeCoTaskMem method:

 Marshal.FreeCoTaskMem(outputData); 

Of course, you can wrap this in something nicer, for example:

 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); } } 
+3
source

The pinvoke router cannot guess how large the returned byte [] is. Raw pointers to memory in C ++ do not have the visible size of the allocated memory block. This is why you added the uOutputSize argument. Good for the client program, but not good enough for the pinvoke marker. You should help and apply the [MarshalAs] attribute to pOutputBuffer by specifying the SizeParamIndex property.

Note that the array is copied by the marshaller. This is not so desirable, you can avoid this by allowing client code to pass an array. Marshaller will bind it and pass a pointer to the managed array. The only problem is that the client code will not have a decent way to guess how great is the possibility of creating an array. A typical solution is to allow the client to call it twice, first with uOutputSize = 0, the function returns the required size of the array. To make a C ++ function look like this:

 extern "C" __declspec(dllexport) int __stdcall CompressData( const unsigned char* pInputData, unsigned int inSize, [Out]unsigned char* pOutputBuffer, unsigned int uOutputSize) 
+1
source

All Articles