【问题标题】:Marshaling strings to unmanaged memory, passing them to c++ and return back to c# again将字符串编组到非托管内存,将它们传递给 c++ 并再次返回到 c#
【发布时间】:2015-06-24 15:12:21
【问题描述】:

我在 c# 中创建非托管内存块并用结构中的数据填充它。

我遍历结构列表并执行以下操作:

Marshal.StructureToPtr(structTemp, currentMemoryPosition, false);
currentMemPosition = new IntPtr(currentMemPosition.ToInt64() + structSize);      

结构包含引用类型:“字符串”。 我研究了 BOL 的 StructureToPtr 方法,它说:

"All other reference types (for example, strings and arrays) are marshaled to copies"

究竟是什么意思?

这是否意味着该字符串的引用仍将在内存中,尽管结构的实例将超出范围?

上面的非托管内存块,我传递给使用它的 c++ 方法。当工作在 c++ 部分完成时,我再次遍历内存中的结构(在 c# 中)并且:

Marshal.DestroyStructure(currentMemPosition, typeof(struct));

对我来说最重要的问题是:

Whether I can:

1) Create structs with strings inside
2) Marshal them to unmanaged mamory
3) Make use of them on c++ side
4) **Return them from c++**
5) Read them again in c#
6) Deallocate them in c# by using Marshal.DestroyStructure (EDITED)

字符串引用类型的结构布局为:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi, Pack = 1), Serializable]
internal struct TempStruct
{
    [MarshalAs(UnmanagedType.LPStr, SizeConst = 36)]
    private string iuTest;

    public TempStruct(Guid IuTest)
        : this()
    {
        iuTest = IuTest.ToString("D");
    }
}

【问题讨论】:

  • 要在 C# 中再次读取它们,您必须使用 Marshal.PtrToStructure 来“复制回”对 Marshal.StructureToPtr 创建的结构副本所做的更改。第 6 点是错误的:您必须使用 Marshal.DestroyStructure 解除分配副本。请注意,您必须释放为 Marshal.PtrToStructure 分配的内存
  • 我不会更改 c++ 端的字符串。假设我传递给 c++ 5 个字符串,c++ 返回其中 2 个(不接触它们)
  • 如何退货?
  • 你的结构布局是什么,具体来说,是固定大小的字符串并编组为LPStr
  • @xanatos 在 c++ 端 c# 的字符串被映射到:“char *” 我计划将它返回到一个新的结构数组中(在 SAFEARRAY 中返回),认为该指针是为c#中的字符串我可以从c++返回它

标签: c# marshalling


【解决方案1】:

“所有其他引用类型(例如,字符串和数组)都被封送为副本” 究竟是什么意思?

Marshal.StructureToPtr 创建string 的副本。即使您编组到LPWStr,它也会这样做。这与将参数传递给方法不同,有时字符串/数组不会被复制而是直接传递。

所以在调用Marshal.StructureToPtr 之后,您现在拥有iuTest 的两份副本:一份在变量iuTest 中,由.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*#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!
    }
}

结果:

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);

【讨论】:

  • SizeConst 不会被忽略 - 如果字符串没有在 MarshalAs 属性中指定的大小常量,则对 StructureToPtr 的调用将失败,这是您无法解决的 CLR 要求.
  • @aevitas 不,你没有。如果你想做一个UnmanagedType.ByValTStr,你需要它。它被忽略了。我看过Debug-&gt;Windows-&gt;Memory 生成的指针
  • 它可以被忽略,但 CLR 要求它能够为结构获得有意义的大小 - 没有它,您将违反 CLR 对结构强制执行的核心原则之一。
  • @aevitas 你知道[MarshalAs(UnmanagedType.LPStr)] 是什么吗?这意味着C中的结构将是struct MyStruct {char *str;},所以sizeof(MyStruct) == sizeof(char*),所以给定32或64位,结构的大小是固定的,对于str的任何大小。
  • 对我的监督 - 对于LPStr,这个要求当然是隐含的。我的立场是正确的。
猜你喜欢
  • 2013-05-03
  • 2010-12-18
  • 1970-01-01
  • 2018-03-08
  • 1970-01-01
  • 1970-01-01
  • 2011-09-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多