【问题标题】:How to pass C# object references to and from C++如何将 C# 对象引用传入和传出 C++
【发布时间】:2015-11-13 07:39:22
【问题描述】:

有没有办法通过本机互操作将 C# 对象引用(类类型,而不是结构)传入和传出 C++?

这是我的用例:

我正在尝试用 C# 编写游戏引擎,但想使用本机 (C++) 物理库。物理库维护自己的所有物理体的内部状态,并让用户将少量数据(指针)与每个体关联。在物理模拟滴答之后,库提供所有移动的物理实体的列表。我想遍历这个列表并将所有更改传递给相应的 C# 对象。

问题是:将 C# 中的对象与 C++ 中相应的物理实体关联的最佳方法是什么?

我能想到几种方法:

  1. 为每个物理体指定某种 ID,将 ID 与 C++ 中的物理体相关联,并在 C# 中维护 ID 到相应对象的映射。这对我来说似乎是不必要的间接,即使使用优化的映射机制。
  2. 利用将 C# 委托(可以隐式引用 C# 对象实例)编组为 C++ 函数指针的能力。这可能是最好的方法;我不知道 C# 如何实现委托以及这会带来什么类型的开销。
  3. 找到其他方法来引用 C++ 中的 C# 对象并将该引用与每个本机物理体相关联。

有没有我不知道的选项 3 之类的机制? C++/CLI 不是一个选项,因为我想支持没有它的平台。

【问题讨论】:

  • 如果没有 C++/CLI 选项,P/Invoke 是接下来要考虑的事情。尽管通过 P/invoke 引用 c# 对象实际上是不可能的,并且委托也可能很难或不可能。但也许你可以从中得到一些东西。
  • 是的,P/Invoke 就是我说“本机互操作”时的意思;我没有意识到它有一个名字。使用 P/Invoke 将 C# 委托作为函数指针传递给 C++ 是完全可行的,我只是不知道它有什么样的开销。 msdn.microsoft.com/en-us/library/367eeye0.aspx
  • 找到另一个答案,描述了代表的一些(感知的)性能特征。 stackoverflow.com/a/2082975 也许使用它们是实现这一点的最佳方式。在 C++ 中迭代更改的主体列表时,只需为每个主体调用一个对应的委托,即可将新数据导入 C#-land。
  • Pinvoke 走错了路,有助于从 C# 调用本机代码,但反之则不行。让本机 C++ 了解外部类型系统以跨平台的方式看起来是什么样子是不可能的。微软在 COM 和 C++/CX 方面做得很好,但 *nix 的人不会与它有任何关系。不是在那里发明的。
  • C++ 代码实际上并不需要了解 C# 类型系统的任何信息;在这种情况下,它可以返回到 C# 的不透明指针将完成这项工作。只是说“嘿,你知道你告诉我的那件事吗?”我想它只是因为垃圾收集和重定位而不可用。

标签: c# c++ pinvoke


【解决方案1】:
  1. 映射唯一标识符将允许您将托管代码与非托管代码隔离开来。如果非托管方不对您的托管对象做任何事情可能会更好,因为您确实无法控制对象的大部分生命周期。
  2. 可以将委托作为函数指针。您需要做的就是定义您的委托类型并创建一个实例。然后您可以使用Marshal.GetFunctionPointerForDelegate 获取要传递给非托管端的地址。唯一需要注意的是,您必须确保回调使用的是__stdcall
  3. 可能可以直接访问类成员和函数,但如果没有 C++/CLI 包装器,您就是在玩火。
  4. 在 C# 端分配一块内存并将其传递给 C++。在托管端的类中,使用属性以适当的偏移量访问此内存。
  5. 制作对象COM visible

另外,请记住,AccessViolationExceptions 通常无法在 .NET 中捕获。

【讨论】:

  • #4 很有趣。我曾考虑为每个对象分配一块内存,但很快就拒绝了这个想法,因为没有办法为每个对象设置一个“固定”语句。但是,如果它们都共享一块内存,那么...
  • 通过分配一个块,我在想一些类似于Marshal.AllocCoTaskMemMarshal.AllocHGlobal 的东西。
  • 哇,我不知道这些方法存在。谢谢!我可以在我的代码中想到其他几个有用的地方。但现在想想,即使它们不可用,我自己也可以轻松实现 AllocHGlobal 和 FreeHGlobal。
  • 如果你真的很有野心,你也可以通过 p/Invoke 使用任何 WinAPI Memory Management Functions
【解决方案2】:

我建议使用专为此类情况设计的工具,即System.Runtime.InteropServices.GCHandle

用法

在:

  1. 使用GCHandleType.NormalGCHandleType.Weak 为您的CLR 对象分配GCHandle
  2. 通过GCHandle.ToIntPtrGCHandle 转换为IntPtr
  3. IntPtr 作为您提到的用户数据指针传递给 C++ 端。

输出:

  1. 从 C++ 端获取 IntPtr
  2. 通过GCHandle.FromIntPtrIntPtr 转换回GCHandle
  3. 使用GCHandleTarget 属性获取原始CLR 对象。

当不再从 C++ 端引用 CLR 对象时(因为 C++ 对象已被破坏或因为您将用户数据指针重置为例如 IntPtr.Zero),请通过调用 GCHandle.Free 释放 GCHandle

您应该分配什么类型的GCHandle 取决于您是否希望GCHandle 使对象保持活动状态(GCHandleType.Normal)或不(GCHandleType.Weak)。

【讨论】:

  • 在委托的情况下,我可以从 C++ 调用委托并在 C# 端使用 this 来获取对象引用。但这无关紧要。在这种情况下,有一个对象引用的重点是我可以在它上面调用一个方法。如果我可以直接调用该方法,它就可以完成工作。
  • 我不知道 GCHandle 的东西。我会调查那些。谢谢!
  • GCHandle 似乎正是我想要的。如果您摆脱了答案的委托部分,我会接受这个答案。
  • @JoBates 完成。我不确定删除那部分是否会改善我的答案,但因为我是一个贪婪的混蛋...... :)
  • 抱歉有条件录取。我已经有了使用我知道可行的委托的方法;真正的问题是“还有更直接的替代方案吗?”而 GCHandle 就完美地回答了这个问题。
猜你喜欢
  • 2012-06-22
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 2011-07-06
  • 1970-01-01
  • 2010-12-06
  • 2018-07-18
相关资源
最近更新 更多