【问题标题】:How to pass address of objects created in a C# List to C++ dll?如何将 C# 列表中创建的对象的地址传递给 C++ dll?
【发布时间】:2011-09-14 12:33:45
【问题描述】:

我一直在谷歌上寻找一些问题的答案,但不太明白我发现了什么。在使用 System.IO 读取一些文本文件后,我有一些对象被创建并存储在 C# List 中。之后,我想将这些对象中的每一个的引用(使用 const 指针)发送到 C++ dll 中的内部类,以便它可以使用它们来计算某些算法。

这是我正在做的一些简单示例(不是实际代码):

C# 类:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class SimpleClass
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string           Name;
    public float            Length;
}

对应的C结构体:

struct SimpleClass
{
    const char* Name;
    float Length;
};

存储在

List<SimpleClass> ItemList;

解析一些文本文件后。

然后调用下面的dll函数:

C#:

[DllImport("SimpleDLL")]
public static extern void AddSimpleReference(SimpleClass inSimple);

C:

void AddSimpleReference(const SimpleClass* inSimple)
{
   g_Vector.push_back(inSimple); // g_Vector is a std::vector<const SimpleClass* > type
}

我试过的是:

for(int i=0; i<ItemList.Count;++i)
{
    SimpleClass theSimpleItem = ItemList[i];
    AddSimpleReference(theSimpleItem);
}

最初,我认为只需使用赋值运算符就可以很容易地获得实际的引用/地址,因为 C# 中的类是通过引用传递的,但事实证明 C++ 类总是推送相同的地址值(临时引用的地址值)到容器中,而不是实际地址。如何获取实际的对象地址并将其发送到 C++ DLL,以便它可以只读访问 C# 对象?

更新:对于那些使用不安全代码发布答案的人,我们深表歉意。我忘了说 C# 代码实际上是 Unity 游戏引擎中的脚本,它不允许使用不安全的代码。

【问题讨论】:

    标签: c# c++ pointers dll pass-by-reference


    【解决方案1】:

    首先,您需要更改互操作签名以获取指针(从而使其不安全)。

    [DllImport("SimpleDLL")]
    public unsafe static extern void AddSimpleReference(SimpleClass* inSimple);
    

    然后,由于 GC 可以随心所欲地在内存中移动对象,您需要将对象固定在内存中整个时间,您将需要它在非托管端的地址。为此,您需要 fixed 声明:

    SimpleClass theSimpleItem = ItemList[i];
    unsafe
    {
        fixed(SimpleClass* ptr = &theSimpleItem)
        {
            AddSimpleReference(ptr);
        }
    }
    

    如果AddSimpleReference 使用指针然后丢弃它,这将起作用。但是您将指针存储在 std::vector 中以备后用。这是行不通的,因为一旦执行离开fixed 块,GC 将原始项目移动到其他地方,指针可能会变得无效。

    要解决此问题,您需要固定项目,直到完成。为此,您可能需要使用 GCHandle 类型。

    // Change the interop signature, use IntPtr instead (no "unsafe" modifier)
    [DllImport("SimpleDLL")]
    public static extern void AddSimpleReference(IntPtr inSimple);
    
    // ----
    for(int i=0; i<ItemList.Count;++i)
    {
        SimpleClass theSimpleItem = ItemList[i];
        // take a pinned handle before passing the item down.
        GCHandle handle = GCHandle.Alloc(theSimpleItem, GCHandleType.Pinned);
        AddSimpleReference(GCHandle.ToIntPtr(handle));
        // probably a good idea save this handle somewhere for later release
    }
    
    // ----
    // when you're done, don't forget to ensure the handle is freed
    // probably in a Dispose method, or a finally block somewhere appropriate
    GCHandle.Free(handle);
    

    在执行此类操作时,请记住将对象长时间固定在内存中是一个坏主意,因为它会阻止垃圾收集器有效地完成其工作。

    【讨论】:

    • 分配和存储SimpleItem的“完整代码”是这样的吗? SimpleClass theSimpleItem; GCHandle 句柄 = GCHandle.Alloc(theSimpleItem, GCHandleType.Pinned); m_ItemList.Add(theSimpleItem); ?
    • 我认为您的上次编辑使我的理解更加清晰。 m_ItemList 可以由典型的 C# new 指令填充,然后我们可以从列表中的每个 SimpleClass 对象中获取固定句柄。我说的对吗?
    • @fisyher:马丁让我告诉你:“是的。”
    【解决方案2】:

    尽管我认为这不是一个好主意,但请查看不安全代码和内存固定。 Here 是 MSDN 上一个好的开始。

    fixedunsafe 关键字可能是您应该寻找的。​​p>

    【讨论】:

    • 我不能使用unsafe代码,因为C#代码实际上是Unity游戏引擎中的脚本,不允许使用“unsafe”关键字。
    【解决方案3】:

    你不能。 C# GC 会为了好玩而移动对象。您的地址将超出范围。

    【讨论】:

    • 我已经阅读了诸如 GCHandle 和 gcroot 之类的选项来固定创建的对象,但它们似乎使代码复杂化了很多,并且由于性能问题,不鼓励在应用程序的整个持续时间内存储固定的对象我不是很清楚。如果使用 GCHandle 是唯一的方法并且我不想使用它,我想我需要重新设计我的类。感谢您的回复。
    • 内存固定可用于将对象固定到位,因此垃圾收集不会压缩/移动这些对象。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多