从 C/C++ 调用 MATLAB Compiler SDK API 函数
共享库函数
MATLAB® Compiler SDK™ 生成的 C 或 C++共享库至少包含七个函数。有三个生成的函数来管理库的初始化和终止,一个用于打印输出和错误消息,以及两个为库中包含的每个 MATLAB 函数生成的函数。
要生成本节描述的函数,首先将文件夹 复制到当前工作目录。matlabroot\extern\examples\compilersdk\c_cpp\triangle
按照以下过程创建一个名为 libtriangle 的共享库,其中包含函数 sierpinski.m使用 MATLAB 代码创建 C 共享库,生成 C++ mwArray API 共享库并编译 C++ 应用程序,或者生成 C++ MATLAB Data API 共享库并编译 C++ 应用程序,具体取决于所需的目标应用程序。
应用程序类型
创建共享库后,执行与目标语言相对应的 mbuild 命令。此命令使用您的 C/C++ 编译器编译代码并将驱动程序代码与 MATLAB Compiler SDK 生成的 C/C++ 共享库链接。
对于 C 应用程序,使用 mbuild triangle.c libtriangle.lib。
对于 C++ mwArray API 应用程序,请使用 mbuild triangle_mwarray.cpp libtriangle.lib
对于 C++ MATLAB Data API 应用程序,使用 mbuild triangle_mda.cpp
注意
.lib 扩展名适用于 Windows®。在 Mac 上,文件扩展名是 .dylib,而在 UNIX® 上则是 .so。
此命令假定 C/C++ 共享库、驱动代码和相应的头文件位于当前工作文件夹中。
这些命令创建一个以所选源代码文件命名的主程序,该程序调用 libtriangle 共享库中的函数。该库导出一个函数(包含在 sierpinski.m 中),该函数使用简单的迭代算法来生成称为谢尔宾斯基三角形的分形。主程序可以选择采用单个数字参量来指定用于生成分形的点数。例如,triangle 8000 生成一个包含 8,000 个点的图。

