Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding difference between use of fixed{}, Marshal.AllocHGlobal()and GCHandle.Alloc()

Let me start by saying I've looked and found descriptions of the use of fixed{}, Marshal.AllocHGlobal()and GCHandle.Alloc() throughout this forum and in many links on the web. However, I have yet to find a concise explanation of when to use the Marshal class vs. the GCHandle class (with and without using fixed{}).

I'm using a third-party .NET library which has a method called Readline() in a "Buffer" class. The manual shows the following function prototype:

bool ReadLine(int x1, int y1, int x2, int y2, System.IntPtr bufData, out int numRead);

with a description of bufData that says: ...The memory area must have a number of bytes larger than or equal to the line length times the value returned by the BytesPerPixel property.

Now later in the user manual they do give an example of accessing the buffer (which I've tweaked a little bit for my specific example):

// Create an array large enough to hold one line from buffer
int size = 640; 
byte[] dataLine = new byte[size * 2];   // 2 bytes per pixel

// Pin the array to avoid Garbage collector moving it  
GCHandle dataLineHandle = GCHandle.Alloc(dataLine, GCHandleType.Pinned); 
IntPtr dataLineAddress = dataLineHandle.AddrOfPinnedObject(); 

and I could follow the above "example" code with:

// Read one line of buffer data 
success = buffer.ReadLine(0, 0, 639, 0, dataLineAddress, out numRead); 

// Unpin the array 
dataLineHandle.Free() 

That could be the end of the story (and I have yet to test the above code), but I ended up googling the GCHandle class which drove me down the path of .NET interoperability, pInvoke, etc.

So my questions... 1) Why can't I use:

IntPtr dataLineAddress = Marshal.AllocHGlobal( size * 2 );

and pass that into ReadLine()?

2) Can I also use the following snippet of code (extracted and tweaked from examples on the web):

int size = 640;
byte[] dataLine= new byte[size * 2];  // 2 bytes per pixel

// prevent garbage collector from moving buffer around in memory
fixed (byte* fixedDataLine = dataLine)
{
  // get IntPtr representing address of first buffer element
  IntPtr dataLineAddress= Marshal.UnsafeAddrOfPinnedArrayElement(fixedDataLine , 0);
  success = buffer.ReadLine(0, 0, 639, 0, dataLineAddress, out numRead);
}

I'd be interested in anyone can shed light on the above techniques and point out my errors in implementation as well as point out when the above methods are appropriate. Finally, even if the above methods are all valid, is there a general push in the past few years toward one approach or the other?

Thanks in advance!! Hyped

like image 867
Hyped Avatar asked Jan 02 '11 19:01

Hyped


1 Answers

Well, the alternative will likely work too. But the Marshal.AllocHGlobal sample isn't complete, you've now got the data in unmanaged memory. You still need to do work to get it into a managed object (array) so you can easily access it, you must call Marshal.Copy(). Inefficient since that copies the data twice. And don't forget to call Marshal.FreeHGlobal().

The fixed sample does the same thing as the vendor sample, it implicitly pins the memory. Awkwardness here is that the API takes an IntPtr, not a byte*. And you must change compile settings to allow the unsafe keyword. It isn't otherwise more efficient.

You're not ahead by doing this differently.

like image 139
Hans Passant Avatar answered Nov 16 '22 01:11

Hans Passant