You are
assuming that the data array is at least as big a size. This is a possible bug waiting to happen
Not checking the result of malloc()
You may not need to do this anyway, simply passing the pinned byte pointer should be fine (unless the called fx() function in some way changes the data in which case the copy is perhaps a requirement). Based on the memory being freed later it is likely that this is not a possibility though.
With that in mind here is a fixed up version
void managed_fx (byte data __gc[], size_t size)
{
if (data.Length < size)
throw gcnew System.IndexOutOfRangeException(
"the unmanaged buffer is not big enough");
// Pin the data
byte __pin *pinned_data = &data [0];
// Copy data to the unmanaged buffer.
void *unmanaged_data = malloc (size);
if (unmanaged_data == NULL)
throw gcnew OutOfMemoryException(
"could not allocate memory for the unmanaged buffer");
memcpy (unmanaged_data, (byte*) pinned_data, size);
// Forward the call
fx (unamanged_data, size);
}
To answer the further questions:
The pinning is necessary to do the memcpy. It does not prevent the deletion of the memory by the GC runtime (well technically it does but so does simply holding of a reference to it) it prevents it moving in memory. Since memcpy is an unmanaged function - it cannot handle the pointers it is manipulating being moved about in memory.
malloc is entirely unmanaged (though the runtime libraries are free to use reserved areas of the managed heap to do this).
There is no clear distinction in source C++/CLI as to whether 'code' is managed or unmanaged. On compilation some parts will be CIL intermediate code, others plain native code the important thing is whether the variables used are managed or unmanaged. A managed reference (anything with the ^) can only be manipulated/used by code which is itself managed. This means the runtime is free to alter the values of the underlying pointers used as it sees fit if it alters the location of the managed object in memory. It does this in a way that is safe to managed functions (they are paused while the world is changed around them).
Since many useful functions exist with no knowledge of how to handle such managed references you may end up needing to take a simple pointer to them.
Since this operation is unsafe (if the GC kicks in a moves the reference about) the pinning pointer exists to make this safe and simple to do since it both takes a pointer and for the life of that pointer prevents the runtime moving the pointed to variable.