Matlab Mex库的生命周期

10

有人知道Matlab MEX库的生命周期吗?具体来说,我对以下内容感兴趣:

  1. 是否有一种方法可以在调用之前强制加载库?
  2. 该库是单例还是加载多个实例?
  3. 是否有任何初始化挂钩可在调用之前使用?
  4. 是否有析构函数挂钩/信号可在卸载库以进行清理时拦截?

我在这里和在线上进行了广泛搜索,但找不到这些问题的答案。我的问题在初始化方面存在一些性能成本,如果可能的话,我想避免这种情况,而无需编写服务。

2个回答

9

MEX文件会一直保持加载状态,直到您清除它(clear myMexFunclear mex)或退出MATLAB。

对于预加载,我只能建议使用没有输入或等效无操作输入调用函数。我在我的mexFunction中创建了简单的代码路径来处理这些调用而不出错,最简单的例子是if(!nrhs) return;。随后的调用将不需要从磁盘加载mexFunction(或任何由MEX函数调用的共享库中的其他函数),您也不需要担心此后的初始化成本。

关于初始化/清理、构造函数/析构函数等,我不知道有什么方法可以看到 MATLAB 在加载或卸载 MEX 文件时在做什么,但是一个 MEX 文件是一个普通的共享库(即 DLL/SO),只导出一个函数(mexFunction is the only entry point),因此,正如 Amro 指出的那样,您可以在 Windows 中实现 DllMain 来定义模块和线程的附加/分离操作(请参见 他的回答 中的优秀示例)。我不知道还有其他与库交互的机制。
为了在模块卸载时执行任务,您可以在mexFunction中使用mexAtExit向MATLAB注册一个函数,在MEX函数卸载(再次清除或MATLAB退出)时调用该函数。只需在全局命名空间中定义一个static函数并将其注册到mexAtExit即可。MATLAB提供的示例(mexatexit.c)演示了如何关闭在mexFunction中打开但未关闭的文件流。您还可以释放持久内存、关闭流等。这是一个人为制造的例子:

mexDLLtext.cpp:

#include "mex.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

static FILE   *fp=NULL;
static double *pDataC=NULL, *pDataCpp=NULL, *pMxData=NULL;
static char fName[L_tmpnam], counter = 0;

static void CleanUp(void)
{
  fclose(fp);        /* close file opened with fopen */
  free(pDataC);      /* deallocate buffer allocated with malloc/calloc */
  delete[] pDataCpp; /* deallocate buffer allocated with new double[...] */
  mxFree(pMxData);   /* free data created with mx function like mxMalloc */

  mexPrintf("Closing %s and freeing memory...\n",fName);
}

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    if (!fp) { tmpnam(fName); fp = fopen(fName,"w"); }
    fprintf(fp,"%d ",++counter);

    if (!pDataC) pDataC = (double*) malloc(sizeof(double)*16);
    if (!pDataCpp) pDataCpp = new double[16];
    if (!pMxData) {
        pMxData = (double*) mxMalloc(sizeof(double)*16);
        mexMakeMemoryPersistent(pMxData); mexPrintf("First!\n");
    }

    mexAtExit(CleanUp);
    // Then use the persistent data...
}

运行时:
>> mex -largeArrayDims mexDLLtest.cpp
>> for i=1:5, mexDLLtest; end
First!
>> clear mexDLLtest
Closing \s1rg.1 and freeing memory...
>> type E:\s1rg.1

1 2 3 4 5 

你可以通过 mexLockmexUnlock 来控制文件的卸载。

然而,当函数开始和返回到MATLAB时,参数(即prhsplhs)的处理方式已经有非常详细的文档,所以我猜这不是你想问的问题。

关于多个实例,如果您使用Windows系统,可以尝试使用Sysinternals' Process Explorer查看在MATLAB.exe下运行的线程中加载了哪些模块。无论我调用函数的次数有多少次或多快,我只在线程列表中看到一个(单线程)MEX文件的一个实例。但是,一旦回到命令行,您可以执行version -modules以查看已加载模块的列表,正如Amro所建议的那样。 MEX文件仍将存在,并且与Process Explorer可见的线程列表一样,我只看到某个MEX文件的一个实例。
感谢Amro的建议。我很想看到更权威的答案!

