P/Invoke Marshal.StructuretoPtr
In this blog I am going to talk about Marshal.StructuretoPtr , especially its last parameter fDeleteOld. Although its msdn description is pretty concise but I have seen it to be a source of confusion a lot of times. Following is what it says:
fDeleteOld
Type: System..::.Boolean
true to have the Marshal..::.DestroyStructure method called on the ptr parameter before this method executes. Note that passing false can lead to a memory leak.
Lets start with an example , lets say that you have a structure Interop_Struct that you would like to marshal across managed/native boundary ie from C# to C+ + typically.
Following is the structure definition in C++ :
struct Interop_Struct
{
int num;
char* ptr;
}
Following is corresponding definition in c#:
[StructLayout(LayoutKind.Sequential)]
struct Interop_Struct
{
int num;
[MarshalAs(UnmanagedType.LPStr)] string ptr;
}
Now say you would like to marshal it using Marshal.StructuretoPtr ,
//Allocate managed structure
Interop_Struct op= new Interop_Struct() ;
//Allocate corresponding native structure
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(op));
//Marshal the structure
Marshal.StructureToPtr(op, ptr, true);//This is wrong!!
Now what would happen once you run the above code. There is something gravely wrong with it that would go unnoticed most of the times.
To illustrate what I am saying, enable page heap for the process, using gfalgs utillity from Debugging Tools for Windows. Now run the process again , under the debugger and it would crash consistently everytime. Following is the callstack :
08 0026f080 64b06f95 00061000 c0c0c0c0 00060000 verifier!AVrfpDphFindBusyMemoryNoCheck+0xb8
09 0026f0a4 64b07240 00061000 c0c0c0c0 0026f114 verifier!AVrfpDphFindBusyMemory+0x15
0a 0026f0c0 64b09080 00061000 c0c0c0c0 00d6ed7e verifier!AVrfpDphFindBusyMemoryAndRemoveFromBusyList+0x20
0b 0026f0dc 77dd5674 00060000 01000002 c0c0c0c0 verifier!AVrfDebugPageHeapFree+0x90
0c 0026f124 77d97aca 00060000 01000002 c0c0c0c0 ntdll!RtlDebugFreeHeap+0x2f
0d 0026f218 77d62d68 00000000 c0c0c0c0 013213f0 ntdll!RtlpFreeHeap+0x5d
0e 0026f238 769a3ffa 00060000 00000000 c0c0c0c0 ntdll!RtlFreeHeap+0x142
0f 0026f24c 769a4016 76a966bc c0c0c0c0 0026f268 ole32!CRetailMalloc_Free+0x1c
10 0026f25c 6d90fa18 c0c0c0c0 0026f27c 6d8e0224 ole32!CoTaskMemFree+0x13
11 0026f268 6d8e0224 03826ffc 032e2c38 01323078 mscorwks!FieldMarshaler_StringUni::DestroyNativeImpl+0x16
12 0026f27c 6d8f36c7 03826ff8 01323078 a0af14fa mscorwks!LayoutDestroyNative+0x3a
13 0026f32c 01e1010d 00000001 03c7331c 03c7331c mscorwks!MarshalNative::StructureToPtr+0x13f
WARNING: Frame IP not in any known module. Following frames may be wrong.
14 0026f360 6d7f1b6c 0026f3ac 00000000 0026f3f0 0x1e1010d
15 0026f370 6d802209 0026f440 00000000 0026f410 mscorwks!CallDescrWorker+0x33
16 0026f3f0 6d816511 0026f440 00000000 0026f410 mscorwks!CallDescrWorkerWithHandler+0xa3
17 0026f528 6d816544 0132c030 0026f5f4 0026f5c0 mscorwks!MethodDesc::CallDescr+0x19c
18 0026f544 6d816562 0132c030 0026f5f4 0026f5c0 mscorwks!MethodDesc::CallTargetWorker+0x1f
19 0026f55c 6d880c45 0026f5c0 a0af1116 00000000 mscorwks!MethodDescCallSite::CallWithValueTypes_RetArgSlot+0x1a
1a 0026f6c0 6d880b65 01322ffc 00000001 0026f6fc mscorwks!ClassLoader::RunMain+0x223
1b 0026f928 6d8810b5 00000000 a0af1a2e 00000001 mscorwks!Assembly::ExecuteMainMethod+0xa6
1c 0026fdf8 6d88129f 012e0000 00000000 a0af199e mscorwks!SystemDomain::ExecuteMainMethod+0x456
1d 0026fe48 6d8811cf 012e0000 a0af1946 00000000 mscorwks!ExecuteEXE+0x59
1e 0026fe90 6be6571d 6d881137 0026fea8 6bf5ae72 mscorwks!_CorExeMain+0x15c
1f 0026fe9c 6bf5ae72 6be60000 0026febc 6bf55121 mscoreei!CorExeMain+0x20
20 0026fea8 6bf55121 00000000 77921174 7ffd3000 MSCOREE!GetMetaDataInternalInterface+0x2d5
21 0026febc 77d6b3f5 7ffd3000 78d7eb0a 00000000 MSCOREE!CorExeMain+0x8
22 0026fefc 77d6b3c8 6bf55119 7ffd3000 00000000 ntdll!__RtlUserThreadStart+0x70
23 0026ff14 00000000 6bf55119 7ffd3000 00000000 ntdll!_RtlUserThreadStart+0x1b
As is evident from call-stack CoTaskMemFree is being called on an address which is not pointing to process heap (c0c0c0). Now the reason why it is called is also evident from callstack, Marshal.StructuretoPtr is calling LayoutDestroyNative which internally is calling CoTaskMemFree.
So for native structures which have pointers as members , if you pass TRUE as the value of fdeleteOld parameter to Marshal.structureToPtr, CoTaskMemFree will be called on the member variables of structure which are pointers so that the corresponding allocated memory is freed , before the corresponding new value of that member variable from managed structure is copied to native structure.
If you are allocating native structure and not assigning values to it so that member variables which are pointers are pointing to allocated memory on process heap , you should be pasing false as the third parameter and not true.
In the above sample if you pass false as last parameter to Marshal.structuretoPtr , it would not crash even with page heap enabled.
As for the reason why without enabling page heap crash is random is , because many times the pointer member variable might be holding a valid address(although not correct) , and so calling CoTaskMemFree on it does not cause an immediate AV however note that it has corrupted the heap and eventually it would cause problems , which would be difficult to diagnose.
Using false avoids the call to CoTaskMemFree which causes the crash. However make sure that you use it judicially , pass true if ptr is pointing to a pre-allocated structure , which has members which needs to be freed. Also you still need to make sure that you call Marshal.FreeHGlobal on the ptr once you are done with it.
Comments
Anonymous
April 13, 2010
Great Blog.. Cool Background... In spite of this parameter is described in MSDN, nobody really knows what do use there. i tried true and it worked.. good. now i have windows 7 and voilá.. it hasen't worked.. NicoAnonymous
April 15, 2010
What exactly do you mean by saying , it didnt work.Anonymous
July 28, 2010
Awesome post! I was wondering why does the third parameter set to true crash my application. Now I know, thanks to mjawa!Anonymous
December 05, 2010
I do agree with the Blog, I do have experienced the same problem / exception and just for experiment I tried to send false in the function call and it worked. Thanks, maheshAnonymous
May 08, 2011
This is Awesome.. I have tried lot of ways to stop the crash but finally i reached to this post.. it saved lot of my time.. thanks guys...Anonymous
October 21, 2013
using false in third parameter causing memory leakAnonymous
June 23, 2015
Thank you so much! I've just been struglling a very hard case of memory corruption with no leads at all. I came accross this blog, and bingo!!! this was exatly the issue.