Search code examples
c#arrayspinvokemarshallingintptr

Helper functions for marshalling arrays of structures (with pointers)


This appears to be the most commonly asked C# interop question and yet seems to be difficult to find a working solution for.

I am in need of allocating an array of matrix datastructure in C# passing it to a C DLL which fills up the data and returns it to the caller to deal with.

Based on various pages on the web, I seem to have managed to get data and memory from C# into C++ but not, it appears, back...

Code follows.

thanks in advance for any help Shyamal


I have a C++ structure as follows

typedef struct tagTMatrix
{
   int   Id;
   int   NumColumns;
   int   NumRows;
   double*  aData;
} TMatrix;

which I declare in C# as

[StructLayout(LayoutKind.Sequential)]
unsafe public struct TMatrix
{
public Int32 id;
public Int32 NumCols;
public Int32 NumRows;
public Int32 NumPlanes;
public IntPtr aData;
};



[DllImport("kernel32.dll")]
internal static extern IntPtr LoadLibrary(String dllname);
[DllImport("kernel32.dll")]
internal static extern IntPtr GetProcAddress(IntPtr hModule, String 
procname);

unsafe internal delegate void FillMatrices(IntPtr mats, long num);

[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")] // Saw this 
mentioned somewhere
static extern void CopyMemory(IntPtr dest, IntPtr[] src, int cb);

unsafe private void butTest_Click(object sender, EventArgs e)
{
    IntPtr library = LoadLibrary("TestDLL.dll");
    IntPtr procaddr = GetProcAddress(library, "FillMatrices");
    FillMatrices fm = 
(FillMatrices)Marshal.GetDelegateForFunctionPointer(procaddr, 
typeof(FillMatrices));
    TMatrix[] mats = new TMatrix[2];
    mats[0]=new TMatrix();
    mats[1]=new TMatrix();
    mats[0].id=1;
    mats[0].NumCols=2;mats[0].NumRows=1;mats[0].NumPlanes=0;
    mats[0].aData = Marshal.AllocHGlobal(sizeof(double) * 2);
    double [] array=new double[2];
    array[0]=12.5;array[1]=2.3;
    fixed (double* a = array)
{
IntPtr intPtr = new IntPtr((void*)a);
mats[1].aData = Marshal.AllocHGlobal(sizeof(double) * 2);
//mats[1].aData = 13;
mats[1].aData = intPtr;
mats[1].id = 2;
mats[1].NumCols = 1; mats[1].NumRows = 2; mats[1].NumPlanes = 0;
}
IntPtr[] ptrs = new IntPtr[2];
int total=0;
for (int i = 0; i < ptrs.Length; i++)
{
total = total + sizeof(IntPtr) * (4 + mats[i].NumCols * mats[i].NumRows);
ptrs[i] = 
Marshal.AllocHGlobal(sizeof(IntPtr)*(4+mats[i].NumCols*mats[i].NumRows));
}
Marshal.StructureToPtr(mats[0], ptrs[0], false);
Marshal.StructureToPtr(mats[1], ptrs[1], false);
//_list.test_list =
IntPtr pointer=Marshal.AllocHGlobal(total);
CopyMemory(pointer, ptrs, 2 * IntPtr.Size);
//TMatrix m1=new TMatrix();
//mats[0].aData = 10;// new double[20];
//TMatrix m2 = new TMatrix();
// mats[1].aData = 20;// new double[9];
//Marshal.StructureToPtr(m2, p1, false);
//mats.Add(m2);
//Marshal.StructureToPtr(mats, p1, false);
//IntPtr p2=Marshal.AllocHGlobal(270);
//Marshal.StructureToPtr(mats.ToArray(),p2,false);
fm(pointer,2);
// Now I want to get back this data ???
}


// C++ function
extern "C" void FillMatrices(TMatrix** mats, int matcount)
{
 FILE* fp=fopen("C:\\mats.txt","w+");
 fprintf(fp,"Number of matrices = %d\n",matcount);
 fflush(fp);
 for(int i=0;i<matcount;++i)
 {
  TMatrix* m=mats[i];
  fprintf(fp,"id = %d rows %d cols %d \n",m->Id,m->NumRows,m->NumColumns);
  fflush(fp);

  for(int j=0;j<m->NumRows;++j)
  {
   fprintf(fp,"%d ",j);
   fflush(fp);
   for(int k=0;k<m->NumColumns;++k)
   {
    fprintf(fp,"%f ", m->aData[k*m->NumRows+j]);
    // modify the data - it should be available back in C#
    m->aData[k*m->NumRows+j]=k;
    fflush(fp);
   }
   fprintf(fp,"\n");
   fflush(fp);
  }
  fprintf(fp,"--------------------------\n");

  fflush(fp);
 }
 fclose(fp);
}

Solution

  • Here's a modified version of my initial code that works with an array of matrices:

    typedef struct Matrix
    {
        int rowsCount;
        int colsCount;
        int* data;
    } TMatrix;
    
    extern "C" __declspec(dllexport) void InitializeMatrix(TMatrix** matrices, int count) 
    {
        srand(time(NULL));
        printf("<unmanaged>\n");
        for(int i = 0; i < count; i++)
        {
            TMatrix* m = matrices[i];
            printf("rows %d cols %d\n", m->rowsCount, m->colsCount);
    
            for(int j = 0; j < m->rowsCount; j++)
            {
                for(int k = 0; k < m->colsCount; k++)
                {
                    printf("%d ", m->data[k * m->rowsCount + j]);
                    // modify the data - it should be available back in C#
                    m->data[k * m->rowsCount + j] = rand() % 10;
                }
                printf("\n");
            }
        }
        printf("</unmanaged>\n\n");
    }
    

    And the managed part:

    [StructLayout(LayoutKind.Sequential)]
    struct Matrix
    {
        public int RowsCount;
        public int ColsCount;
        public IntPtr Data;
    }
    
    class Program
    {
        [DllImport("TestLib.dll")]
        private static extern void InitializeMatrix(IntPtr ptr, int count);
    
        static void Main(string[] args)
        {
            const int count = 3;
    
            // Allocate memory
            IntPtr ptr = Marshal.AllocHGlobal(count * Marshal.SizeOf(typeof(IntPtr)));
            IntPtr[] matrices = new IntPtr[count];
            for (int i = 0; i < count; i++)
            {
                Matrix matrix = new Matrix();
                // Give some size to the matrix
                matrix.RowsCount = 4;
                matrix.ColsCount = 3;
                int size = matrix.RowsCount * matrix.ColsCount;
                int[] data = new int[size];
                matrix.Data = Marshal.AllocHGlobal(size * Marshal.SizeOf(typeof(int)));
                Marshal.Copy(data, 0, matrix.Data, size);
    
                matrices[i] = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Matrix)));
                Marshal.StructureToPtr(matrix, matrices[i], true);
            }
            Marshal.Copy(matrices, 0, ptr, count);
    
    
            // Call unmanaged routine
            InitializeMatrix(ptr, count);
    
            Console.WriteLine("<managed>");
            // Read data back
            Marshal.Copy(ptr, matrices, 0, count);
            for (int i = 0; i < count; i++)
            {
                Matrix m = (Matrix)Marshal.PtrToStructure(matrices[i], typeof(Matrix));
                int size = m.RowsCount * m.ColsCount;
                int[] data = new int[size];
                Marshal.Copy(m.Data, data, 0, size);
    
                // Pretty-print the matrix
                Console.WriteLine("rows: {0} cols: {1}", m.RowsCount, m.ColsCount);
                for (int j = 0; j < m.RowsCount; j++)
                {
                    for (int k = 0; k < m.ColsCount; k++)
                    {
                        Console.Write("{0} ", data[k * m.RowsCount + j]);
                    }
                    Console.WriteLine();
                }
            }
            Console.WriteLine("</managed>");
    
    
            // Clean the whole mess (try...finally block omitted for clarity)
            for (int i = 0; i < count; i++)
            {
                Matrix m = (Matrix)Marshal.PtrToStructure(matrices[i], typeof(Matrix));
                Marshal.FreeHGlobal(m.Data);
                Marshal.FreeHGlobal(matrices[i]);
            }
            Marshal.FreeHGlobal(ptr);
        }
    }
    

    HTH