在 c# 和 c++ 之间将 double 类型的二维多维数组作为输入和输出的 pinvoke 编组

Posted

技术标签:

【中文标题】在 c# 和 c++ 之间将 double 类型的二维多维数组作为输入和输出的 pinvoke 编组【英文标题】:pinvoke marshalling of 2d multidimensional array of type double as input and output between c# and c++ 【发布时间】:2018-11-21 19:35:26 【问题描述】:

我有以下 c# 和 c++ pinvoke 编组的 2d 多维数组类型的双重问题,我正在尝试解决。

我已经查看了以下命中,以获得我目前拥有的 P/Invoke with arrays of double - marshalling data between C# and C++。

我查看了Marshalling C# Jagged Array to C++,它具有非常好的场景匹配,但不清楚如何从答案到实施的所有方面。

我的问题,我想如果我目前走在正确的道路上,我是如何展开 c++ *outputArray = new double[*outputArrayRows, *outputArrayCols]; 的,该 c++ *outputArray = new double[*outputArrayRows, *outputArrayCols]; 已从启用 DllImport 的调用成功传递回 c# IntPtr outputArrayPtr 变量到 var outputArray = new double[outputArrayRows, outputArrayCols]; 变量中需要才能继续。

问题 = 关于 for 循环是否是正确的下一步以及我在其中使用什么提取语法有任何见解?

c++ 方面

extern "C" __declspec(dllexport) void SomeFunction(double** inputArray, int inputArrayRows, int inputArrayCols,
    double** outputArray, int* outputArrayRows, int* outputArrayCols)

    // just initialize the output results for testing purposes no value assignment as of yet
    *outputArrayRows = 10;
    *outputArrayCols = 2;
    *outputArray = new double[*outputArrayRows, *outputArrayCols];
    return;


extern "C" __declspec(dllexport)DllExport void FreeArray(double** allocatedArrayPtr)

    delete[] allocatedArrayPtr;

c# 方面

[DllImport(dllName /*, CallingConvention = CallingConvention.Cdecl */)]
static extern void SomeFunction(double[,] inputArray, int inputArrayRows, int inputArrayCols, 
    out IntPtr outputArray, out int outputArrayRows, out int outputArrayCols);
[DllImport(dllName /*, CallingConvention = CallingConvention.Cdecl */)]
static extern void FreeArray(IntPtr allocatedArrayPtr);

[TestMethod]
public void DllImport_SomeFunction_ShouldNotThrowException()

    var inputArray = new double[,]   1, 2, 3 ,  4, 5, 6 ,  7, 8, 9  ;

    IntPtr outputArrayPtr; int outputArrayRows, outputArrayCols;
    DllImportUnitTests.SomeFunction(inputArray, inputArray.GetLength(0), inputArray.GetLength(1), 
        out outputArrayPtr, out outputArrayRows, out outputArrayCols);
    var outputArray = new double[outputArrayRows, outputArrayCols];
    IntPtr[] outputArrayPtrArray = new IntPtr[outputArrayRows];
    //Marshal.Copy(outputArrayPtr, outputArray, 0, outputArrayRows); // overload for double[] but not for double[,]
    Marshal.Copy(outputArrayPtr, outputArrayPtrArray, 0, outputArrayRows);
    FreeArray(outputArrayPtr);
    for (var i = 0; i < outputArrayPtrArray.Length; i++)
    
        Marshal.Copy(outputArrayPtrArray[i], outputArray[i ???], 0, outputArrayCols);
    

    Assert.IsNotNull(outputArray);

更新包含答案 [/什么对我有用]

