adapt wrapper class for two dimensional case
Asked Answered
T

1

3

This question is an extension of this question.

I would like to adapt the wrapper for the two dimensional case. This is my first attempt:

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:

The orginal matlab code looks like this:

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

and can be invoked like this:

a = [ 1 2; 3 4]

r = test(a)

producing:

r =

     4     6

Unfortunately the produced C cannot achieve what Matlab can achieve (i.e. return an array):

__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;
}
Trimerous answered 23/2, 2013 at 15:43 Comment(2)
allocatedSize is the total number of elements. So that *sizeof(double) is wrong. Just remove it. Set allocatedSize to data.GetLength(0) * data.GetLength(1). The main problem is that you cannot blit double[,] onto a col major MATLAB array. I am correct that it is col major right. Once I know that, then I can adapt your code.Sheasheaf
Thanks. I have adapted the code as you suggested. For new double[,] { { 1, 2, 4 }, { 1, 3, 4 } } I obtain 7 rather than 7 and 8.Trimerous
S
2

I'm assuming that the MATLAB array struct use col-major ordering. In which case the struct constructor needs to look like this:

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

If the native code expects row-major then it's simpler. A C# multi-dimensional array is stored as a contiguous row-major array. So you can use code very similar to the one dimensional code I provided in your recent question.

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

Note well that these two variants differ in the way they pass the data to the native code. The first version passes a copy of the original data. The second passes a reference to the original data. I'm not sure how you want your code to behave. It's easy enough to adapt the second version to pass a copy. As for the first version, if you wanted the native code to modify the data and have those modifications reflected back to the managed code, then you'd need to marshal the modifications back after the native call returned.

Sheasheaf answered 23/2, 2013 at 15:59 Comment(8)
Thanks. Tiny mistake. Please use ; after nRowTrimerous
So, is it col major or row major? I'm curious now.Sheasheaf
I have to say that I have some issues. I can pass in 2d arrays now but the return value is only a scalar - matlab coder just generates this signature. This is kind of ok for as I can pass in a one dimensional non-fixed array (i.e. first column of 2d array) and get the result.Trimerous
You could pass an empty emxArray_real_T by reference as a parameter. And use this to return the result. Or you could return emxArray_real_T* as a function return value and marshal it as IntPtr. I don't know enough about Matlab coder to understand how allocation works. Also, I'm desperate to know, col-major or row-major?Sheasheaf
see added C code. I think it's col-major. try what you suggested now.Trimerous
I can help you with marshalling, pinvoke, how to allocate a Matlab array structure. But I really can't help with Matlab coder.Sheasheaf
The saga continuous. almost there ... see: https://mcmap.net/q/1781999/-de-marshal-resultsTrimerous
OK, so it's row major then?Sheasheaf

© 2022 - 2024 — McMap. All rights reserved.