在Windows中,MEX文件实际上只是带有.mexw32/.mexw64扩展名的DLL文件。因此,您应该能够编写常规的DllMain入口函数,并处理DLL_PROCESS_ATTACHDLL_THREAD_ATTACH等通知。此外,MATLAB提供了一种记录退出函数的方法:mexAtExit。顺便说一句,您可以使用version -modules来查看所有加载的模块列表,而不是使用Sysinternals工具。 - Amro
对于管理 MEX 函数的生命周期,我发现实现 switchyard 模式是一种好的技术(使函数接收一个 'string' 作为参数来确定执行什么操作,如 new/delete/get/set 等)。您甚至可以通过将 MEX 函数放置在 private 文件夹中并通过 MATLAB 面向对象类来公开其功能,从而使 MEX 函数“私有化”,这将为您提供适当的构造函数/析构函数语义,特别适用于 MEX 文件包装 C++ 类(请参见 mexopencv 项目以了解此模式的示例)。 - Amro
@Amro 感谢您的建议。我忘记了 mexAtExit。这很重要,所以我加了一点关于它的内容,并且根据您的评论,我总体上改进了我的答案。关于 DllMain 的好主意。我链接到了您的答案。 - chappjc
1
谢谢,使用mexAtExit/mexMakeMemoryPersistent的示例很好。顺便说一句,你是对的,任何时候只有一个MEX模块实例被加载(否则将会破坏共享库的整个目的)。更不用说整个MX/MEX API都不是线程安全的了(http://www.mathworks.com/matlabcentral/answers/101658)。即使可能的话(https://dev59.com/qEnSa4cB1Zd3GeqPRtys#1587451),在同时线程中加载同一个MEX函数的多个副本(在同一进程内)也会带来一系列新问题 :) - Amro

7

如我在评论中提到的,在Windows操作系统下,您可以实现DllMain入口点。这是因为MEX文件只是具有不同扩展名的常规DLL文件。下面是一个最小示例:

testDLL.cpp

#include "mex.h"
#include <windows.h>

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
    switch (dwReason) {
    case DLL_PROCESS_ATTACH:
        mexPrintf("DLL_PROCESS_ATTACH: hModule=0x%x\n", hModule);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        mexPrintf("DLL_PROCESS_DETACH: hModule=0x%x\n", hModule);
        break;
    }
    return TRUE;
}

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    mexPrintf("Inside MEX-function\n");
}

以下是它的工作原理:
>> mex -largeArrayDims testDLL.cpp

>> testDLL
DLL_PROCESS_ATTACH: hModule=0xa0980000
Inside MEX-function

>> testDLL
Inside MEX-function

>> clear testDLL
DLL_PROCESS_DETACH: hModule=0xa0980000

对于Linux,您可以使用全局/静态C++对象来实现类似的功能。请参见此链接:https://dev59.com/x2cs5IYBdhLWcg3w8oXK - Amro
很好的例子,现在已经从我的回答中链接。+1 - chappjc
这是一个很好的答案,谢谢!我在使用Linux,但是我可以像Amro指出的那样实现一个带有静态构造函数的C++类。同时,非常感谢chappj提供的详细信息和示例。我不知道为什么系统不允许我选择两个正确答案。 - panos
@panos 没问题,Amro 是真正的 MEX 专家。虽然感谢你的点赞。无论如何,如果你删除 #include <windows.h> 并将 NULL 替换为 (void*),我的示例应该可以在 Linux 上工作,尽管你在 MATLAB 中注册的 mexAtExit 函数不像 DllMain 或静态类构造函数和析构函数那样灵活。 - chappjc
我应该说,总的来说,你应该非常小心地将什么放入你的DllMain函数中:https://www.google.com/search?q=DllMain+site:blogs.msdn.com/b/oldnewthing - Amro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接