如何将C#List中创建的对象的地址传递给C++ DLL?

fis*_*her 3 c# c++ dll pointers pass-by-reference

我一直在寻找谷歌找到我的问题的一些答案,但不太明白我发现了什么.在使用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;
}
Run Code Online (Sandbox Code Playgroud)

与相应的C结构:

struct SimpleClass
{
    const char* Name;
    float Length;
};
Run Code Online (Sandbox Code Playgroud)

存储在

List<SimpleClass> ItemList;
Run Code Online (Sandbox Code Playgroud)

解析一些文本文件后.

然后调用以下dll函数:

C#:

[DllImport("SimpleDLL")]
public static extern void AddSimpleReference(SimpleClass inSimple);
Run Code Online (Sandbox Code Playgroud)

C:

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

我试过的是:

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

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

更新:对于那些使用不安全代码发布答案的人抱歉.我忘了提到C#代码实际上是用作Unity游戏引擎中的一个脚本,它不允许使用不安全的代码.

R. *_*des 6

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

[DllImport("SimpleDLL")]
public unsafe static extern void AddSimpleReference(SimpleClass* inSimple);
Run Code Online (Sandbox Code Playgroud)

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

SimpleClass theSimpleItem = ItemList[i];
unsafe
{
    fixed(SimpleClass* ptr = &theSimpleItem)
    {
        AddSimpleReference(ptr);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果AddSimpleReference使用指针然后丢弃它,这将工作.但是你将指针存储在std::vectorfor 中以供日后使用.这是行不通的,因为一旦执行离开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);
Run Code Online (Sandbox Code Playgroud)

在做这样的事情时,请记住长时间在内存中固定对象是个坏主意,因为它会阻止垃圾收集器有效地完成其工作.