A MEX solution to split classdef-defined object arrays into cells

2 views (last 30 days)
As an experiment with MEX files, I am trying to create a mex routine that will take an array A of classdef-defined objects and return a cell array C whos elements are the individual elements of the input array, i.e., C{i}=A(i). I know this can be done conventionally with num2cell, but I am looking for a mex alternative.
Using a rudimentary classdef,
classdef fakeclass
properties
a=2
end
end
and the following mex code obj2cell.c,
#include "mex.h"
#include "matrix.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
// Check the number of input and output arguments
if (nrhs != 1)
{
mexErrMsgIdAndTxt("objectArrayToCell:invalidInput", "One input argument is required.");
}
// Get the input object array
const mxArray *inputArray = prhs[0];
// Get the number of elements in the input array
mwSize numElements = mxGetNumberOfElements(inputArray);
// Create a cell array to store the elements
plhs[0] = mxCreateCellMatrix(1, numElements);
// Copy each element from the object array to the cell array
for (mwIndex i = 0; i < numElements; ++i)
{
mxSetCell(plhs[0], i, mxDuplicateArray(mxGetCell(inputArray, i)));
}
}
I performed the following test, but it gives only empty results.
>> C=obj2cell([fakeclass,fakeclass])
C =
1×2 cell array
{0×0 double} {0×0 double}
Can someone point out the flaw in my code?

Accepted Answer

James Tursa
James Tursa on 12 Sep 2023
Edited: James Tursa on 12 Sep 2023
You need to use mxGetProperty( ) to get at the properties of a classdef object. And since this returns a deep copy, no need for the subsequent mxDuplicateArray( ). E.g.,
mxSetCell(plhs[0], i, mxGetProperty(inputArray, i, "a"));
And of course in your actual code you would need to put in some checks to ensure that prhs[0] is indeed the proper class with the expected property. E.g.,
if( !mxIsClass(inputArray,"fakeclass") ) {
// error message
}
I don't think there is an API function for checking if a property exists. You simply have to examine the returned value of mxGetProperty( ) to see if it is NULL or not. Or you could simply let the routine fill the cell array with NULL values (which is legit). At the MATLAB level, NULL elements are converted to empty 0x0 doubles on the fly when accessed. You would have to decide if this behavior is acceptable or not when requesting non-existent properties. You may want to check which properties the object has at the MATLAB level and pass that in as a 2nd input argument to tell the mex routine which property to get.
  12 Comments
James Tursa
James Tursa on 16 Sep 2023
Edited: James Tursa on 16 Sep 2023
Well, even though you don't feel too keen on subsref( ) and don't want deep copies, that is the only way I know how to do what you want at the moment. Here is the C-code:
// Bare bones function to turn array into cell array of elements
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
const mwSize *dims;
mwSize ndim;
mxArray *S, *subs, *lhs, *ix;
mxArray *rhs[2];
double *pr;
const char *fieldnames[2] = {"type","subs"};
const char parens[] = "()";
size_t i, n;
if( nrhs ) {
// The indexing struct
S = mxCreateStructMatrix(1, 1, 2, fieldnames); // create the struct
mxSetFieldByNumber(S, 0, 0, mxCreateString(parens)); // set the S.type
subs = mxCreateCellMatrix(1,1); // create the subs cell
ix = mxCreateDoubleScalar(0); // create a dummy scalar
pr = (double *) mxGetData(ix); // get pointer to dummy scalar data
mxSetCell(subs,0,ix); // attach dummy scalar to subs cell
mxSetFieldByNumber(S, 0, 1, subs); // set the S.subs
// Create the output variable
dims = mxGetDimensions(prhs[0]);
ndim = mxGetNumberOfDimensions(prhs[0]);
plhs[0] = mxCreateCellArray(ndim, dims);
n = mxGetNumberOfElements(prhs[0]);
// Set up for indexing call
rhs[0] = (mxArray *) prhs[0];
rhs[1] = S;
// Loop over elements
for( i=0; i<n; i++ ) {
*pr = i+1; // set the index we are trying to get (MATLAB 1-based)
mexCallMATLAB(1,&lhs,2,rhs,"subsref"); // Extract the element (deep copy)
mxSetCell(plhs[0],i,lhs); // Attach the element to output
}
mxDestroyArray(S); // destroy the S struct
}
}
And here is a sample run:
>> d1 = fakeclass; d1.a = 1;
>> d2 = fakeclass; d2.a = 2;
>> d3 = fakeclass; d3.a = 3;
>> fake = [d1,d2,d3];
>> c = obj2cell(fake)
c =
1×3 cell array
{1×1 fakeclass} {1×1 fakeclass} {1×1 fakeclass}
>> c{1}
ans =
fakeclass with properties:
a: 1
>> c{2}
ans =
fakeclass with properties:
a: 2
>> c{3}
ans =
fakeclass with properties:
a: 3
There is nothing special about what type of object gets passed into the mex function. As long as you can get elements of it with subsref, the mex function should probably work with it. E.g., here is just an ordinary double array example:
>> d = 1:3;
>> c = obj2cell(d)
c =
1×3 cell array
{[1]} {[2]} {[3]}
(Of course, having gone to all this trouble using subsref from a mex routine, it should be pointed out that it would be simpler to simply use mexCallMATLAB to call back and use num2cell instead)
Matt J
Matt J on 19 Sep 2023
And everything returned from a mexCallMATLAB( ) call will be a deep copy, even if the same operation at the m-file level is a shallow copy.
Too bad. Well, at least it might be a more succinct way of deep-copying an object than trying to determine and loop over all the properties individually.

Sign in to comment.

More Answers (0)

Categories

Find more on Write C Functions Callable from MATLAB (MEX Files) in Help Center and File Exchange

Products


Release

R2021b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!