2

I have a C++ Header File that gives me access to two functions that, after cutting stuff that is not necessary, are this:

extern __declspec (dllimport) bool __cdecl GetBinary(unsigned short* _allocatedBufferSizeBufferLength, char* _receiveBuffer);

The _data and _receiveBuffer parameters are supposed to be binary information, not the strings as char* would initially suggest.

The reference C++ implementation I have would use the functions like this:

char output;
unsigned short allocatedBufferLength = 1;
GetBinary(&allocatedBufferLength,&output);

My Import Declaration for these two right now looks like this:

[DllImport( "MyDriver.dll", EntryPoint = "GetBinary", CharSet = CharSet.Ansi, SetLastError = true, CallingConvention = CallingConvention.Cdecl )]
public static extern bool GetBinary( out ushort allocatedBufferSizeBufferLength, StringBuilder receiveBuffer );

With this in mind, what I try to get as a result are byte[] binary arrays. This works well in most cases. However, I have byte arrays with "0" bytes right in the middle. With the StringBuilder this will result in me only getting the first part of the array. How would I be able to reliably get the entire binary array/blob?

Edit: This is how I use it in my C# Method, normally I can extract the binary data from the string inside the StringBuilder:

StringBuilder outVar = new StringBuilder( 30 );
allocatedBufferLength = (ushort)(outVar.Length - 1);
UnsafeNativeMethods.GetBinary( out allocatedBufferLength, outVar );
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
private_meta
  • 561
  • 4
  • 19

2 Answers2

2

The protocol for this function appears to be that the caller allocates the buffer, and passes the length of the allocated buffer to the function by reference. I assume that the function modifies the length on return to contain the number of bytes copied.

You want to change the signature to return a byte array so that you can deal with text that contains null characters. So the C# would look like this:

[DllImport("MyDriver.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool GetBinary(
    ref ushort allocatedBufferSizeBufferLength, 
    [In, Out] byte[] receiveBuffer
);

Note that there is no need for CharSet since we have switched to a byte array. There are no text parameters. And note also that SetLastError is for use with functions that call the SetLastError Windows API function. I see no evidence that yours does so. Your function returns a C++ bool and so we marshal it as UnmanagedType.I1.

To call the function we must allocate a byte array for the function to populate. For instance:

ushort len = 256;
byte[] buffer = new byte[len];
bool retval = GetBinary(ref len, buffer);
if (!retval)
    // handle error
// process buffer

Presumably the length is passed by ref to allow the function to tell the caller how many bytes were actually copied.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Since the answer that helped me was deleted, I used this in the declaration for the byte[] parameter: `[MarshalAs( UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 0 )]` – private_meta Jun 02 '14 at 08:43
  • 1
    The `MarshalAs` attribute you include in the comment is fine, although it can be omitted here. The answer that was deleted did not contain any code that matched your unmanaged function. – David Heffernan Jun 02 '14 at 08:49
-1

Either you can use IntPtr instead of char* _receiveBuffer and search the memory pointer then for the string blocks terminated by a NULL terminator. Or you could try it with char[] as type for _receiveBuffer and walk through the array of chars.

Walking through the memory when using IntPtr would be something like:

        for (var i = 0; i < _allocatedBufferSizeBufferLength; i++)
        {
            var b = Marshal.ReadByte(ptr, i);
            // do something here with the byte just read
        }
AcidJunkie
  • 1,878
  • 18
  • 21
  • How do I access the data through the IntPtr? edit: I mean, a short example or reference would be nice. While setting this up I tried IntPtr once, but I couldn't get it to work. – private_meta May 28 '14 at 10:50
  • done. What was not working with the IntPtr? did you had a look at the memory pointed to by IntPtr? This can be done using the memory window of Visual Studio (Menu: Debug->Windows->Memory) – AcidJunkie May 28 '14 at 11:02
  • Apparently I have no idea how to use an IntPtr. I get AccessViolationExceptions. How do I actually set up the DllImport call and its usage? I tried IntPtr = new IntPtr(); with using ref, out and nothing as an addition to the IntPtr variable. The Memory Window, when trying to view "ptr" shows nothing (only question marks) – private_meta May 28 '14 at 11:17
  • When i have a look at the parameter naming of the function, i think the _receiveBuffer parameter has the wrong type. From what i understand, that should be `char** receiveBuffer` as this function allocates memory and returns the amount of allocated memory as well as a pointer to the allocated memory. Or am i wrong? – AcidJunkie May 28 '14 at 11:26
  • There are other functions in the API that return longs, shorts, strings. Strings are similar, and I can fetch them by using a StringBuilder, just as I wrote in my Question. With the StringBuilder code I assume that it doesn't try to allocate any memory, I need to allocate it, it just fills it. – private_meta May 28 '14 at 11:30
  • It's not enough to say, "use `IntPtr`". You also need to cover how the caller allocates it, and then reads the contents. But really it's way easier to allocate a byte array, for instance. You suggest using `char[]` which is closer, but fails because C++ `char` is 8 on Windows bit and `C#` char is 16 bit. – David Heffernan May 28 '14 at 13:09