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;