pinvoke marshalling of 2d multidimensional array of type double as input and output between c# and c++

放肆的年华 提交于 2019-12-08 03:56:50

问题


I have the following c# and c++ pinvoke marshalling of 2d multidimensional array of type double matter I'm trying to solve.

I've reviewed the following hit to get what I have currently P/Invoke with arrays of double - marshalling data between C# and C++ .

I've reviewed Marshalling C# Jagged Array to C++ which has a very good scenario match but it's not clear how to go from answer to all aspects of implementation.

My issue, I think if i'm on right path so far, is how I unwind the c++ *outputArray = new double[*outputArrayRows, *outputArrayCols]; that is successfully passed back from DllImport enabled call to the c# IntPtr outputArrayPtr variable into the var outputArray = new double[outputArrayRows, outputArrayCols]; variable I need in order to proceed.

Question = Any insights on if the for loop is the right next step and what extraction syntax I use inside of it?

c++ side of things

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# side of things

[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);
}


update containing answer [ / what worked for me ]

Based on comments I updated title to denote this issue has to do with trying to pass and receive a 2d [ / multi-dimensional ] array not a jagged array. That said what became apparent in my tests is that vs17 c++ windows desktop dll project environment only does jagged arrays [ e.g. c++ DllExport double** SomeFunction(double** inputArray, . . . and double** returnArray = new double*[numberOfRows] and c# double[][] dogLegValues = new double[numberOfRows][/* numberOfCols not specified */]; ]. Below i'm adding the c# pinvoke DllImport and c++ function signatures that I was able to get things working with and some of the interesting marshalling code for prepping the 2d array for passing as jagged array and for processing the returned jagged array eventually converting it to 2d array that caller was expecting if that helps others.

c# DllImport statement and comments capturing findings

[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++ function signature

#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# marshaling code for 2d array flattened into 1d array

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# marshaling code for 2d array

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++ jagged array initialization and assignment examples

// 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;

回答1:


This code:

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

does not do what you think it does. (I shortened the variable names because they just complicate things.)

C++ inherits from C a lack of multi-dimensional arrays. What it does have is arrays of arrays, or arrays of pointers to arrays. In both cases you index these as array[firstIndex][secondIndex]. You can't new an array of pointers like that, you have to write something like:

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];
}

What you have actually invoked is the C++ comma operator, which evaluates and discards the first operand, and then evaluates the second operand. Crank your compiler warnings up; a good compiler will warn that the first operand has no side-effects.



来源:https://stackoverflow.com/questions/53419367/pinvoke-marshalling-of-2d-multidimensional-array-of-type-double-as-input-and-out

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!