GlobalAlloc flags for Marshal.PtrToStructure

1

Short version:

Could passing the handle from GlobalAlloc(GMEM_MOVEABLE, Size) to Marshal.PtrToStructure() and Marshal.FreeHGlobal() cause memory corruption?

Long version:

I'm using Windows Global memory allocation to pass a data structure back and forth between a Delphi and C# application (the fact that it's Delphi isn't really significant to this question, because it's just Win32 API calls).

On the Delphi side, I pass in a record, it allocates the space, locks the memory, copies the structure into memory, and the unlocks the memory:

function MarshalRec(SourceRec: TInteropItemRec): THandle;
var
  Size: integer;
  Buffer: Pointer;
begin
  Size := sizeof(SourceRec);
  result := GlobalAlloc(GMEM_MOVEABLE and GMEM_ZEROINIT, Size);
  Buffer := GlobalLock(result);
  try
    CopyMemory(Buffer, @SourceRec, Size);
  finally
    GlobalUnlock(result);
  end;
end;

On the C# side, it gets that THandle (which is basically an unsigned int) into an IntPtr and uses Marshal.PtrToStructure to copy the data into the C# structure:

public void FromMemory(IntPtr Source)
{
    Marshal.PtrToStructure(Source, this);
    Marshal.FreeHGlobal(Source);
}

The problem I'm running into, is very rarely (as in 4 times over 6 months for me), the whole application goes down (This application has encountered an error and must close). If I try to pause execution in Visual Studio, I get "A fatal error has occurred and debugging needs to be terminated. For more details, please see the Microsoft Help and Support web site. HRESULT=0x80131c08."

Anyway, we managed to get a couple logs of it happening, and in both cases, it showed a recent call to that "MarshalRec" function above, a few other function calls, and then some processing of Windows messages on the event loop on the Delphi thread (yeah, it has its own thread and event loop to deal with a time sensitive device driver).

So my suspicion is falling on the GMEM_MOVEABLE flag to GlobalAlloc. I couldn't find anything in the Marshal class that does the GlobalLock and GlobalUnlock stuff, so I had assumed it was handled internally by PtrToStructure().

Does PtrToStructure deal properly with a handle, or does it need an actual pointer obtained from GlobalLock()? Is it possible that in very rare cases, Windows happens to move the memory I allocated, which means I need to call GlobalLock() to get an actual pointer to pass in? That FreeHGlobal is actually freeing something it shouldn't have, which brings down the whole app the next time that resource is accessed?

And if so, should changing the GMEM_MOVABLE to GMEM_FIXED prevent this from happening again? Or do I need to DllImport GlobalLock() and GlobalUnlock()?

I'm tempted to just blindly make those changes, but given the non-reproducibility of this issue, there's no way to tell if it's fixed until it happens again. So I'm looking for feedback on whether this code could lead to the symptoms I'm seeing, or if I need to start coming up with other theories.

c#
interop
marshalling
asked on Stack Overflow Nov 18, 2010 by Bryce Wagner

1 Answer

2

Well, you are explicitly violating the contract for GlobalAlloc(). Your hope that Marshal.PtrToStructure() will call GlobalLock is unfounded, it has no way to tell whether the passed IntPtr is a handle or a pointer.

GlobalAlloc is a fairly hopelessly outdated legacy function from the Windows 3.x era. Yes, it is quite likely to return the address as the handle value with GlobalLock() being a no-op. But it is certainly not documented to do this. CoTaskMemAlloc() is the better mouse trap.

answered on Stack Overflow Nov 18, 2010 by Hans Passant

User contributions licensed under CC BY-SA 3.0