【问题标题】:Helper functions for marshalling arrays of structures (with pointers)用于编组结构数组的辅助函数(带指针)
【发布时间】:2010-09-27 07:49:36
【问题描述】:

这似乎是最常见的 C# 互操作问题,但似乎很难找到有效的解决方案。

我需要在 C# 中分配一个矩阵数据结构数组,并将其传递给 C DLL,该 C DLL 填充数据并将其返回给调用者进行处理。

根据网络上的各种页面,我似乎已经设法将数据和内存从 C# 获取到 C++,但似乎没有返回...

代码如下。

提前感谢您的帮助 夏马尔


我有一个 C++ 结构如下

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

我在 C# 中声明为

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

【问题讨论】:

    标签: c# arrays pinvoke marshalling intptr


    【解决方案1】:

    这是我在一个旧项目中的做法,在该项目中,我必须将整数矩阵传递给 C 例程,该例程将用值填充它,然后我必须在托管代码中取回值。

    我在非托管代码中有一个例程,它将用一些值填充整数矩阵:

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

    然后在托管代码中我做了一些事情:

    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 是一个 PITA。我强烈建议您尽可能避免它,并且在不可能的情况下尝试以最小化编组数据的方式组织您的函数。

    【讨论】:

    • 谢谢。就像您的情况一样,我似乎已经设法使事情与一个矩阵一起工作。我寻求的是一种用于此类矩阵数组的方法,我想用它来避免非常长的 C 方法签名。
    • 其实我认为掌握这个话题很重要。无论语言之间发生什么,忽视指针、调用约定等基础知识都是危险的。我们拥有的示例越多,积累的方法越多越好。
    【解决方案2】:

    以下是我用来在 C# 客户端应用程序上编组 C++ 网络结构的几种方法:

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

    【讨论】:

      【解决方案3】:

      这是我的初始代码的修改版本,适用于矩阵数组:

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

      以及托管部分:

      [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

      【讨论】:

      • 做一个堆栈验证只是为了确定。如果 COM 编程教会了我什么,那就是“有效”并不总是意味着“正确”。在完全不相关的情况下,堆栈错误可能会在 30 分钟后出现。因此,如果您有一个在 Delphi 中运行的调试器,请始终尝试确保堆栈平衡。
      猜你喜欢
      • 2019-07-10
      • 2011-02-01
      • 1970-01-01
      • 2023-03-07
      • 2018-04-21
      • 1970-01-01
      • 2013-04-03
      • 1970-01-01
      • 2013-02-16
      相关资源
      最近更新 更多