【问题标题】:adapt wrapper class for two dimensional case为二维情况调整包装类
【发布时间】:2026-01-11 14:15:01
【问题描述】:

这个问题是这个question的扩展。

我想为二维情况调整包装器。这是我的第一次尝试:

public class EmxArrayRealTWrapper : IDisposable
{
private readonly emxArray_real_T _value;
private GCHandle _dataHandle;
private GCHandle _sizeHandle;

public emxArray_real_T Value
{
    get { return _value; }
}

public EmxArrayRealTWrapper(double[,] data)
{
    _dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
    _value.data = _dataHandle.AddrOfPinnedObject();
    _sizeHandle = GCHandle.Alloc(new int[] { data.GetLength(0), data.GetLength(1) }, GCHandleType.Pinned);
    _value.size = _sizeHandle.AddrOfPinnedObject();
    _value.allocatedSize = data.GetLength(0) * data.GetLength(1) * sizeof(double);
    _value.numDimensions = 2;
    _value.canFreeData = false;
}

public void Dispose()
{
    _dataHandle.Free();
    _sizeHandle.Free();
    GC.SuppressFinalize(this);
}

~EmxArrayRealTWrapper()
{
    Dispose();
}
}

[StructLayout(LayoutKind.Sequential)]
public struct emxArray_real_T
{
public IntPtr data;
public IntPtr size;
public int allocatedSize;
public int numDimensions;
[MarshalAs(UnmanagedType.U1)]
public bool canFreeData;
}

PS:

原始的matlab代码如下:

    function [x] = test(a)
    %#codegen

    x = 0;
    if(~isempty(coder.target))
      assert(isa(a,'double'));
      assert(all(size(a) == [1 Inf]));
   end

    x = sum(a);

并且可以这样调用:

a = [ 1 2; 3 4]

r = test(a)

制作:

r =

     4     6

很遗憾,生成的 C 无法达到 Matlab 所能达到的效果(即返回一个数组):

__declspec(dllexport) real_T test(const emxArray_real_T *a);

real_T test(const emxArray_real_T *a)
{
  real_T x;
  int32_T k;
  if (a->size[1] == 0) {
    x = 0.0;
  } else {
    x = a->data[0];
    for (k = 2; k <= a->size[1]; k++) {
      x += a->data[k - 1];
    }
  }

  return x;
}

【问题讨论】:

  • allocatedSize 是元素的总数。所以*sizeof(double) 是错误的。只需将其删除。将allocatedSize 设置为data.GetLength(0) * data.GetLength(1)。主要问题是您不能将 double[,] blit 到 col 主要 MATLAB 数组上。我是正确的,这是正确的。一旦我知道了这一点,我就可以调整你的代码。
  • 谢谢。我已经按照您的建议修改了代码。对于 new double[,] { { 1, 2, 4 }, { 1, 3, 4 } } 我得到 7 而不是 7 和 8。

标签: c# interop pinvoke matlab-coder


【解决方案1】:

我假设 MATLAB 数组结构使用 col-major 排序。在这种情况下,结构构造函数需要如下所示:

public EmxArrayRealTWrapper(double[,] data)
{
    int nRow = data.GetLength(0);
    int nCol = data.GetLength(1);

    double[] flattenedData = new double[nCol * nRow];
    int index = 0;
    for (int col=0; col<nCol; col++)
    {
        for (int row=0; row<nRow; row++)
        {
            flattenedData[index] = data[row, col];
            index++;
        }
    }                    

    _dataHandle = GCHandle.Alloc(flattenedData, GCHandleType.Pinned);
    _value.data = _dataHandle.AddrOfPinnedObject();
    _sizeHandle = GCHandle.Alloc(new int[] { nCol, nRow }, GCHandleType.Pinned);
    _value.size = _sizeHandle.AddrOfPinnedObject();
    _value.allocatedSize = nCol * nRow;
    _value.numDimensions = 2;
    _value.canFreeData = false;
}

如果本机代码需要行优先,那么它会更简单。 C# 多维数组存储为连续的行优先数组。因此,您可以使用与我在您最近的问题中提供的一维码非常相似的代码。

public EmxArrayRealTWrapper(double[,] data)
{
    int nRow = data.GetLength(0);
    int nCol = data.GetLength(1);
    _dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
    _value.data = _dataHandle.AddrOfPinnedObject();
    _sizeHandle = GCHandle.Alloc(new int[] { nRow, nCol }, GCHandleType.Pinned);
    _value.size = _sizeHandle.AddrOfPinnedObject();
    _value.allocatedSize = nCol * nRow;
    _value.numDimensions = 2;
    _value.canFreeData = false;
}

请注意,这两种变体在将数据传递给本机代码的方式上有所不同。第一个版本传递原始数据的副本。第二个传递对原始数据的引用。我不确定你希望你的代码如何表现。调整第二个版本以传递副本很容易。对于第一个版本,如果您希望本机代码修改数据并将这些修改反映回托管代码,那么您需要在本机调用返回后将修改封送回。

【讨论】:

  • 谢谢。小错误。请用 ;在 nRow 之后
  • 那么,是 col 专业还是 row 专业?我现在很好奇。
  • 我不得不说我有一些问题。我现在可以传入二维数组,但返回值只是一个标量 - matlab 编码器只生成这个签名。这很好,因为我可以传入一个一维非固定数组(即二维数组的第一列)并得到结果。
  • 您可以通过引用传递一个空的emxArray_real_T 作为参数。并使用它来返回结果。或者您可以将emxArray_real_T* 作为函数返回值返回,并将其编组为IntPtr。我对 Matlab 编码器知之甚少,无法理解分配的工作原理。另外,我很想知道,col-major 还是 row-major?
  • 查看添加的 C 代码。我认为这是col-major。试试你现在的建议。