"All other reference types (for example, strings and arrays) are marshaled to copies"
What exactly does it mean?
The Marshal.StructureToPtr
creates copies of the string
. It does it even if you are marshaling to LPWStr
. This is different from passing parameters to methods, where sometimes strings/arrays aren't copied around but are passed directly.
So after calling Marshal.StructureToPtr
you now have two copies of your iuTest
: one in the variable iuTest
, that is directly managed by .NET (and so that will be deallocated automatically), and one in the copy created by Marshal.StructureToPtr
. This copy must be destroyed manually, for example with Marshal.DestroyStructure
.
Note that the SizeConst = 36
is ignored here, because the exact quantity of memory needed will be allocated by Marshal.StructureToPtr
.
Full example:
C#
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1), Serializable]
internal struct TempStruct
{
[MarshalAs(UnmanagedType.LPStr)]
public string iuTest;
public TempStruct(Guid IuTest)
: this()
{
iuTest = IuTest.ToString("D");
}
}
[DllImport("NativeLibrary", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void TempStructMethod(IntPtr ptr);
and then:
var str = new TempStruct(Guid.NewGuid());
Console.WriteLine("C# side: {0}", str.iuTest);
// It will be 4 or 8, depending if you are running 32 or 64 bits
int size = Marshal.SizeOf(typeof(TempStruct));
IntPtr ptr = Marshal.AllocCoTaskMem(size);
// Marshaling to ptr
Marshal.StructureToPtr(str, ptr, false);
// Call your C++ method
TempStructMethod(ptr);
Console.WriteLine("C# side in original struct: {0}", str.iuTest);
// Marshaling back
str = (TempStruct)Marshal.PtrToStructure(ptr, typeof(TempStruct));
Console.WriteLine("C# side after marshaled back: {0}", str.iuTest);
// Freeing the "content" of the marshaled struct (the marshaled
// string in this case)
Marshal.DestroyStructure(ptr, typeof(TempStruct));
// Freeing the memory allocated for the struct object (the
// "container")
Marshal.FreeCoTaskMem(ptr);
and C++ (CoTaskMem*
are in #include <Objbase.h>
):
extern "C"
{
__declspec(dllexport) void TempStructMethod(TempStruct *ts)
{
printf("C++ side: %s\n", ts->iuTest);
// If you want to free a C# marshaled string use CoTaskMemFree
// See in https://github.com/dotnet/coreclr/blob/4cf8a6b082d9bb1789facd996d8265d3908757b2/src/vm/fieldmarshaler.cpp
// FieldMarshaler_StringAnsi::DestroyNativeImpl and
// FieldMarshaler_StringUni::DestroyNativeImpl
// Here we want to modify the string C-side
// First we free it
CoTaskMemFree(ts->iuTest);
ts->iuTest = NULL;
char *str = "abcdefab-cdef-abcd-efab-cdefabcdefab";
int len = strlen(str) + 1;
// Then we allocate a new string
// Use CoTaskMemAlloc to allocate memory that can be freed by C#
ts->iuTest = (char*)CoTaskMemAlloc(len);
// Then we copy our string in the allocated memory
strcpy(ts->iuTest, str);
// Note that you could have reused the "original"
// memory of ts->iuTest in this case, because it
// was of the "right" size. I freed and reallocated
// just to show how to do it!
}
}
Result:
C# side: d3ccb13c-fdf9-4f3d-9239-8d347c18993c
C++ side: d3ccb13c-fdf9-4f3d-9239-8d347c18993c
C# side in original struct: d3ccb13c-fdf9-4f3d-9239-8d347c18993c
C# side after marshaled back: abcdefab-cdef-abcd-efab-cdefabcdefab
C#-side you can even use the char*
pointer of the marshaled struct... You know that it is at offset 0 of the marshaled struct (because it is the first field), so:
IntPtr ptr2 = Marshal.ReadIntPtr(ptr, 0); // will read the char* pointer
string str2 = Marshal.PtrToStringAnsi(ptr2);
Console.WriteLine("Unmarshaling manually: {0}", str2);
(not directly connected to the question, asked in char):
marshalling an array from C# to C++, marshalling back from C++ to C# another array:
C++
struct TempStruct
{
char* iuTest;
};
extern "C"
{
__declspec(dllexport) void GetSomeData(TempStruct *inPtr, TempStruct **outPtr, int *numPtr)
{
// Number of elements we want to return
*numPtr = 10;
// Space for these elements
*outPtr = (TempStruct*)malloc(*numPtr * sizeof(TempStruct));
for (int i = 0; i < *numPtr; i++)
{
TempStruct *curr = *outPtr + i;
// Here we allocate some space for the char* iuTest
curr->iuTest = (char*)malloc(10);
// And we write something on it (in this case the 'i')
itoa(i, curr->iuTest, 10);
}
}
__declspec(dllexport) void FreeSomeData(TempStruct *ptr, int num)
{
for (int i = 0; i < num; i++)
{
TempStruct *curr = ptr + i;
// First we free the char* iuTest
free(curr->iuTest);
}
// Then we free the ptr
free(ptr);
}
}
C#
// Some TempStruct(s) to pass to C++
TempStruct[] someData = new TempStruct[5];
for (int i = 0; i < someData.Length; i++)
{
someData[i] = new TempStruct(Guid.NewGuid());
}
// C++ will return its TempStruct array in ptr
IntPtr ptr;
int length;
GetSomeData(someData, out ptr, out length);
// This must be equal to C++ sizeof(TempStruct)
int size = Marshal.SizeOf(typeof(TempStruct));
TempStruct[] someData2 = new TempStruct[length];
for (int i = 0; i < length; i++)
{
// We marshal back an element of TempStruct
IntPtr curr = (IntPtr)(ptr + (size * i));
someData2[i] = (TempStruct)Marshal.PtrToStructure(curr, typeof(TempStruct));
}
// Important! We free the TempStruct allocated by C++. We let the
// C++ do it, because it knows how to do it.
FreeSomeData(ptr, length);