基于 cmets,我更新了标题以表示此问题与尝试传递和接收 2d [ / 多维 ] 数组而不是锯齿状数组有关。也就是说,在我的测试中变得很明显的是 vs17 c++ windows 桌面 dll 项目环境只做锯齿状数组 [例如c++ DllExport double** SomeFunction(double** inputArray, . . .double** returnArray = new double*[numberOfRows] 和 c# double[][] dogLegValues = new double[numberOfRows][/* numberOfCols not specified */]; ]。下面我添加了我能够使用的 c# pinvoke DllImport 和 c++ 函数签名,以及一些有趣的编组代码,用于准备二维数组以作为交错数组传递并处理返回的交错数组,最终将其转换为调用者期望的二维数组,如果这对其他人有帮助。

c# DllImport 语句和 cmets 捕获结果

[DllImport(dllName /*, CallingConvention = CallingConvention.Cdecl */)]
//static extern /* double[] */ IntPtr SomeFunction(double[] inputArray, int inputArrayRows, out int outputArrayRows); // pinvoke can marshal double[] 1d array input but not output
static extern /* double[,] */ IntPtr SomeFunction(/* double[,] */ IntPtr[] inputArray, int inputArrayRows, out int outputArrayRows); // pinvoke cannot marshal double[,] 2d array input or output

c++函数签名

#define DllExport extern "C" __declspec(dllexport)
//DllExport double* SomeFunction(double* inputArray, int inputArrayRows, int* outputArrayRows) // using flattened 2d array input and output
DllExport double** SomeFunction(double** inputArray, int inputArrayRows, int* outputArraysRows) // using 2d converted to jagged array [ of arrays ] input and output

将二维数组扁平化为一维数组的c#编组代码

int outputArrayRows; const int outputArrayCols = 2;
double[] inputArrayFlattened = new double[inputArray.Length];
//var index = 0; foreach (var value in inputArray)  inputArrayFlattened[index] = value; index++;  // more concise flattening but adds a stack frame variable 
for (var i = 0; i < inputArray.GetLength(0); i++)  for (var j = 0; j < inputArray.GetLength(1); j++) inputArrayFlattened[i * inputArray.GetLength(1) + j] = (double)inputArray.GetValue(i, j); 
IntPtr outputArrayPtr = MyUnitTests.SomeFunction(inputArrayFlattened, inputArray.Length, out dogLegValuesRows);
double[] outputArray = new double[outputArrayCols]; Marshal.Copy(outputArrayPtr, outputArray, 0, outputArrayCols);

二维数组的c#编组代码

IntPtr[] inputArrayPtr = new IntPtr[inputArray.GetLength(0)];
var inputArrayJagged = inputArray.ToJaggedArray();
for (var i = 0; i < inputArrayJagged.Length; i++)

    IntPtr inputArrayJaggedRowPtr = Marshal.AllocCoTaskMem(sizeof(double) * inputArrayJagged[i].Length);
    Marshal.Copy(inputArrayJagged[i], 0, inputArrayJaggedRowPtr, inputArrayJagged[i].Length);
    inputArrayPtr[i] = inputArrayJaggedRowPtr;

IntPtr outputArrayJaggedPtr = MyUnitTests.SomeFunction(inputArrayPtr, inputArray.GetLength(0), out outputArrayRows);
IntPtr[] outputArrayJaggedPtrArray = new IntPtr[outputArrayRows];
Marshal.Copy(outputArrayJaggedPtr, outputArrayJaggedPtrArray, 0, outputArrayRows);
//FreeArray(outputArrayJaggedPtr); // doesn't appear we need this given passing result back as return value and no issue when returning 1 row but crashes when returning 2 rows

double[][] outputArray = new double[outputArrayRows][/* outputArrayCols not specified */];
for (var i = 0; i < outputArrayJaggedPtrArray.Length; i++)

    outputArray[i] = new double[outputArrayCols]; // can't do this with a double[,] 2d array or can you ???
    double[] outputArrayJaggedRow = new double[outputArrayCols]; 
    Marshal.Copy(outputArrayJaggedPtrArray[i], outputArrayJaggedRow, 0, outputArrayCols);
    outputArray[i] = outputArrayJaggedRow;


var results = outputArray.ToTwoDimensionalArray();

c++交错数组初始化及赋值示例

// hard coded test return values used to get pinvoke marshalling worked out using flattened 2d array input and output
double* returnArray = new double[2]; // or new double[outputDataCols] 
returnArray[0] = 1234.56; returnArray[1] = 98.76; dogLegValuesRows = 1;

// hard coded test return values used to get pinvoke marshalling worked out using 2d converted to jagged array [ of arrays ] input and output
double** returnArray = new double*[2]; // or new double*[*outputDataRows]
returnArray[0] = new double[2]; // or new double[*outputDataCols]
returnArray[0][0] = 1234.56; returnArray[0][1] = 98.76; //*outputDataRows = 1;
returnArray[1] = new double[2]; // or new double[*outputDataCols]
returnArray[1][0] = 7890.12; returnArray[1][1] = 34.56; *outputDataRows = 2;

【问题讨论】:

double[,] 是多维数组,而不是锯齿数组(double[][] 是)。 C# 多维数组就像 C/C++ 多维数组(二进制布局相同)。那么,你能澄清一下你到底在追求什么吗? @Simon 感谢您指出这一点。根据您的评论和 Martin 的评论,我意识到我正在尝试传递二维数组形式的多维数组并接收一个作为返回值。我想我会更新我的原始帖子以更正标题并添加“更新”详细信息,评论使这项工作看起来很有趣的部分。 【参考方案1】:

这段代码:

*values = new double[*valuesOuterLen, *valuesInnerLen];

不做你认为它做的事。 (我缩短了变量名,因为它们只会使事情复杂化。)

C++ 继承自 C 的缺乏多维数组。它所拥有的是数组数组,或指向数组的指针数组。在这两种情况下,您都将它们索引为array[firstIndex][secondIndex]。你不能new这样的指针数组,你必须写这样的东西:

double*** values;  // Triple pointer!  Ow!!  *values is a pointer to
                   // (an array of) pointers to (arrays of) doubles.
*values = new double*[*valuesOuterLen];
for (size_t i=0; i<valuesOuterLen; i++) 
    (*values)[i] = new double[*valuesInnerLen];

您实际调用的是 C++ 逗号运算符,它计算并丢弃第一个操作数,然后计算第二个操作数。启动编译器警告;一个好的编译器会警告第一个操作数没有副作用。

【讨论】:

@myusrn 将数组展平为一维听起来是个好主意。 我现在有一些工作。有帮助的是您对无法像我在示例中所做的那样新建一个指针数组的评论以及 c++ 实际上没有该声明的内容。这让我意识到,通过测试证实,pinvoke 只能通过和接受作为返回锯齿状数组与二维数组。这也导致了更简化的 DllImport 语句和一些有趣的编组代码,以准备传递转换为交错数组的二维数组并处理转换为二维数组的返回交错数组。 我想将您的回复标记为答案,因为它使我能够让事情正常进行,但正如您在我的原始问题的“更新”部分中看到的那样,其中的代码与结束的内容相关联工作起来似乎“双**值”有效,而不是“双***值”对我不起作用。不知道为什么会这样。

以上是关于在 c# 和 c++ 之间将 double 类型的二维多维数组作为输入和输出的 pinvoke 编组的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C# 和 C++ 之间进行互操作

在 C# 中使用非托管 C++ 代码对所有双精度类型返回 0

C++ 到 C# 移植问题:我将如何处理具有 double* 作为参数的方法?

C++转换构造函数:将其它类型转换为当前类的类型

在C#中如何将int类型强制转换为double类型

c#中如何把string类型转换为double类型?