【问题标题】:Mex files: how to return an already allocated matlab arrayMex 文件:如何返回已分配的 matlab 数组
【发布时间】:2013-11-17 18:52:50
【问题描述】:

我发现了一个非常棘手的问题,我似乎无法轻易解决。简而言之,我想从 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;
}

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

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

请帮忙!

编辑

在与 Amro 进一步查看和讨论后,似乎即使我的第一个代码 sn-p 也不支持,并且在某些情况下可能导致 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

【问题讨论】:

  • 为什么pargout[0] = pargin[0]; 不做你想做的事?如果这似乎太不受支持,那么mxCreateSharedDataCopy 是否通过共享相同的数据指针来满足您的需求,但处理引用计数以使 MATLAB 不会最终崩溃?
  • 一种使用共享数据的有趣方法。您能否将详细的解决方案发布为新答案?
  • @Shai: typecastx James Tursa 提交的内容是一个很好的例子;它基本上调用:plhs[0] = mxCreateSharedDataCopy(prhs[0]); 而不是mxDuplicateArray 我在我的回答中写道
  • @Shai For mxCreateSharedDataCopyhere is a thorough, but dated examplehere is a quick one from James Tursa。另请参阅(半)未记录的 MEX API 函数的 this nice list
  • 我认为pargin_to_pargout(a.field); segfaults 因为nargout 是0 而pargout[0] 是无效的。我在答案中添加了关于 mxCreateSharedDataCopy 的讨论。也许会有用。

标签: matlab memory-management mex


【解决方案1】:

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

#include "mex.h"

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    plhs[0] = mxDuplicateArray(prhs[0]);
}

【讨论】:

  • 我不想复制数组。我想创建一个新数组,它保存与第一个相同的物理内存指针。 mxDuplicateArray 将分配新内存并复制数据。
  • 这很危险,可能导致 MATLAB 崩溃。不过,有一些未记录的方式来共享数据,请参阅这篇文章:stackoverflow.com/a/18849127/97160
  • 崩溃的原因是两个mxArray结构指向内存中的相同数据,但是MATLAB不知道这两个变量是“链接”的;所以后来当一个被释放时,另一个被悬空指针留在不一致的状态......在内部,MATLAB使用mxArray_tag结构中的未记录标志跟踪这种事情(想想引用计数)。当您为常规 MATLAB 矩阵编写 A = B; 时,就会看到这种数据共享行为。有关更多信息,请参阅该帖子的 cmets 中发布的链接。
  • 实际上plhs[0] = prhs[0] 是合法的,尽管这种情况没有明确记录。输出数组是一个单独的mxArray,它与输入数组共享其数据,当其中一个在 MATLAB 端被更改/删除时,与mxSetData 情况不同,它始终被正确处理。除非您知道自己在做什么,否则我通常不推荐它 :) 加上它是否有点令人困惑,因为它破坏了输入 prhs 的恒定性。请参阅此讨论:mathworks.com/matlabcentral/answers/77048
  • 奇怪的是,在某些情况下使用plhs[0] = prhs[0] 时,我发现了一个可靠的崩溃。我已经在mathworks.com/matlabcentral/answers/77048 上发布了对 James Tursa 的回复。所以似乎即使这是不正确的:(绝对没有(合法的)方式来复制那里和回来的内存。
【解决方案2】:

虽然没有记录,但 MEX API 函数 mxCreateSharedDataCopy given as a solution by 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]; 并且在从mexFunction 返回之前未对plhs[0] 进行进一步修改,那么一切都很好,并且由于MATLAB,您将拥有一个共享的数据副本。但是,如果在上述分配之后您修改了 plhs[0] 在 MEX 函数中,那么在 prhs[0] 中也会看到更改,因为它引用了相同的数据缓冲区。另一方面,当显式生成共享副本(使用mxCreateSharedDataCopy)时,有两个不同的mxArray 对象,对一个数组数据的更改将触发复制操作,从而产生两个完全独立的数组。还有,直接赋值can cause segmentation faults in some cases

修改后的 MathWorks 示例

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

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

正如评论所说,这不在mex.h,所以你必须自己声明。

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

  1. 通过mxDuplicateArray进行深拷贝:

    copy1 = mxDuplicateArray(prhs[0]);
    
  2. 通过mxCreateSharedDataCopy共享副本:

    copy2 = mxCreateSharedDataCopy(copy1);
    
  3. 直接复制mxArray*,由我添加:

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

然后它打印每个 mxArray 的数据缓冲区地址 (pr) 及其第一个值。这是修改后的mxsharedcopy(x)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]copy0,除了指向同一个mxArray 的另一个指针之外,我们没有创建任何新内容。
  2. 比较prhs[0]copy1,注意mxDuplicateArray 在地址721BF120 处创建了一个新的mxArray,并将数据复制到19740060 处的新缓冲区中。
  3. copy2copy1 有不同的地址 (mxArray*),这意味着它也是不同的 mxArray 不仅是由不同变量指向的同一个地址,而是 它们都是在地址19740060共享相同的数据

问题归结为:在plhs[0]copy0copy2 中返回是否安全(分别来自简单的指针复制或mxCreateSharedDataCopy)或者是否有必要使用mxDuplicateArray,实际上复制数据?我们可以通过销毁copy1 并验证copy2 仍然有效来证明mxCreateSharedDataCopy 可以工作:

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

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

回到问题。比 MathWorks 示例更进一步,返回输入的共享数据副本。做吧:

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

【讨论】:

  • 嗨,chappjc,很好的答案 - 但我的消息很糟糕:我刚刚玩过 Matlab R2014A Pre - 发现这个很好的无证 mxCreateSharedDataCopy 和其他人已经消失了。好伤心。
  • @BastianEbeling 我看到在 R2014a 中 mxCreateSharedDataCopy 现在导出为修饰的 C++ 函数(修改依赖于编译器);未修饰的名称为struct mxArray_tag * matrix::detail::noninlined::mx_array_api::mxCreateSharedDataCopy(struct mxArray_tag const *。似乎 MathWorks 已经进行了一些代码重构,包括将 MX API 函数组织到各种命名空间中。无论如何,它仍然对我有用,尽管请注意 mex 现在对 C 和 C++ 有不同的配置。您是否收到链接器错误?
  • chappjc 新年快乐!我发现 C++ 也损坏了一个 - 但对我来说,没有机会在普通的 c-mex 中运行该函数。是:导致链接器错误。我不想将我的 c-mex 代码重写为 c++-mex:你知道我的意思吗?
  • 也许更多细节会更清楚:在windoze环境中编译会导致:Building with 'Microsoft Windows SDK 7.1 (C)'. Error using mex Creating library double2byte.lib and object double2byte.exp double2byte.obj : error LNK2019: unresolved external symbol mxCreateSharedDataCopy referenced in function mexFunction double2byte.mexw64 : fatal error LNK1120: 1 unresolved externals
  • @BastianEbeling:你在 R2014a 之前就已经工作了吗?当我在 R2014a 中编译时,我不必进行任何更改,使用帖子中的相同原型。但是,您可能需要使用开关运行 mex 以指示 C++ 代码,不需要重写,但链接应该可以工作。
猜你喜欢
  • 1970-01-01
  • 2023-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-28
  • 1970-01-01
  • 1970-01-01
  • 2021-05-18
相关资源
最近更新 更多