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.