调用共享库的程序的结构
所有调用 MATLAB Compiler SDK 生成的共享库的程序都具有大致相同的结构。
声明变量并处理/验证输入参量。
调用 MATLAB Runtime 初始化函数并测试是否成功。此函数设置全局 MATLAB Runtime 状态并允许构造 MATLAB Runtime 实例。
对于 C 或 C++
mwArrayAPI 应用程序,使用mclInitializeApplication。对于 C++ MATLAB Data API 应用程序,使用
matlab::cpplib::initMATLABApplication,它可以选择采用运行时选项向量,例如-nojvm和-logfile。返回值是指向封装应用程序状态的MATLABApplication对象的指针。
避免在调用初始化函数之前从应用程序发出
cd命令。这样做可能会导致 MATLAB Runtime 初始化失败。对于 C++ MATLAB Data API 应用程序,初始化
matlab::data::ArrayFactory,使用它来生成传递给函数调用matlab::data::Array对象。(可选)创建运行循环,将主函数的逻辑与主函数分离。运行循环函数为在共享库中封装 MATLAB 代码执行提供了一种方便的跨平台机制。在 macOS 上,这可以处理特定的线程问题并满足 Cocoa API 的要求。
对于 C 或 C++
mwArrayAPI 应用程序,使用mclRunMain。小心
如果您的应用程序自带完整的图形环境,请不要在 macOS 上使用
mclRunMain。对于 C++ MATLAB Data API 应用程序,使用
matlab::cpplib::runMain。
为每个库调用一次库初始化函数,以创建该库所需的 MATLAB Runtime 实例。该函数执行库本地初始化。它解压可部署代码存档并启动一个 MATLAB Runtime 实例,其中包含执行该存档中的代码所需的信息。
对于 C 或 C++
mwArrayAPI 应用程序,使用<library>Initialize[WithHandlers]。对于 C++ MATLAB Data API 应用程序,使用
matlab::cpplib::initMATLABLibrary或matlab::cpplib::initMATLABLibraryAsync。要调用初始化库中的函数,请在
feval返回的fevalAsync上调用unique_ptr或initMATLABLibrary。每个版本都有几个重载版本。它们都将 MATLAB 函数的名称作为第一个参数。但是,它们在接受和返回单个matlab::data::Array对象、matlab::data::Array或本机类型方面有所不同。返回本机类型的形式必须将该类型作为模板参数。
在程序主体中调用库中的函数并处理结果。
(可选)如果应用程序显示 MATLAB 图形窗口,请调用图形处理函数以使部署的应用程序显示图形事件。
对于 C 或 C++
mwArrayAPI 应用程序,使用mclWaitForFiguresToDie。对于 C++ MATLAB Data API 应用程序,使用
matlab::cpplib::MATLABLibrary::waitForFiguresToClose。
对于 C 或 C++
mwArrayAPI 应用程序,为每个库调用一次库终止函数<library>Terminate以销毁与该库关联的 MATLAB Runtime 实例。一旦库终止,该库导出的函数就不能再在应用程序中调用。调用 MATLAB Runtime 终止函数来释放与全局 MATLAB Runtime 状态相关的资源。一旦调用此函数,就不能再调用应用程序中的共享库。
对于 C 或 C++
mwArrayAPI 应用程序,请调用mclTerminateApplication。对于 C++ MATLAB Data API 应用程序,在由
matlab::cpplib::initMATLABApplication返回的return()对象上调用MATLABApplication函数或允许它超出范围。直到在其下创建的所有库都终止或超出范围时,它才会终止。
清理变量,关闭文件等,然后退出。
库初始化和终止函数
库初始化和终止函数分别创建和销毁共享库所需的 MATLAB Runtime 实例。在调用共享库中的任何其他函数之前,您必须调用初始化函数,并且在完成对共享库的调用之后,您应该调用终止函数,否则可能会有内存泄漏的风险。
对于使用 mwArray API 的 C 共享库和 C++ 共享库,初始化函数 <library>Initialize[WithHandlers] 有多种形式,终止函数 <library>Terminate 有一种形式。您生成的 C/C++ 共享库的名称用作函数名称的一部分。初始化函数的最简单形式不需要任何参量;很可能,这是您的应用程序将调用的版本。在这个示例中,这种形式的初始化函数被称为 libtriangleInitialize。
bool libtriangleInitialize(void)
此函数使用默认打印和错误处理程序以及编译过程中生成的其他信息创建 MATLAB Runtime 实例。
但是,如果您想要更好地控制如何处理打印输出和错误消息,请调用该函数的第二种形式,它接受两个参量。在这个示例中,这种形式的初始化函数被称为 libtriangleInitializeWithHandlers。
bool libtriangleInitializeWithKey(
const char* session_key
)
通过调用此函数语法,您可以提供由 MATLAB Runtime 调用的打印和错误处理例程的自己的版本。这些例程都具有相同的签名(有关完整详细信息,请参阅打印和错误处理函数)。通过覆盖默认值,您可以控制输出的显示方式,例如,是否将输出放入日志文件。
注意
在调用任一形式的库初始化例程之前,必须首先调用 mclInitializeApplication 来设置全局 MATLAB Runtime 状态。
如果您已经使用 mcc -k 选项通过 AES 加密密钥编译了 C++ 共享库,则可以使用名为 libtriangleInitializeWithKey 的初始化函数的另一种形式。此语法允许您在 C++ 应用程序中运行时提供解密密钥,而不是使用 MEX 加载程序。
bool libtriangleInitializeWithHandlers(
mclOutputHandlerFcn error_handler,
mclOutputHandlerFcn print_handler
)
您还可以使用语法 libtriangleInitializeWithHandlersAndKey 指定错误处理程序、打印处理程序和解密密钥。
在 Microsoft® Windows 平台上,MATLAB Compiler SDK 生成一个额外的初始化函数:标准 Microsoft DLL 初始化函数 DllMain。
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason,
void *pv)
生成的 DllMain 执行一项非常重要的服务;它定位共享库在磁盘上的存储目录。此信息用于查找可部署存档,如果没有该存档,应用程序将无法运行。如果您修改生成的 DllMain(不推荐),请确保保留其这部分功能。
注意
在 Windows 上,如果您想让您的共享库调用 MATLAB 共享库,则在 <libname>Initialize, <libname>Terminate 调用期间不能从您的共享库调用 MATLAB 库初始化或终止函数(即 DllMain(DLL_ATTACH_PROCESS))。无论中间共享库是隐式还是显式加载,这都适用。在 DllMain() 之后进行调用。
库的终止很简单。
void libtriangleTerminate(void)
在调用 mclTerminateApplication 之前调用此函数(每个库调用一次)。
打印和错误处理函数
默认情况下,MATLAB Compiler SDK 生成的应用程序和共享库将打印输出发送到标准输出,将错误消息发送到标准错误。MATLAB MATLAB Compiler SDK 生成一个默认打印处理程序和一个默认错误处理程序来实现此策略。如果您想改变这种行为,您必须编写自己的错误和打印处理程序并将它们传递给适当的生成的初始化函数。
您可以替换这两个函数中的一个、两个或者都不替换。MATLAB Runtime 通过打印处理程序发送所有常规输出,通过错误处理程序发送所有错误输出。因此,如果您重新定义这两个函数中的任何一个,MATLAB Runtime 将对所有属于其调用该处理程序的类的输出使用该函数的版本。
默认打印处理程序采用以下形式。
static int mclDefaultPrintHandler(const char *s)
实现很简单;它接受一个字符串,将其打印在标准输出上,并返回打印的字符数。如果您覆盖或替换此函数,您的版本还必须采用字符串并返回“已处理”的字符数。当执行的 MATLAB 文件请求打印输出时,MATLAB Runtime 会调用打印处理程序(例如通过 MATLAB 函数 disp。打印处理程序不会以回车符或换行符来终止输出。
默认错误处理程序与打印处理程序具有相同的形式。
static int mclDefaultErrorHandler(const char *s)
但是,打印处理程序的默认实现略有不同。它将输出发送到标准错误输出流,但如果字符串不是以回车符结尾,错误处理程序就会添加一个。如果您用自己的错误处理程序替换默认错误处理程序,则也应该执行此检查,否则 MATLAB 运行时打印的某些错误消息将无法正确格式化。
有关在应用程序中使用自定义打印和错误处理函数的示例,请参阅位于 的文件。matlabroot\extern\examples\compilersdk\c_cpp\catcherror
小心
错误处理程序不处理实际的错误,而是处理在 MATLAB Runtime 捕获并处理错误后生成的消息。您不能使用此函数来修改 MATLAB Runtime 的错误处理行为 - 如果您想控制 MATLAB Compiler SDK 生成的应用程序如何响应错误情况,请在 MATLAB 文件中使用 try 和 catch 语句。
注意
如果您提供 mclDefaultPrintHandler 或 mclDefaultErrorHandler 的备用 C++ 实现,则函数必须声明 extern "C"。例如:
extern "C" int myPrintHandler(const char *s);
从 MATLAB 文件生成的函数
对于 MATLAB Compiler SDK 命令行上指定的每个 MATLAB 文件,产品都会生成两个函数,即 mlx 函数和 mlf 函数。每个生成的函数都执行相同的操作(调用您的 MATLAB 文件函数)。这两个函数有不同的名称并且呈现不同的接口。每个函数的名称均基于 MATLAB 文件中第一个函数的名称(本例中为 sierpinski);每个函数都以不同的三个字母的前缀开头。
注意
对于 C 共享库,MATLAB Compiler SDK 生成 mlx 和 mlf 函数,如本节所述。对于 C++ 共享库,该产品生成 mlx 函数的方式与生成 C 共享库的方式相同。但是,该产品会生成修改后的 mlf 函数,它具有以下区别:
函数名前的
mlf被删除以保持与 R13 的兼容性。该函数的参量是
mwArray而不是mxArray。
mlx 接口函数
以前缀 mlx 开头的函数采用与 MATLAB MEX 函数相同类型和数量的参量。(有关 MEX 函数的更多详细信息,请参阅外部接口文档。)第一个参量 nlhs 是输出参量的数量,第二个参量 plhs 是一个指向数组的指针,函数将用所请求的返回值数量来填充该数组。(这些参量名称中的“ lhs ”是“左侧”的缩写 - MATLAB 表达式中的输出变量是赋值运算符左侧的变量。)第三和第四个参数是输入的数量和包含输入变量的数组。
void mlxSierpinski(int nlhs, mxArray *plhs[], int nrhs,
mxArray *prhs[])mlf 接口函数
生成的第二个函数以前缀 mlf 开头。此函数期望其输入和输出参量作为单独的变量传递,而不是打包到数组中。如果该函数能够产生一个或多个输出,则第一个参量是调用者请求的输出数量。
void mlfSierpinski(int nargout, mxArray** x, mxArray** y,
mxArray* iterations, mxArray* draw)
在这两种情况下,生成的函数都会为其返回值分配内存。如果在处理完输出变量后不删除此内存(通过 mxDestroyArray),则程序将会泄漏内存。
您的程序可以调用其中更方便的任何一个函数,因为它们都以相同的方式调用您的 MATLAB 文件函数。大多数程序可能会调用该函数的 mlf 形式,以避免管理 mlx 形式所需的额外数组。示例程序 triangle.c 调用 mlfSierpinski。
mlfSierpinski(2, &x, &y, iterations, draw);
在此调用中,调用者请求两个输出参量,x 和 y,并提供两个输入,iterations 和 draw。
如果传递给 mlf 函数的输出变量不是 NULL,则 mlf 函数将尝试使用 mxDestroyArray 释放它们。这意味着您可以在连续调用 mlf 函数时重用输出变量,而不必担心内存泄漏。它还意味着您必须为所有输出变量传递 NULL 或有效的 MATLAB 数组,否则您的程序将失败,因为内存管理器无法区分未初始化(无效)数组指针和有效数组。它将尝试释放一个非 NULL 的指针 - 释放无效的指针通常会导致分段错误或类似的致命错误。
在 MATLAB 函数接口中使用 varargin 和 varargout
如果您的 MATLAB 函数接口使用 varargin 或 varargout,则必须将它们作为元胞数组传递。例如,如果您有 Nvarargin,则需要创建一个大小为 1-by-N 的元胞数组。类似地,varargout 作为一个元胞数组返回。varargout 的长度等于函数调用中指定的返回值的数量减去传递的实际变量的数量。与 MATLAB 软件一样,表示 varargout 元胞数组必须是最后一个返回变量(第一个输入变量之前的变量),而表示 varargin s 的元胞数组必须是函数调用的最后一个形式参数。
有关创建元胞数组的信息,请参阅外部接口文档中的 C MEX 函数接口。
例如,考虑这个 MATLAB 文件接口:
[a,b,varargout] = myfun(x,y,z,varargin)
对应的 C 接口是
void mlfMyfun(int numOfRetVars, mxArray **a, mxArray **b,
mxArray **varargout, mxArray *x, mxArray *y,
mxArray *z, mxArray *varargin)
在此示例中,varargout 中的元素数量为 (numOfRetVars - 2),其中 2 表示返回的两个变量 a 和 b。varargin 和 varargout 都是单行、多列元胞数组。
小心
C++ 共享库接口不支持具有零 (0) 个输入参量的 varargin。使用空的 mwArray 调用您的程序会导致打包库收到一个带有 nargin = 1 空数组。C 共享库接口允许您调用 mlfFOO(NULL)(打包的 MATLAB 代码将其解释为 nargin=0)。但是,使用 C++ 共享库接口调用 FOO((mwArray)NULL) 会导致打包的 MATLAB 代码将空数组视为第一个输入并解释 nargin=1。
例如,将一些 MATLAB 代码打包为 C++ 共享库,使用 varargin 作为 MATLAB 函数的输入参量列表。让 MATLAB 代码显示变量 nargin。使用函数 调用该库但无法打包,并产生以下错误消息:FOO()
... 'FOO' : function does not take 0 arguments mwArray junk;
FOO(junk);
FOO((mwArray)NULL);nargin=1。在 MATLAB 中,FOO() 是 nargin=0 而 FOO([]) 是 nargin=1。 使用 varargin 和 varargoutMATLAB 函数的 C++ 接口. 即使 MATLAB 函数使用 mlx 或 varargin 其 C++ varargout 接口也不会改变。但是,如果 MATLAB 函数使用 varargin 或 varargout,则 C++ 函数接口(第二组函数)会发生变化。
例如,查看使用 varargin 或 varargout 的各种 MATLAB 函数签名的生成代码。
注意
为简单起见,以下示例仅显示生成的 C++ 函数签名的相关部分。
function varargout = foo(varargin)
函数 varargout = foo(i1,i2,varargin)
使用共享库时检索 MATLAB Runtime 状态信息
使用共享库时,您可以调用函数从 MATLAB Runtime 状态中检索特定信息。有关详细信息,请参阅设置和检索共享库的 MATLAB Runtime 数据。