C++ Mex function being passed a MATLAB class object

49 views (last 30 days)
AJ
AJ on 11 Nov 2024 at 18:51
Edited: James Tursa on 13 Nov 2024 at 1:14
In my application, I want to have a mex function that accepts an argument that is a custom (but simple) class object with class properties.
The mex function should also return a congruent class object, but having different values (properties). Something like:
myObjectCopy = mxDuplicateArray( prhs[0] ); // Has a .data property
mxSetProperty(myObjectCopy, 0, "data", mxCreateDoubleScalar(4.9) );
plhs[0] = myObjectCopy;
I'm having some access violation issues during successive calls, and I suspect that mxDuplicateArray() may not be creating a deep copy for class objects.
I'd appreciate any definitive advice.

Accepted Answer

James Tursa
James Tursa on 12 Nov 2024 at 2:06
Edited: James Tursa on 12 Nov 2024 at 3:01
mxDuplicateArray( ) does indeed create deep copies ... or at least it used to. Not sure why that would have changed. There might be issues with resources in some classdef objects, but for simple classdef objects like you describe above there should be no problem with access violations or seg faults. E.g., take this code:
% myObject.m
classdef myObject
properties
data;
end
end
and
// objectdata.cpp
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
mxArray *myObjectCopy;
myObjectCopy = mxDuplicateArray( prhs[0] ); // Has a .data property
mxSetProperty(myObjectCopy, 0, "data", mxCreateDoubleScalar(4.9) );
plhs[0] = myObjectCopy;
}
Compile the mex routine:
>> mex objectdata.cpp
Building with 'MinGW64 Compiler (C++)'.
MEX completed successfully.
Run it:
>> mo = myObject
mo =
myObject with properties:
data: []
>> mo.data = 3.4
mo =
myObject with properties:
data: 3.4000
>> mr = objectdata(mo)
mr =
myObject with properties:
data: 4.9000
>> mr = objectdata(mo)
mr =
myObject with properties:
data: 4.9000
>> mr = objectdata(mo)
mr =
myObject with properties:
data: 4.9000
>> mr = objectdata(mo)
mr =
myObject with properties:
data: 4.9000
>> mr = objectdata(mo)
mr =
myObject with properties:
data: 4.9000
>> for k=1:100000; mr = objectdata(mo);end
>>
I don't have any issues with calling this mex routine repeatedly. Can you provide us with more complete code and details of your class?
I believe mxSetProperty( ) takes care of destroying the current data property in the background so that you don't have a memory leak, although this is not documented. E.g., as evidence, this doesn't run out of memory:
>> mo.data = rand(1,100000000);
>> for k=1:10000000; mr = objectdata(mo);end
>>
Note that this is different behavior from mxSetCell( ) for cell arrays and mxSetField( ) for struct arrays, where you would get a memory leak if you did not manually properly destroy the existing cell or field element first.
*** EDIT #1 ***
So, I put in some memory prints surrounding the mxDuplicateArray( ) calls and got something unexpected, at least to me:
// objectdata2.cpp
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
mxArray *myObjectCopy;
mexPrintf("\n\nBefore mxDuplicateArray call ...\n\n");
mexCallMATLAB(0,NULL,0,NULL,"memory");
myObjectCopy = mxDuplicateArray( prhs[0] ); // Has a .data property
mexPrintf("\n\nAfter mxDuplicateArray call ...\n\n");
mexCallMATLAB(0,NULL,0,NULL,"memory");
mxSetProperty(myObjectCopy, 0, "data", mxCreateDoubleScalar(4.9) );
plhs[0] = myObjectCopy;
}
Running:
>> mo = myObject
mo =
myObject with properties:
data: []
>> mo.data = rand(1,100000000);
>> mr = objectdata2(mo)
Before mxDuplicateArray call ...
Maximum possible array: 3338 MB (3.500e+09 bytes) *
Memory available for all arrays: 3338 MB (3.500e+09 bytes) *
Memory used by MATLAB: 3160 MB (3.313e+09 bytes)
Physical Memory (RAM): 16073 MB (1.685e+10 bytes)
* Limited by System Memory (physical + swap file) available.
After mxDuplicateArray call ...
Maximum possible array: 3339 MB (3.501e+09 bytes) *
Memory available for all arrays: 3339 MB (3.501e+09 bytes) *
Memory used by MATLAB: 3160 MB (3.313e+09 bytes)
Physical Memory (RAM): 16073 MB (1.685e+10 bytes)
* Limited by System Memory (physical + swap file) available.
mr =
myObject with properties:
data: 4.9000
The memory footprint did not go up when mxDuplicateArray( ) was called, even though the data property is 763MB and should be easy to see. I may have to retract what I said about mxDuplicateArray( ) when dealing with classdef objects. These don't look like deep copies to me now. Because you don't have direct access to the properties, maybe MATLAB is in fact doing shared data copies behind the scenes.
That being said, I still don't know why you would be getting seg faults. It would help if you posted a small complete example that demonstrates the problem.
*** EDIT #2 ***
To confirm this:
// mxDuplicateArray.c
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
plhs[0] = mxDuplicateArray( prhs[0] ); // Has a .data property
}
and
// datapointers.c
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
mexPrintf("pr = %p\n",mxGetData(prhs[0]));
}
gives this:
>> mo = myObject
mo =
myObject with properties:
data: []
>> mo.data = rand(1,100000000);
>> mr = mxDuplicateArray(mo)
mr =
myObject with properties:
data: [1×100000000 double]
>> datapointers(mo.data)
pr = 000002E1FF6DF060
>> datapointers(mr.data)
pr = 000002E1FF6DF060
So, yes, it is confirmed that mxDuplicateArray( ) does a shared data copy in the background when working with classdef objects (same data pointer). Surprising to me, but there it is.
  5 Comments
