Mex files: how to return an already allocated matlab array
Solution 1:
You should use mxDuplicateArray
, thats the documented way:
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
plhs[0] = mxDuplicateArray(prhs[0]);
}
Solution 2:
While undocumented, the MEX API function mxCreateSharedDataCopy
iswas given as a solution by MathWorks, now apparently disavowed, for creating a shared-data copy of an mxArray
. MathWorks even provides an example in their solution, mxsharedcopy.c
.
As described in that removed MathWorks Solution (1-6NU359), the function can be used to clone the mxArray
header. However, the difference between doing plhs[0] = prhs[0];
and plhs[0] = mxCreateSharedDataCopy(prhs[0]);
is that the first version just copies the mxArray*
(a pointer) and hence does not create a new mxArray
container (at least not until the mexFunction
returns and MATLAB works it's magic), which would increment the data's reference count in both mxArray
s.
Why might this be a problem? If you use plhs[0] = prhs[0];
and make no further modification to plhs[0]
before returning from mexFunction
, all is well and you will have a shared data copy thanks to MATLAB. However, if after the above assignment you modify plhs[0]
in the MEX function, the change be seen in prhs[0]
as well since it refers to the same data buffer. On the other hand, when explicitly generating a shared copy (with mxCreateSharedDataCopy
) there are two different mxArray
objects and a change to one array's data will trigger a copy operation resulting in two completely independent arrays. Also, direct assignment can cause segmentation faults in some cases.
Modified MathWorks Example
Start with an example using a modified mxsharedcopy.c
from the MathWorks solution referenced above. The first important step is to provide the prototype for the mxCreateSharedDataCopy
function:
/* Add this declaration because it does not exist in the "mex.h" header */
extern mxArray *mxCreateSharedDataCopy(const mxArray *pr);
As the comment states, this is not in mex.h
, so you have to declare this yourself.
The next part of the mxsharedcopy.c
creates new mxArray
s in the following ways:
-
A deep copy via
mxDuplicateArray
:copy1 = mxDuplicateArray(prhs[0]);
-
A shared copy via
mxCreateSharedDataCopy
:copy2 = mxCreateSharedDataCopy(copy1);
-
Direct copy of the
mxArray*
, added by me:copy0 = prhs[0]; // OK, but don't modify copy0 inside mexFunction!
Then it prints the address of the data buffer (pr
) for each mxArray
and their first values. Here is the output of the modified mxsharedcopy(x)
for x=ones(1e3);
:
prhs[0] = 72145590, mxGetPr = 18F90060, value = 1.000000
copy0 = 72145590, mxGetPr = 18F90060, value = 1.000000
copy1 = 721BF120, mxGetPr = 19740060, value = 1.000000
copy2 = 721BD4B0, mxGetPr = 19740060, value = 1.000000
What happened:
- As expected, comparing
prhs[0]
andcopy0
we have not created anything new except another pointer to the samemxArray
. - Comparing
prhs[0]
andcopy1
, notice thatmxDuplicateArray
created a newmxArray
at address721BF120
, and copied the data into a new buffer at19740060
. -
copy2
has a different address (mxArray*
) fromcopy1
, meaning it is also a differentmxArray
not just the same one pointed to by different variables, but they both share the same data at address19740060
.
The question reduces to: Is it safe to return in plhs[0]
either of copy0
or copy2
(from simple pointer copy or mxCreateSharedDataCopy
, respectively) or is it necessary to use mxDuplicateArray
, which actually copies the data? We can show that mxCreateSharedDataCopy
would work by destroying copy1
and verifying that copy2
is still valid:
mxDestroyArray(copy1);
copy2val0 = *mxGetPr(copy2); % no crash!
Applying Shared-Data Copy to Input
Back to the question. Take this a step further than the MathWorks example and return a share-data copy of the input. Just do:
if (nlhs>0) plhs[0] = mxCreateSharedDataCopy(prhs[0]);
Hold your breath!
>> format debug
>> x=ones(1,2)
x =
Structure address = 9aff820 % mxArray*
m = 1
n = 2
pr = 2bcc8500 % double*
pi = 0
1 1
>> xDup = mxsharedcopy(x)
xDup =
Structure address = 9afe2b0 % mxArray* (different)
m = 1
n = 2
pr = 2bcc8500 % double* (same)
pi = 0
1 1
>> clear x
>> xDup % hold your breath!
xDup =
Structure address = 9afe2b0
m = 1
n = 2
pr = 2bcc8500 % double* (still same!)
pi = 0
1 1
Now for a temporary input (without format debug
):
>> tempDup = mxsharedcopy(2*ones(1e3));
>> tempDup(1)
ans =
2
Interestingly, if I test without mxCreateSharedDataCopy
(i.e. with just plhs[0] = prhs[0];
), MATLAB doesn't crash but the output variable never materializes:
>> tempDup = mxsharedcopy(2*ones(1e3)) % no semi-colon
>> whos tempDup
>> tempDup(1)
Undefined function 'tempDup' for input arguments of type 'double'.
R2013b, Windows, 64-bit.
mxsharedcopy.cpp (modified C++ version):
#include "mex.h"
/* Add this declaration because it does not exist in the "mex.h" header */
extern "C" mxArray *mxCreateSharedDataCopy(const mxArray *pr);
bool mxUnshareArray(const mxArray *pr, const bool noDeepCopy); // true if not successful
void mexFunction(int nlhs,mxArray *plhs[],int nrhs,const mxArray *prhs[])
{
mxArray *copy1(NULL), *copy2(NULL), *copy0(NULL);
//(void) plhs; /* Unused parameter */
/* Check for proper number of input and output arguments */
if (nrhs != 1)
mexErrMsgTxt("One input argument required.");
if (nlhs > 1)
mexErrMsgTxt("Too many output arguments.");
copy0 = const_cast<mxArray*>(prhs[0]); // ADDED
/* First make a regular deep copy of the input array */
copy1 = mxDuplicateArray(prhs[0]);
/* Then make a shared copy of the new array */
copy2 = mxCreateSharedDataCopy(copy1);
/* Print some information about the arrays */
// mexPrintf("Created shared data copy, and regular deep copy\n");
mexPrintf("prhs[0] = %X, mxGetPr = %X, value = %lf\n",prhs[0],mxGetPr(prhs[0]),*mxGetPr(prhs[0]));
mexPrintf("copy0 = %X, mxGetPr = %X, value = %lf\n",copy0,mxGetPr(copy0),*mxGetPr(copy0));
mexPrintf("copy1 = %X, mxGetPr = %X, value = %lf\n",copy1,mxGetPr(copy1),*mxGetPr(copy1));
mexPrintf("copy2 = %X, mxGetPr = %X, value = %lf\n",copy2,mxGetPr(copy2),*mxGetPr(copy2));
/* TEST: Destroy the first copy */
//mxDestroyArray(copy1);
//copy1 = NULL;
//mexPrintf("\nFreed copy1\n");
/* RESULT: copy2 will still be valid */
//mexPrintf("copy2 = %X, mxGetPr = %X, value = %lf\n",copy2,mxGetPr(copy2),*mxGetPr(copy2));
if (nlhs>0) plhs[0] = mxCreateSharedDataCopy(prhs[0]);
//if (nlhs>0) plhs[0] = const_cast<mxArray*>(prhs[0]);
}