【问题标题】:Marshalling a float[][] as float**将 float[][] 编组为 float**
【发布时间】:2013-02-11 00:21:08
【问题描述】:

假设我有一个带有原型的 C++ 函数

int someFunction(const float ** raws)

如何使用 C# 中的 float[][] 参数调用此函数?可能没有使用不安全的代码。

【问题讨论】:

  • float[][]float ** 根本不同,所以可能不是。
  • 这应该不是不可能的。我确定有办法。这就是我称之为“编组”的原因。
  • 我的意思是一种黑客方法是将 float[][] 放入类/结构中,然后将其传递。
  • 专门通过引用传递它,这样就不会在每次将它传递给函数时复制堆栈上的整个 float[][]。
  • 某种暗示,只要在类/结构中的数据上添加正确的包装

标签: c# c++ pinvoke marshalling


【解决方案1】:

据我所知,您必须自己完成大部分工作。 Interop 将帮助您编组顶级数组,但您的工作将是固定所有嵌套数组,然后在完成后取消固定它们。此代码显示了一种方法:

using System;
using System.Runtime.InteropServices;

namespace ManagedClient
{
    class Program
    {
        [DllImport("UnmanagedDll.dll", CallingConvention = CallingConvention.StdCall)]
        private static extern int UseFloats([MarshalAs(UnmanagedType.LPArray)] IntPtr[] raws);

        static void Main(string[] args)
        {
            float[][] data =
            {
                new[] { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f },
                new[] { 1.0f, 1.1f, 1.2f, 1.3f },
                new[] { 2.0f },
                new[] { 3.0f, 3.1f }
            };

            var handles = new GCHandle[data.Length];
            var pointers = new IntPtr[data.Length];

            try
            {
                for (int i = 0; i < data.Length; ++i)
                {
                    var h = GCHandle.Alloc(data[i], GCHandleType.Pinned);
                    handles[i] = h;
                    pointers[i] = h.AddrOfPinnedObject();
                }

                UseFloats(pointers);
            }
            finally
            {
                for (int i = 0; i < handles.Length; ++i)
                {
                    if (handles[i].IsAllocated)
                    {
                        handles[i].Free();
                    }
                }
            }
        }
    }
}

这会构建一个指针数组,每个指针都指向输入数组中的float 数据。 (这是您的 C 函数期望数据到达的格式。)您的 C 代码如何知道每个子数组的长度取决于您。在我的测试代码中,我只是硬编码它以匹配传入的 C# 代码:

__declspec(dllexport) int __stdcall UseFloats(const float ** raws) 
{
    printf("%f %f %f %f %f\n", raws[0][0], raws[0][1], raws[0][2], raws[0][3], raws[0][4]);
    printf("%f %f %f %f\n", raws[1][0], raws[1][1], raws[1][2], raws[1][3], raws[0][4]);
    printf("%f\n", raws[2][0]);
    printf("%f %f\n", raws[3][0], raws[3][1]);
    return 0;
}

实际上,您可能想要做一些事情来告诉非托管代码每个子数组的长度 - 您选择的非托管函数签名不会让被调用代码以任何方式知道每个子数组的长度. (大概您使用 float** 的原因是因为您想要锯齿状数组。如果不是,并且每个子数组的长度完全相同,那么在这里使用矩形数组而不是指针,它也会使编组更容易。)

【讨论】:

  • 好答案。您可能的意思是“大概是您使用 float[][] 的原因”而不是“float**”,应该加下划线或加粗,因为如果是这种情况,二维数组 (float[,]) 可以自动编组。
  • 谢谢!但我的意思是我写的:我责怪“float**”,因为这是他试图调用的非托管函数的签名。这就是问题根源所在的签名。 (如果非托管签名仍然是浮动的,则在 C# 中切换到 float[,] 对您没有多大帮助** - 该非托管签名需要一个指针数组,这是问题的核心。从 float[,] 开始会意味着您只需要固定一个对象,但您仍然需要构建一个 IntPtr[]。)我认为最好的解决方案是尽可能更改非托管签名。
  • @Ian 你为什么使用[MarshalAs(UnmanagedType.LPArray)]?另外,如果您不使用该属性,默认编组是什么?
  • 我使用它是因为我知道非托管端需要以 C 样式传递数组,即作为指向第一个元素的指针。我不记得默认值是什么,当我使用 P/Invoke 时,我更喜欢明确以确保我明白我的意思,而不是依赖默认值。
【解决方案2】:

Ian 已经回答了你的问题,我只想建议在 C++ 端使用 SAFEARRAY。

SAFEARRAY 是 C/C++ 中数组定义模糊问题的 COM 答案,它们是包含基础元素的数量和大小的结构,更适合 .Net 数组。使用它们将允许从 C# 自动编组数组。

SAFEARRAY 在 C++ 中使用起来很痛苦,但 ATL 有一些很好的包装器可以让事情变得更容易。如果可能的话,更改 C++ 函数以使用它,或者考虑编写一个 C++ 包装器以与使用它们的 .Net 互操作,并使用 C++ 编写编组代码。

【讨论】:

  • 不幸的是,C++ 代码是由其他人编写的,我无法控制它。不过感谢您的建议!
猜你喜欢
  • 1970-01-01
  • 2014-06-28
  • 2011-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-25
相关资源
最近更新 更多