“所有其他引用类型(例如字符串和数组)都被编组到副本”
它到底是什么意思?
The Marshal.StructureToPtr
创建的副本string
。即使您正在编组,它也会这样做LPWStr
。这与将参数传递给方法不同,在方法中有时不会复制字符串/数组,而是直接传递。
所以打电话后Marshal.StructureToPtr
您现在有两份副本iuTest
: 变量中的一iuTest
,由 .NET 直接管理(因此将自动释放),并且在由 .NET 创建的副本中Marshal.StructureToPtr
。该副本必须手动销毁,例如使用Marshal.DestroyStructure
.
请注意,SizeConst = 36
这里被忽略,因为所需的确切内存量将由Marshal.StructureToPtr
.
完整示例:
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);
进而:
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);
和 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# 端你甚至可以使用char*
封送结构的指针...您知道它位于封送结构的偏移量 0 处(因为它是第一个字段),因此:
IntPtr ptr2 = Marshal.ReadIntPtr(ptr, 0); // will read the char* pointer
string str2 = Marshal.PtrToStringAnsi(ptr2);
Console.WriteLine("Unmarshaling manually: {0}", str2);
(与问题没有直接关系,在字符中询问):
将一个数组从 C# 编组到 C++,再从 C++ 编组回 C# 另一个数组:
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);