James Tursa
James Tursa on 13 Nov 2024 at 0:38
Edited: James Tursa on 13 Nov 2024 at 1:14
You have a misunderstanding of how classdef objects work in a mex routine. I will contrast that behavior with cell and struct arrays.
void mxSetCell(mxArray *pm, mwIndex index, mxArray *value);
void mxSetField(mxArray *pm, mwIndex index, const char *fieldname, mxArray *pvalue);
void mxSetFieldByNumber(mxArray *pm, mwIndex index, int fieldnumber, mxArray *pvalue);
In all three of the above cases, the pointer value itself (not a copy of the mxArray) is actually attached to the cell or struct array. Then value is removed from the garbage collection list (its disposition now being fully determined by whatever happens to its parent). The key point is that a deep copy is NOT made. This is a very shallow copy. Similarly take these API functions:
mxArray *mxGetCell(const mxArray *pm, mwIndex index);
mxArray *mxGetField(const mxArray *pm, mwIndex index, const char *fieldname);
mxArray *mxGetFieldByNumber(const mxArray *pm, mwIndex index, int fieldnumber);
The returned mxArray pointer is the actual pointer from the cell or struct array. It is not any kind of mxArray copy whatsoever. You are pointing into the cell or struct array elements themselves. Whatever you do to the data inside the returned mxArray will be done to the original cell or struct array pm.
Cell and struct arrays are very efficient in mex routines because only shallow copies are involved, not deep copies. The downside is when you want to use the same mxArray repeatedly. There should be an official way to do this in a mex routine but unfortunately there isn't ... you have to resort to hacks (an issue that is beyond the scope of this post).
Now consider the classdef object API functions:
mxArray *mxGetProperty(const mxArray *pa, mwIndex index, const char *propname);
The returned mxArray pointer points to a DEEP copy of the pa object property. I presume, based on the earlier testing above, that if this property itself is a classdef object it will be a shared data copy? Don't know for sure about that and can probably test to determine this, but that is a side point for the present discussion. The main point is that the returned mxArray pointer points to a newly created mxArray, not the original property in pa.
void mxSetProperty(mxArray *pa, mwIndex index, const char *propname, const mxArray *value);
For this case, a DEEP copy is made of value and that is what is attached to pa as the new property (with noted caveat). Any subsequent changes you make to value have no effect on the property in pa because they are not the same mxArray.
Now look at your code:
mxSetProperty(plhs[0],0,"data",pMxDataPropertyOut); // Why no return code for this function???
// ==== END TRIAL 2
pDataOut = mxGetDoubles(pMxDataPropertyOut);
MEXASSERT(pDataOut != NULL,"mxGetDoubles(pMxDataPropertyOut) failed");
for (size_t i = 0; i < numberOfElements; i++)
pDataOut[i] = pDataIn[i] * 2.0;
You set the data property of plhs[0] to pMxDataPropertyOut (when all of the double values are still 0.0), and THEN you modify the data of pMxDataPropertyOut! But at that point pMxDataPropertyOut is not related to plhs[0] whatsoever, because a DEEP copy of it was put into plhs[0], not a shared data copy. You need to reverse the order of these statements and modify pMxDataPropertyOut before you put a deep copy of it into plhs[0]. E.g.,
pDataOut = mxGetDoubles(pMxDataPropertyOut);
MEXASSERT(pDataOut != NULL,"mxGetDoubles(pMxDataPropertyOut) failed");
for (size_t i = 0; i < numberOfElements; i++) // modify the data first!
pDataOut[i] = pDataIn[i] * 2.0;
mxSetProperty(plhs[0],0,"data",pMxDataPropertyOut); // then deep copy to plhs[0]
Again, this behavior is different from the treatment of cell and struct arrays in a mex routine. Cell and struct arrays work with shallow copies of the elements, and classdef objects work with deep copies of the properties (with the potential caveat noted above).
AJ
AJ on 13 Nov 2024 at 1:04
Thank you for enlightening; I'm fortunate that you chose to respond.
I see now in the help for mxSetProperty:
"mxSetProperty makes a copy of the value before assigning it as the new property value. If the property uses a large amount of memory, then making a copy might be a concern. There must be sufficient memory in the heap to hold the copy of the value."
Likewise, "mxGetProperty makes a copy of the value."
I've been writing mex functions for 32 years, which just goes to show that there's always something more to learn!

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

R2024a

Community Treasure Hunt

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

Start Hunting!