从 C# 到 C++ 并返回的数组,没有不安全的代码 [重复]

Posted

技术标签:

【中文标题】从 C# 到 C++ 并返回的数组,没有不安全的代码 [重复]【英文标题】:Array from C# to C++ and back, without unsafe code [duplicate] 【发布时间】:2018-07-05 16:40:32 【问题描述】:

我知道这个问题已经被问过并且可能被认为是重复的,但在阅读了其他答案后,我仍然很困惑,不明白那里提供的解释。

如果有人能提供示例代码 一些解释,我将不胜感激。

我正在处理以下任务: 我在 Unity 中有一个纹理/纹理(无法从驱动器加载,动态生成),我将其转换为数组。它可以是 int、byte 或 float 的数组。

目前,我在 C# 中进行对象检测,但我想让它更快。因此,我想做以下事情。 访问 C++ 代码(从 dll,我刚刚开始使用 C++ 的旅程)并运行一个方法,该方法将获取我的输入数组,进行计算并将新数组(旧的未修改)返回到 C# 代码。

或者,我可以输入两个数组(首选),其中一个被修改。 输出数组不必每次都创建,它可以被覆盖。 我不能在 C# 中使用不安全代码,因为这是出于资产目的(兼容性问题)。

样机代码

C#
create arrays aOne,aTwo;
SomeC++Method(aOne,ref aTwo)
or 
aTwo = SomeC++Method(aOne,aTwo)
or 
aTwo = SomeC++Method(aOne) // variant where aTwo is created in C++

SomeC++MethodToDeAllocateMemory() //so there is no memory leak

C++
SomeC++Method(aOne,aTwo)
  for (xyz)
   do someting
  return modified aTwo
SomeC++MethodToDeAllocateMemory()
  release memory

我在这里找到了这个问题的答案: Updating a C# array inside C++ without Marshal.Copy or Unsafe

示例解决方案:

c++ 代码:

extern "C" __declspec(dllexport) bool PopulateArray(float* floats, int length)

    for (int i = 0; i < length; ++i)
    
        floats[i] = i;
    

    return true;

C# 代码

using UnityEngine;
using System.Runtime.InteropServices;

public class Test : MonoBehaviour 

    [DllImport("ArrayFilling", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool PopulateArray([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] float[] floats, int length);

    int length = 4000000; //corresponds to 2000x2000 texture of grayscale values

    void Start()
    
        //create new array
        float[] floats = new float[length];

        //used for speed test
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

        //check internal method speed
        sw.Start();
        //floats array gets update here
        Populate(ref floats);
        sw.Stop();
        Debug.Log("internal: " + sw.ElapsedMilliseconds);
        sw.Reset();

        //check external method speed
        sw.Start();
        //floats array gets updated here
        PopulateArray(floats, length);
        sw.Stop();
        Debug.Log("external: " +sw.ElapsedMilliseconds);

    

    void Populate(ref float[] floats)
    
        for (int i = 0,n = length; i<n;i++)
        
            floats[i] = i;
        
    

关于速度: -外部方法 (DLL) - 5 毫秒 - 内部方法(无效填充) - 23 毫秒

【问题讨论】:

这可能吗?我想您可以通过两种语言/应用程序创建的 Web 服务发送数据。 你可以使用 C++/CLI -> ***.com/questions/5655936/… @FarhanQasim 这看起来像是在尝试提高内存数据的数字处理性能;使用 Web 服务将是实现这一点的最糟糕的方法,除非 Web 服务可以访问更强大的处理引擎(例如:多个 GPU,或者将 Web 服务作为通往服务器集群的网关可以分片数据) 副本中的第一个链接显示了如何将数组从 C# 传递到 C++。第二个链接显示了如何做相反的事情。您必须在收到从 C++ 返回到 C# 的数组后释放它。您必须固定阵列,否则您的插件有时会导致 Unity 崩溃而有时会工作。此外,您将创建和销毁速度较慢的数组。这就是为什么人们决定将数组从 C# 传递到 C++ 以进行修改。我建议您阅读 this 帖子,因为它描述了您在此问题中提到的大多数内容的解决方案。 @richej CLI 并非与每个平台都兼容。 OP 应该用纯 C 或 C++ 编写插件。 【参考方案1】:

.NET 和 C++ 之间的 P/Invoke 支持非常好。通常它相当于使用[DllImport] 声明一些extern 方法来告诉运行时在哪里可以找到外部dll。传递数组时,关键问题是谁在分配它们。如果 C# 代码正在分配它们,那么您通常可以将它们传入 - 例如这里:https://***.com/a/5709048/23354。如果 C++ 代码正在分配它们,那么您的 C# 代码将需要在指针中进行对话,这意味着 unsafe,您不允许这样做(来自问题)。所以如果可能的话:让 C# 代码拥有数组。

如果数组不完全匹配任何可用调用 - 或者您需要对数组应用偏移量 - 那么您可以声明 extern 方法以采用 IntPtr 或一些 int* 等,但是你回到unsafe 登陆这里。您将使用fixed 将引用转换为指针,即fixed(int* ptr = arr) SomeExternalCall(ptr + offset, arr.Length - offset); 。但如果可能的话,使用简单的“传递数组”方法会更简单。

请注意,如果方法不是同步的 - 即如果 C++ 代码将保持指针一段时间 - 那么您将需要通过 GCHandle 手动固定数组。 P/Invoke 代码知道在单个 extern 调用期间固定一个数组(就像在上面的示例中 fixed 会做的那样),但它不能保证在之后不移动它 该调用已完成 - 这将使 C++ 代码持有的指针不可靠。 GCHandle 解决了这个问题,方法是在您需要的任何时间段内将对象固定到位。

不过,坦率地说; C# 在处理数据方面还不错 - 即使使用 100% 安全的代码。如果您的 C++ 实现尚未编写,我建议您首先尝试的是:简单地改进 C# 实现以提高效率。

【讨论】:

嗨,马克,谢谢你的回答。我正在阅读有关编组的信息,但这令人困惑。我已经在 C# 中用我的算法摸索了。改进它的唯一方法(据我所知)是使用不安全的代码(我不能)。该算法相对简单(在数组中查找点,相应地更新其他数组)。问题是图像尺寸很大,因此您可以通过数组的速度成为瓶颈。 Unsafe 会改进它,因为我可以在裸内存上工作,但不能使用它,所以我想到了 C++。看起来程序员找到我的主题就是答案。 关于“简单地改进 C# 实现以提高效率。” - 我已经花费了大量时间优化它,这是下一步可能会提供更好的结果。在用“舒适”的答案束缚自己之前,我想探索所有可能性。

以上是关于从 C# 到 C++ 并返回的数组,没有不安全的代码 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

嵌入式单声道:将数组从 C# DLL 返回/复制到 C++

将安全数组的安全数组从 C++ 中的 VARIANT 编组到 C#

将 C++ 数组返回到 C#

如何将结构数组从 c++ 传递到 c#?

当 C# 调用 c++ 函数时,是不是可以在被调用的 c++ 函数中初始化数组并返回到 C#?

使用 C++/CLI 包装器将二维数组从 C# 传递到非托管 C++