Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Question
Wednesday, July 11, 2018 10:35 AM
Hi,
I do a lot of Win32 calls from C#. It's not clear to me when I need to use Marshal.AllocHGlobal() to create buffers for data transfer to/from the API. For instance, the following code works using normal buffers from C#. Why should I ever bother with Marshal.AllocHGlobal()?
Thanks.
byte[] InBuf = new byte[16];
byte[] OutBuf = new byte[16];
unsafe
{
fixed ( byte *p = InBuf )
{
*((Int16*) p) = 2;
*((UInt16*) (p+2)) = 0;
}
}
IsSuccess = DeviceIoControl
( hTestDevice, Win32.CTL_CODE ( 40000, 0x800, Win32.METHOD_BUFFERED, Win32.FILE_ANY_ACCESS ),
InBuf, 4,
OutBuf, 4,
out BytesReturned,
IntPtr.Zero
);
All replies (6)
Thursday, July 12, 2018 2:34 PM ✅Answered | 1 vote
" I presume the buffers are allocated on the stack,"
No they aren't. Reference types (of which arrays are) are allocated in the heap. Any memory in the heap can be moved around by the CLR at any point. So your local variables are stack based upon what they point to aren't. Hence if you pass that pointer to unmanaged code then the data may very well move out from under you. That's sort of what the fixed keyword is for.
But none of this really matters for AllocHGlobal. The purpose of this method is to allocate memory globally so it can be accessed outside the process. The managed arrays you're creating aren't accessible outside the .NET app. When you pass them via the Marshal call then the memory allocated (and deallocated) by the marshaler. Hence it is handling the dirty work for you. This is mostly explained in MSDN under the marshaling topic but there could be optimizations that aren't discussed as well.
If you are just making a single call to unmanaged code with those arrays then marshal is fine, and smaller. But things fall apart if you are making multiple calls or that memory needs to persist beyond the life of the call. For example, if you have a native call that requires you provide it a buffer and then it will read/write that buffer for the life of your app then Marshal won't work anymore because the memory is deallocated on the call return. You can use AllocHGlobal to allocate memory that is accessible from unmanaged code and will persist until you clean it up. The downside to this approach is that you cannot simply access it in .NET anymore as a regular managed object.
If your code is working and is performant then there is no reason to use AllocHGlobal. This is especially true for small arrays like in your example. If you need large blocks of memory though then AllocHGlobal may be a better choice.
Michael Taylor http://www.michaeltaylorp3.net
Thursday, July 12, 2018 4:05 PM ✅Answered | 1 vote
Your code will work because the marshaler is handling the details. It auto-pins for the life of the call so you don't really need to change anything in your code. It will work as you expect.
Michael Taylor http://www.michaeltaylorp3.net
Thursday, July 12, 2018 9:16 AM
Hi,
>>When do I need to use Marshal.AllocHGlobal()?
From MSDN document about this method:
Marshal.AllocHGlobal Method (IntPtr): Allocates memory from the unmanaged memory of the process by using the pointer to the specified number of bytes.
Marshal.AllocHGlobal Method (Int32): Allocates memory from the unmanaged memory of the process by using the specified number of bytes.
When AllocHGlobal calls LocalAlloc, it passes a LMEM_FIXED flag, which causes the allocated memory to be locked in place. Also, the allocated memory is not zero-filled.
Please refer to the demo from Marshal Class to understand when to use it:
using System;
using System.Text;
using System.Runtime.InteropServices;
public struct Point
{
public Int32 x, y;
}
public sealed class App
{
static void Main()
{
// Demonstrate the use of public static fields of the Marshal class.
Console.WriteLine("SystemDefaultCharSize={0}, SystemMaxDBCSCharSize={1}",
Marshal.SystemDefaultCharSize, Marshal.SystemMaxDBCSCharSize);
// Demonstrate the use of the SizeOf method of the Marshal class.
Console.WriteLine("Number of bytes needed by a Point object: {0}",
Marshal.SizeOf(typeof(Point)));
Point p = new Point();
Console.WriteLine("Number of bytes needed by a Point object: {0}",
Marshal.SizeOf(p));
// Demonstrate how to call GlobalAlloc and
// GlobalFree using the Marshal class.
IntPtr hglobal = Marshal.AllocHGlobal(100);
Marshal.FreeHGlobal(hglobal);
// Demonstrate how to use the Marshal class to get the Win32 error
// code when a Win32 method fails.
Boolean f = CloseHandle(new IntPtr(-1));
if (!f)
{
Console.WriteLine("CloseHandle call failed with an error code of: {0}",
Marshal.GetLastWin32Error());
}
}
// This is a platform invoke prototype. SetLastError is true, which allows
// the GetLastWin32Error method of the Marshal class to work correctly.
[DllImport("Kernel32", ExactSpelling = true, SetLastError = true)]
static extern Boolean CloseHandle(IntPtr h);
}
Regards,
Stanly
MSDN Community Support
Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact [email protected].
Thursday, July 12, 2018 11:59 AM
Thank you Stanly Fan.
My question is not so much about how to use Marshal.AllocHGlobal(), but why do I use it? Will my program crash if I don't use it?
In the code fragment I have shown, I presume the buffers are allocated on the stack, therefore are fixed in location. Are there situations when stack-residing buffers are moved?
Thursday, July 12, 2018 3:59 PM
Many thanks for your in-depth explanation, CoolDadTx. It's helpful. I tried reading Microsoft's documents and just ended up scratching my head.
I can only guess why my code worked. 1) just luck that I haven't yet been hit with a memory move. 2) the marshaller was intelligent enough to see non-fixed memory buffer and did its own AllocHGlobal() call and memory copy to compensate. Without the certainty of knowing what is going on, I will assume the worst - that what I did was unsafe.
Thursday, July 12, 2018 4:26 PM
Your code will work because the marshaler is handling the details. It auto-pins for the life of the call so you don't really need to change anything in your code. It will work as you expect.
Michael Taylor http://www.michaeltaylorp3.net
Thank you for the assurance. Then I will carry on as I did.