de-marshal results?
Asked Answered
A

1

1

This is an extension to this question to be able to return an array rather than a scalar.

The produced C code from the matlab code via matlab coder looks ok now (see below). I just try to figure out how to get the results back into the C# world. Here is my first attempt:

C# code

[DllImport(@"C:\bla\CPlusPlus.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void test(ref emxArray_real_T a, ref emxArray_real_T result);

static void Main(string[] args)
{
    double[,] array2D = new double[,] { { 1, 2, 4 }, { 1, 3, 4 } };
    var wrapper = new EmxArrayRealTWrapper(array2D);

    var t = wrapper.Value;
    var t1 = wrapper.Value;
    test(ref t, ref t1);
}

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

Matlab code:

function [result] = test(a, result)
%#codegen

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

result = sum(a);

produced C code

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

  k = result->size[0] * result->size[1];
  result->size[0] = 1;
  result->size[1] = 1;
  emxEnsureCapacity((emxArray__common *)result, k, (int32_T)sizeof(real_T));
  result->data[0] = y;
}

PS:

Given David's answer I am trying something like this at the moment:

[DllImport(@"C:\bla\CPlusPlus.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void test(ref emxArray_real_T a, ref emxArray_real_T result);

static void Main(string[] args)
{
    double[,] array2D = new double[,] { { 1, 2, 4 }, { 1, 3, 4 } };
    double[,] temp = new double[,] { { 0 }, { 0 } };
    var wrapper = new EmxArrayRealTWrapper(array2D);
    var wrapper1 = new EmxArrayRealTWrapper(temp);

    var t = wrapper.Value;
    var t1 = wrapper1.Value;
    test(ref t, ref t1);

    // initialise this by your call to the native code
    int[] size = new int[2];
    Marshal.Copy(t1.size, size, 0, 2);
    int nCol = size[0];
    int nRow = size[1];
    double[] data = new double[nCol * nRow];
    Marshal.Copy(t1.data, data, 0, nCol * nRow);
}

This only gives me one entry: 7 nCol and nRow are equal to 1.

Amygdalate answered 23/2, 2013 at 17:15 Comment(2)
t1 may point at the same memory, so the above may not work but it is a first attempt.Amygdalate
I am sorry I cannot see how ...Amygdalate
W
1

You are essentially asking how to read the contents of a emxArray_real_T into a C# object.

Let's first consider a 1D array. Read it like this:

emxArray_real_T result;
// initialise this by your call to the native code
int size = Marshal.ReadInt32(result.size);
double[] data = new double[size];
Marshal.Copy(result.data, data, 0, size);

And that's it. You'd want to assert that result.numDimensions == 1.

And you may not need to do the Marshal.Copy step. You probably still have access to the array you passed for result.data and so you can just use that.

The two dimensional case is just more of the same. Again you'll want to check that result.numDimensions == 2.

int[] size = new int[2];
Marshal.Copy(result.size, size, 0, 2);
int nCol = size[0];
int nRow = size[1];
double[] data = new double[nCol * nRow];
Marshal.Copy(result.data, data, 0, nCol * nRow);

That puts the data in a one-dimensional array and presumably you'll want to put this into a two-dimensional managed array. Assuming the MATLAB is col-major, you'll need to deal with the col-major to row-major translation.

double[,] arr = new double[nRow, nCol];
int index = 0;
for (int col = 0; col<nCol; col++)
{
    for (int row = 0; row<nRow; row++)
    {
        array[row, col] = data[index];
        index++;
    }
}
Whipcord answered 23/2, 2013 at 17:29 Comment(10)
Sorry I don't get it. I tried to implement what you suggested - see PS original question.Amygdalate
You need to try harder to understand what is going on here. I think you want us to solve your problem without you understanding anything. Of course nrow and ncol are both 1. Of course only a single value is returned. That's what the native code returns. It's right there in the code in your question. The code in the answer is accurate. You have all you need from this answer and my other answers to your questions. If you are still stuck you need to learn more about Matlab array storage.Whipcord
The only thing I am unsure about is whether the data is col major or row major. And which order the values appear in the size array. You should be able to work this out from the documentation. However, I do wonder whether you understand what col major and row major mean.Whipcord
Well, nrow and ncol should not be both one. Given: double[,] array2D = new double[,] { { 1, 2, 4 }, { 1, 3, 4 } }; (in C# speak) I would expect either nrow or ncol to be 2. The result should be 7 and 8 - one row and two columns or whatever. I am sorry if I give the impression that I want you to solve my problem. It is not my intention! It could be that I am missing something - sorry has been a long day ...Amygdalate
The generated C code in your question quite clearly sets nRow and nCol both to 1.Whipcord
Anyway, what are you trying to do here. Perhaps there's a simpler solution.Whipcord
I wanted to pass in a n by n array for example and expected to get a 'double[]' of length n back (to use C# speak). That the matlab coder sets nRow and nCol both to 1 cannot be correct imho. the matlab code works as expected but the generated c code is obviously wrong. I guess I was too ambitious. I can always easily pass in one vector and get results vector by vector. I wanted to avoid this. hope this makes sense. Thanks for all you help!Amygdalate
I mean, what's your problem in a broader sense? What are you invoking MATLAB here? Isn't there an easier way?Whipcord
And yes, the code generated by Matlab coder just seems wrong. How can sum return a single vector when passed a 2 column matrix?Whipcord
I just created this toy example to get the interface (hopefully) working. Thanks!Amygdalate

© 2022 - 2024 — McMap. All rights reserved.