views:

1357

answers:

4

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);
}
A: 

Here's how I did it in an old project in which I had to pass a matrix of integers to a C routine that would fill it with values and then I had to get back the values in managed code.

I had a routine in unmanaged code that would fill a matrix of integers with some values:

#include "stdafx.h"
#include "TestLib.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

extern "C" __declspec(dllexport) void InitializeMatrix(int** matrix, int rows, int cols) 
{
    srand(time(NULL));
    printf("rows: %d\ncols: %d\n", rows, cols);
    for (int i = 0; i < rows; i++) 
    {
        for (int j = 0; j < cols; j++)
        {
            matrix[i][j] = rand() % 10;
        }
    }
}

And then in managed code I did something among the lines:

class Program
{
    [DllImport(@"TestLib.dll")]
    private static extern void InitializeMatrix(IntPtr ptr, int rows, int cols);

    static void Main(string[] args)
    {
        const int rowsCount = 3;
        const int colsCount = 4;

        // Allocate memory for the matrix: (rowsCount * sizeof(IntPtr)) * (colsCount * sizeof(int))
        IntPtr ptr = AllocateMatrix(rowsCount, colsCount);
        try
        {
            // Call unmanaged routine to fill the allocated memory with data
            InitializeMatrix(ptr, rowsCount, colsCount);

            // Marshal back data
            int[][] matrix = GetMatrixFromPointer(ptr, rowsCount, colsCount);

            // Pretty-print the matrix
            for (int i = 0; i < rowsCount; i++)
            {
                for (int j = 0; j < colsCount; j++)
                {
                    Console.Write("{0} ", matrix[i][j]);
                }
                Console.WriteLine();
            }
        }
        finally
        {
            // Release allocated memory
            FreeMatrix(ptr, rowsCount, colsCount);
        }
    }

    private static IntPtr AllocateMatrix(int rowsCount, int colsCount)
    {
        IntPtr ptr = Marshal.AllocHGlobal(rowsCount * Marshal.SizeOf(typeof(IntPtr)));
        IntPtr[] rows = new IntPtr[rowsCount];
        for (int i = 0; i < rowsCount; i++)
        {
            int[] cols = new int[colsCount];
            rows[i] = Marshal.AllocHGlobal(colsCount * Marshal.SizeOf(typeof(int)));
            Marshal.Copy(cols, 0, rows[i], colsCount);
        }
        Marshal.Copy(rows, 0, ptr, rows.Length);
        return ptr;
    }

    private static int[][] GetMatrixFromPointer(IntPtr ptr, int rowsCount, int colsCount)
    {
        int[][] result = new int[rowsCount][];
        IntPtr[] rows = new IntPtr[rowsCount];
        Marshal.Copy(ptr, rows, 0, rowsCount);
        for (int i = 0; i < rowsCount; i++)
        {
            int[] cols = new int[colsCount];
            Marshal.Copy(rows[i], cols, 0, colsCount);
            result[i] = cols;
        }
        return result;
    }

    private static void FreeMatrix(IntPtr ptr, int rowsCount, int colsCount)
    {
        IntPtr[] rows = new IntPtr[rowsCount];
        Marshal.Copy(ptr, rows, 0, rowsCount);
        for (int i = 0; i < rowsCount; i++)
        {
            Marshal.FreeHGlobal(rows[i]);
        }
        Marshal.FreeHGlobal(ptr);
    }
}

P/Invoke is a PITA. I would strongly recommend you to avoid it as much as possible and in cases where it is not possible try to organize your functions in a way to minimize marshaling data.

Darin Dimitrov
Thanks. I seem to have managed to get things working with one matrix as in your case. What I seek is a method for an array of such matrices which I would like to use to avoid really long C method signatures.
A: 

Here's a couple of methods I used to marshal C++ network structs on a C# client application:

    public static T Get<T>(byte[] msg, int offset)
    {

        T[] t = new T[] { default(T) };
        int len = Marshal.SizeOf(typeof(T));
        GCHandle th = GCHandle.Alloc(t, GCHandleType.Pinned);
        GCHandle mh = GCHandle.Alloc(msg, GCHandleType.Pinned);
        try
        {
            unsafe
            {
                byte* pb = (byte*)mh.AddrOfPinnedObject();
                byte* srcptr = pb + offset;
                byte* dest = ((byte*)th.AddrOfPinnedObject());
                for (int i = 0; i < len; i++)
                {
                    dest[i] = srcptr[i];
                }
            }
        }
        finally
        {
            mh.Free();
            th.Free();
        }


        return t[0];
    }


    public static string GetString(byte[] msg, int offset, int length)
    {
        StringBuilder retVal = new StringBuilder(length);
        unsafe
        {
            fixed (byte* pb = msg)
            {
                byte* pc = (byte*)(pb + offset);
                for (int x = 0; x < length; x++)
                {
                    if (pc[x] == 0) break;
                    retVal.Append((char)pc[x]);
                }
            }
        }
        return retVal.ToString(0, retVal.Length);
    }
dviljoen
A: 

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

Darin Dimitrov
A: 

Thanks a ton for that, will run it through my compiler soon...