How can I properly send a double array in C# to a C dll?

139 Views Asked by At

I have a C DLL that im calling from a C# application. I am able to successfully receive the struct from the DLL but im having trouble sending the same struct with data into the DLL. Here is what I have:

C# application struct:

public class MatrixClass
{

    private double[,] cells;

    [StructLayout(LayoutKind.Sequential)]
    public struct Matrix
    {
        public IntPtr cells;
        public int cellRows;
        public int cellColumns;   
    }
}

C DLL struct:

    typedef struct
    {
        double **cells;
        int cellRows;
        int cellColumns;
    } Matrix;

Here is an example function from My C DLL that I need to recieve the struct from:

void GetMatrixCell(Matrix *MatrixIn, int nRow, int nColumn, double *value)
{
    *value = 0.0;
    int nColIndex = nColumn - 1;
    int nRowIndex = nRow - 1;
    *value = MatrixIn->cells[nRowIndex][nColIndex];
}

How im calling it:

[DllImport(matrixDLL, CallingConvention = CallingConvention.Cdecl)]
static extern int GetMatrixCell(Matrix mIn, int nRow, int 
nColumn, ref double value);

public double GetCell(int Row, int Column)
{
    double result = 0.0;
    int stride = Rows * sizeof(double);
    IntPtr p = Marshal.AllocHGlobal(Rows * stride);
    Matrix mIn = MatrixtoDLL(this, Rows, Columns, p, stride); //this = MatrixClass
    GetMatrixCell(mIn, Row, Column, ref result);
    Marshal.FreeHGlobal(p);
    return result;
}

MatrixtoDll Funtion from above:

    public static Matrix MatrixtoDLL(MatrixClass input, int rows, int columns, IntPtr p, int stride)
    {
        Matrix toReturn = new Matrix ();
        toReturn.cellColumns = columns;
        toReturn.cellRows = rows;

        for (int i = 0; i < columns; i++) //rows
        {
            double[] temp = new double[rows];

            for (int x = 0; x < rows; x++)
                temp[x] = input.cells[i, x]; //[columns, rows]

            Marshal.Copy(temp, 0, p + stride * i, rows);
        }

        toReturn.cells = p;

        return toReturn;
    }

Im thinking my MatrixtoDLL is where I need to rethink? or I need to be sending the data another way. Like I mentioned above I can successfully receive the data (cells) as an IntPtr and convert it to a 2d array but the program does not like they way im trying to send the struct. Thanks in advance!

Edit: This is the unhandled exception I get: An unhandled exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll Additional information: Exception has been thrown by the target of an invocation.

1

There are 1 best solutions below

0
Charlieface On

Your P/Invoke function is not defined correctly. It shoud be

[DllImport(matrixDLL, CallingConvention = CallingConvention.Cdecl)]
static extern void GetMatrixCell(in Matrix mIn, int nRow, int nColumn, ref double value);

Furthermore, Matrix.cells should be a pointer to an array of pointers to one-dimensional arrays, in other words a jagged array.

We can use GCHandle for this, this also avoids unnecessary copying

    private double[][] cells;

    [StructLayout(LayoutKind.Sequential)]
    public struct Matrix
    {
        public IntPtr[] cells;
        public int cellRows;
        public int cellColumns;   
    }

public double GetCell(int Row, int Column)
{
    double result = 0.0;
    Matrix mIn = new Matrix
    {
        cellColumns = Columns,
        cellRows = Rows,
    };

    var handles = new GCHandle[this.cells.Length];

    try
    {
        for (var i = 0; i < this.cells.Length; i++)
            handles[i] = GCHandle.Alloc(this.cells[i], GCHandleType.Pinned);

        mIn.cells = handles.Select(h => h.AddrOfPinnedObject()).ToArray();
        GetMatrixCell(mIn, Row, Column, ref result);
    }
    finally
    {
        foreach (var h in handles)
            if(h.IsAlllocated)
                h.Free();
    }
    return result;
}

I must say, I'm unclear the point of Matrix.cellColumns and cellRows. The C function doesn't seem to use it.