locked
Understanding the difference between using fixed{}, Marshal.AllocHGlobal()and GCHandle.Alloc() RRS feed

  • Question

  • 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 searching the web on 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!!
    Hyped1107
    Monday, January 3, 2011 1:05 AM

Answers

  • Hello Hyped1107,

    1. >> Why can't I use:

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

    and pass that into ReadLine() ?

    1.1 The difference between the above code and the initial one :

      GCHandle dataLineHandle = GCHandle.Alloc(dataLine, GCHandleType.Pinned);
      IntPtr dataLineAddress = dataLineHandle.AddrOfPinnedObject();

    are as follows :

    1.1.1 GCHandle.Alloc() wraps an already existing managed object. Marshal.AllocHGlobal() on the other hand freshly allocates memory and this memory that is allocated is specifically from the unmanaged heap of the application.

    1.1.2 Because the IntPtr that was obtained via GCHandle refers to an existing managed object, the managed object will be modified directly by the ReadLine() function. Hence you can directly access the (modified) values of "dataLine" right after calling ReadLine(), e.g. :

                for (int i = 0; i < numRead; i++)
                {
                    Console.WriteLine(dataLine[i]);
                }

    On the other hand, with the IntPtr obtained via Marshal.AllocHGlobal(), right after calling ReadLine(), you would need to copy the contents of "dataLineAddress" back into a managed array, e.g. :

            Marshal.Copy(dataLineAddress, dataLine, 0, numRead);
            Marshal.FreeHGlobal(dataLineAddress);

     

    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);
      } 

    2.1 The use of the "fixed" keyword and the Marshal.UnsafeAddrOfPinnedArrayElement() function are incorrect.

    2.2 The remarks section of "Marshal.UnsafeAddrOfPinnedArrayElement Method" http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.unsafeaddrofpinnedarrayelement.aspx) indicates the following :

    "The array must be pinned using a GCHandle before it is passed to this method."

    Hence the correct way to use Marshal.UnsafeAddrOfPinnedArrayElement() would be as follows :

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

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

                // get IntPtr representing address of first buffer element
                IntPtr dataLineAddress = Marshal.UnsafeAddrOfPinnedArrayElement(dataLine, 0);
                success = buffer.ReadLine(0, 0, 639, 0, dataLineAddress, out numRead);

                // Unpin the array
                dataLineHandle.Free();

    2.3 The "fixed" keyword allows you to write and use "unsafe" code in which direct pointers to memory may be used. See "Unsafe Code and Pointers (C# Programming Guide)" http://msdn.microsoft.com/en-us/library/t2yzs44b.aspx for more info.

    2.4 Hence, it would be more appropriately used if the ReadLine() function was defined as follows :

    bool ReadLine(int x1, int y1, int x2, int y2, byte* bufData, out int numRead);

    The following would be a sample call to such a ReadLine() function :

            unsafe public void TestUsingFixed()
            {
               ...
               ...
               ...
               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
                 success = buffer.ReadLine(0, 0, 639, 0, fixedDataLine, out numRead);
               }
               ...
               ...
               ...
            }

     

    - Bio.

     

    • Marked as answer by Cookie Luo Friday, January 7, 2011 9:04 AM
    Tuesday, January 4, 2011 4:50 PM