5

我发现了一个非常棘手的问题,我似乎无法轻松解决。简而言之,我想从 mex 文件返回一个数组,该数组已作为 mex 函数输入传递。你可以轻松地做到这一点:

void mexFunction(int nargout, mxArray *pargout [ ], int nargin, const mxArray *pargin[])
{
   pargout[0] = pargin[0];
}

但这不是我需要的。我想从中获取原始指针pargin[0],在内部处理它,并通过设置相应的数据指针返回一个新创建的 mex 数组。像那样:

#include <mex.h>

void mexFunction(int nargout, mxArray *pargout [ ], int nargin, const mxArray *pargin[])
{
  mxArray *outp;
  double *data;
  int m, n;

  /* get input array */
  data = mxGetData(pargin[0]);
  m = mxGetM(pargin[0]);
  n = mxGetN(pargin[0]);

  /* copy pointer to output array */
  outp = mxCreateNumericMatrix(0,0,mxDOUBLE_CLASS,mxREAL);
  mxSetM(outp, m);
  mxSetN(outp, n);
  mxSetData(outp, data);
  /* segfaults with or without the below line */
  mexMakeMemoryPersistent(data);
  pargout[0] = outp;
}

它不起作用。我得到一个段错误,如果不是立即,然后在几个电话之后。我相信文档中没有提到这种情况。唯一的要求是data使用 分配指针mxCalloc,这显然是有的。因此,我认为这段代码是合法的。

我需要这样做,因为我正在将一个复杂的 MATLAB 结构解析为我的内部 C 数据结构。我处理数据,有些数据被重新分配,有些则没有。我想透明地返回输出结构,而不考虑何时必须简单地复制mxArray(第一个代码片段)以及何时必须创建它。

请帮忙!

编辑

在与 Amro 进一步查看和讨论之后,似乎即使我的第一个代码片段也不支持,并且在某些情况下可能导致 MATLAB 崩溃,例如,当将结构字段或单元元素传递给这样的 mex 函数时:

>> a.field = [1 2 3];
>> b = pargin_to_pargout(a.field);   % ok - works and assigns [1 2 3] to b
>> pargin_to_pargout(a.field);       % bad - segfault

看来我将不得不走“无证 MATLAB”的道路并使用mxCreateSharedDataCopymxUnshareArray.

4

2 回答 2

9

你应该使用mxDuplicateArray,这是记录的方式:

#include "mex.h"

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    plhs[0] = mxDuplicateArray(prhs[0]);
}
于 2013-11-06T13:50:58.930 回答
6

虽然没有记录,但 MEX API 函数mxCreateSharedDataCopy MathWorks提供的解决方案,现在显然已被拒绝,用于创建mxArray. MathWorks 甚至在他们的解决方案中提供了一个示例,mxsharedcopy.c.

如删除的 MathWorks 解决方案 (1-6NU359) 中所述,该函数可用于克隆mxArray标头。plhs[0] = prhs[0];然而,做和之间的区别在于plhs[0] = mxCreateSharedDataCopy(prhs[0]);,第一个版本只是复制mxArray*(一个指针),因此不会创建一个新mxArray容器(至少在mexFunction返回和 MATLAB 工作之前不会),这将增加数据在两者中的引用计数mxArrays。

为什么这可能是一个问题?如果您在从 中返回之前使用plhs[0] = prhs[0];并且没有进行进一步修改,那么一切都很好,并且由于 MATLAB,您将拥有一个共享的数据副本。但是,如果在上述分配之后您在 MEX 函数中进行了修改,则也可以看到更改,因为它引用了相同的数据缓冲区。另一方面,当显式生成共享副本(使用)时,有两个不同的对象,并且对一个数组数据的更改将触发复制操作,从而产生两个完全独立的数组。此外,在某些情况下,直接分配可能会导致分段错误plhs[0]mexFunctionplhs[0] prhs[0]mxCreateSharedDataCopymxArray

修改后的 MathWorks 示例

mxsharedcopy.c从使用上面引用的 MathWorks 解决方案修改的示例开始。第一个重要步骤是为mxCreateSharedDataCopy函数提供原型:

/* Add this declaration because it does not exist in the "mex.h" header */
extern mxArray *mxCreateSharedDataCopy(const mxArray *pr);

正如评论所述,这不在 中mex.h,因此您必须自己声明。

下一部分通过以下方式mxsharedcopy.c创建新的s:mxArray

  1. 通过以下方式进行深拷贝mxDuplicateArray

    copy1 = mxDuplicateArray(prhs[0]);
    
  2. 通过以下方式共享副本mxCreateSharedDataCopy

    copy2 = mxCreateSharedDataCopy(copy1);
    
  3. 的直接副本mxArray*,由我添加:

    copy0 = prhs[0]; // OK, but don't modify copy0 inside mexFunction!
    

然后它pr为每个mxArray值和它们的第一个值打印数据缓冲区 ( ) 的地址。这是修改后的输出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

发生了什么:

  1. 正如预期的那样,除了另一个指向相同的指针之外prhs[0],我们没有创建任何新的东西。copy0mxArray
  2. 比较prhs[0]copy1,注意mxDuplicateArray创建了一个新mxArray的 at 地址721BF120,并将数据复制到了一个新的缓冲区 at 19740060
  3. copy2mxArray*不同copy1的地址(_ _ mxArray_19740060

问题归结为:返回or (分别从简单的指针副本 or )返回是否安全,plhs[0]或者是否有必要使用实际复制数据的 ?我们可以通过销毁并验证它仍然有效来证明这将起作用:copy0copy2mxCreateSharedDataCopymxDuplicateArraymxCreateSharedDataCopycopy1copy2

mxDestroyArray(copy1);
copy2val0 = *mxGetPr(copy2); % no crash!

将共享数据副本应用于输入

回到问题。比 MathWorks 示例更进一步,并返回 input 的共享数据副本。做就是了:

if (nlhs>0) plhs[0] = mxCreateSharedDataCopy(prhs[0]);

屏住呼吸!

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

现在进行临时输入(不带format debug):

>> tempDup = mxsharedcopy(2*ones(1e3));
>> tempDup(1)
ans =
     2

有趣的是,如果我不使用mxCreateSharedDataCopy(即仅使用plhs[0] = prhs[0];)进行测试,MATLAB 不会崩溃,但输出变量永远不会实现:

>> tempDup = mxsharedcopy(2*ones(1e3)) % no semi-colon
>> whos tempDup
>> tempDup(1)
Undefined function 'tempDup' for input arguments of type 'double'.

R2013b,Windows,64 位。

mxsharedcopy.cpp(修改后的 C++ 版本):

#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]);
}
于 2013-11-08T02:42:21.267 回答