MATLAB.MexTools
6.2.0
See the version list below for details.
dotnet add package MATLAB.MexTools --version 6.2.0
NuGet\Install-Package MATLAB.MexTools -Version 6.2.0
<PackageReference Include="MATLAB.MexTools" Version="6.2.0" />
paket add MATLAB.MexTools --version 6.2.0
#r "nuget: MATLAB.MexTools, 6.2.0"
// Install MATLAB.MexTools as a Cake Addin #addin nuget:?package=MATLAB.MexTools&version=6.2.0 // Install MATLAB.MexTools as a Cake Tool #tool nuget:?package=MATLAB.MexTools&version=6.2.0
本项目发布在 NuGet MATLAB.MexTools,GitHub上只有标签
使用本工具快速生成 MATLAB C++ MEX 数据API文件函数。MATLAB自带的 extern include 将函数定义在了头文件中,这导致在使用多个CPP文件实现功能时会出现链接时函数重定义问题。本工具则不存在此问题。
Mex实现
MATLAB C++ MEX 数据API文件函数本质上就是个实现了一套MEX标准接口的动态链接库,扩展名mexw64。你只需要在 Visual Studio 中新建一个C++动态链接库项目,安装此NuGet包,将输出扩展名改为mexw64,包含<Mex实现.h>
,然后在自己的项目中定义函数对象,例:
using namespace matlab::mex;
struct MexFunction :public Function //必须命名为MexFunction,public继承Function
{
MexFunction(); //此方法可选,用于初始化,不能有任何参数输入
void operator()(ArgumentList& outputs, ArgumentList& inputs)override; //必须定义此方法
virtual ~MexFunction(); //此方法可选,用于 clear mex 时释放资源,必须虚析构
};
Function* 创建Mex函数()
{
return new MexFunction();
}
void 销毁Mex函数(Function* 函数指针)
{
delete 函数指针;
}
当MATLAB初次调用MEX文件函数时,会先调用创建Mex函数()
以取得matlab::mex::Function
指针,然后调用其operator()
操作符,输入参数并获取输出。执行clear mex
命令时,则会delete
此指针,级联调用用户定义的虚析构函数(如果有)。总之,MEX文件函数实际上是一个具有初始化、调用、析构生命周期的C++类型,用户应当分别在初始化和析构阶段定义资源获取和释放操作,并在操作符中定义函数调用时的行为,根据输入参数产生输出。这样,即可编译出符合MEX标准、可被MATLAB直接调用的 C++ MEX 文件函数。此外,操作MATLAB数组通常还需要包含<Mex类型.h>
。
埃博拉酱的MATLAB扩展项目就使用了本库参与的MEX文件函数,你可以查看此项目的C++代码,进一步了解如何使用本库编写MEX文件函数。
Mex工具
为了进一步简化MEX编程工作流,<Mex工具.h>
还提供了一系列MEX编程常用的工具函数。
万能转码
MATLAB输入给C++的是Array类型的数组对象,我们常常需要将它转换为标量数值,或者拷贝到C++缓冲区,或者转换为UTF8字符串(MATLAB字符串采用UTF16编码)。万能转码通过函数模板实现所有常见类型的一行转换,只需将转换目标类型设为模板参数,然后调用此函数模板,输入 MATLAB Array 对象即可。
反之,C++产生的标量值、缓冲区和字符串也常常需要转换回Array对象以返回给MATLAB。同样使用万能转码,将MATLAB数组类型作为模板参数,输入C++类型值,即可返回MATLAB数组类型。
某些转换可能还需要输入其它参数,详见函数签名。
自动析构表
C++类对象无法直接转换为MATLAB对象。我们通常将指针传给MATLAB,然后在MATLAB端调用针对此指针的操作接口。C++对象没有自动垃圾回收,必须手动删除对象。一般可以在MATLAB端对指针调用析构函数实现对象释放。
但是,用户可能会使用MATLABclear mex
命令直接卸载MEX文件函数,使得指针未被正确释放。而这些指针可能仍留在工作区中,MATLAB仍可能再次调用这些已变成无效的指针的方法。这些非法操作将导致整个MATLAB进程崩溃。为了避免用户误操作导致崩溃,建议使用自动析构表。
Mex工具维护一个内置的自动析构表。每当有C++对象指针在堆中被新建,并且需要回传给MATLAB前,将此指针登记在自动析构表中:
namespace Mex工具
{
//将对象指针加入自动析构表。clear mex 时此指针将被自动delete。只能对new创建的对象指针使用此方法。
template<typename T>
inline void 自动析构(T* 对象指针)noexcept;
//将对象指针加入自动析构表。clear mex 时此对象将被自动析构。使用指定的删除器。
template<typename T>
inline void 自动析构(T* 对象指针, void(*删除器)(T*))noexcept;
}
可选同时提供特殊的删除器函数。登记在自动析构表中的对象指针,会在clear mex
时自动析构,释放资源。
另一个问题是,析构后变为无效的对象指针可能仍然存在于MATLAB工作区中。这些无效对象一旦被调用了类方法,就可能导致崩溃。如果对象方法中使用了某些被标记为noexcept
的库函数,这种崩溃异常是无法捕获的。因此,只能在执行具体功能前必须对对象指针的有效性进行检查:
namespace Mex工具
{
//指示此对象已被手动析构,可从自动析构表中移除。
inline void 手动析构(void* 对象指针)noexcept;
//检查对象指针是否存在于自动析构表中。如不存在,此指针可能是无效的,或者创建时未加入自动析构表。
inline bool 对象存在(void* 对象指针)noexcept;
}
如果用户手动析构对象,必须在析构函数执行前调用手动析构
函数,将对象指针从自动析构表中移除(此函数本身不执行析构),否则clear mex
时会发生重复析构错误。用户调用对象方法执行前,必须用对象存在
函数检查对象指针是否存在于自动析构表中。由于所有回传给MATLAB端的对象指针都应当加入自动析构表,所以如果发现指针不存在于自动析构表中,说明指针是无效的,应当拒绝执行对象方法,返回异常信息。
除此之外
<Mex工具.h>
还公开接口:
#include<Mex类型.h>
#include<array>
using namespace matlab::data;
using namespace matlab::mex;
namespace Mex工具
{
enum Mex异常;
//这里使用static而不是extern,因为从其它编译单元链接的变量不一定能在DllMain阶段完成初始化,会造成意外错误。
static ArrayFactory 数组工厂;
/*
* 根据动态类型选择类模板并返回成员
* MATLAB数组使用一个枚举值指示数组的动态类型。当你试图编写一个泛型函数时,一般来说需要用一个冗长的switch语句将其正确转换为C++静态类型,然后调用对应的函数模板。本函数封装此过程,根据MATLAB动态类型,实例化对应的模板类,取其返回成员。调用方可按需求设计一个模板类来包装类型特定的方法。本文件中的万能转码函数就用此方法实现标量转换,实现方法亦可作为参考示例。
* @param 模板,必须至少接受一个类型参数作为第一个模板参数,至少包含一个名为“返回”的静态成员,此成员的类型必须与运行时输入的MATLAB类型(即第一个模板参数)无关;如果是静态函数,则函数的所有输入参数和返回值类型必须与运行时输入的MATLAB类型无关。模板的第一个参数应当为所有MATLAB对应类型提供“返回”静态成员,还应当为nullptr_t类型也提供“返回”静态成员——这用于对不支持的MATLAB类型进行异常处理。例如,你可以为nullptr_t类型特化的返回成员提供一个异常输出。
* @param Ts,将要传递给模板的后续模板参数。如果你提供的模板不具有或具有可选的后续模板参数,则此处的后续模板参数也是可选的。
* @param 类型,运行时得到的MATLAB动态类型枚举
* @return 返回模板类名为“返回”的成员
*/
template<template <typename T, typename...Ts>typename 模板, typename...Ts>
constexpr auto 动态类型选择模板(ArrayType 类型);
//此using在将动态类型枚举ArrayType转为静态类型
template<ArrayType T>
using 动态类型转静态 = 内部::动态类型转静态<T>::类型;
constexpr uint8_t 类型总数 = 32;
//此数组存储了每种动态类型枚举对应的静态类型的尺寸。使用方法示例:类型尺寸[(int)inputs[1].getType()]
constexpr std::array<uint8_t, 类型总数>类型尺寸 = 内部::类型尺寸<std::make_integer_sequence<int, 类型总数>>;
//增强功能,可以使用如下三个宏定义在一个MEX文件函数中定义多个API
#define API声明(函数名) void 函数名(ArgumentList& outputs,ArgumentList& inputs)
#define API索引 constexpr void (*(API[]))(ArgumentList&, ArgumentList&) =
#define API调用 const uint8_t 选项=万能转码<uint8_t>(std::move(inputs[0]));if(选项<std::extent_v<decltype(API)>)API[选项](outputs, inputs);else throw 不支持的API;
/*
出错时,将后续返回值设为空数组
为了将C++异常传递给MATLAB,我们通常需要将MEX文件函数的第一个返回值保留作为错误代码
*/
void 异常输出补全(ArgumentList& outputs);
/*
使用动态类型创建一个动态类型缓冲,执行内存拷贝然后打包为 MATLAB Array
泛型编程的一个常见情境,就是我们不关心用户提供的运行时数据类型,只是单纯进行内存拷贝。但是MATLAB官方提供的 MEX API 不允许动态类型的内存拷贝,因此这里提供一个动态类型缓冲区,可以接受一个运行时类型枚举,创建一个动态类型缓冲,完成数据拷贝后再打包成无类型的 MATLAB Array。
*/
struct 动态类型缓冲
{
void* const 指针;
const size_t 字节数;
/*
创建动态类型缓冲。
语法:Mex工具::动态类型缓冲::创建(类型,元素数)
# 参数
ArrayType 类型,动态类型枚举
size_t 元素数
# 返回值
std::unique_ptr<动态类型缓冲>,请通过指针操作对象,因为内部实现是被子类继承的,直接取值会导致对象被截断。
*/
static std::unique_ptr<动态类型缓冲>创建(ArrayType 类型, size_t 元素数);
virtual ~动态类型缓冲() {}
//打包后本对象变为不可用,所有数据封装在 MATLAB Array 中
virtual Array 打包(ArrayDimensions 各维尺寸)noexcept = 0;
virtual Array 打包()noexcept = 0;
};
}
使用示例
#include <Mex工具.h>
#include <Mex实现.h>
#include "Oir读入器.h" //假定此文件中定义了一个“Oir读入器”,现在要将它的指针传给MATLAB端使用
using namespace matlab::mex;
using namespace Mex工具;
//使用此函数将MATLAB端传来的uint64值转换为对象指针,并检查指针是否有效。
Oir读入器* 取指针(const Array& 指针标量)
{
Oir读入器* const 指针 = 万能转码<Oir读入器*>(指针标量);
if (对象存在(指针))
return 指针;
else
{
//处理指针无效的异常情况
}
}
//创建对象,返回指针。不提供模板参数时,万能转码默认将指针转换为 MATLAB uint64 标量,还要加入自动析构表。
API声明(Oir_CreateReader)
{
const String 文件路径 = 万能转码<String>(inputs[1]);
Oir读入器*const 对象指针 = new Oir读入器((LPCWSTR)文件路径.c_str());
outputs[1] = 万能转码(对象指针);
自动析构(对象指针);
}
//手动删除用户提供的对象指针。删除前必须先检查指针有效性。如果指针有效,还需从自动析构表中移除此指针。
API声明(Oir_DeleteReader)
{
Oir读入器* const 指针 = 万能转码<Oir读入器*>(inputs[1]);
if (手动析构(指针))
delete 指针;
}
//一个执行功能的对象方法示例。不提供模板参数时,万能转码默认将C++基本数值类型转换为等效的MATLAB标量类型。
API声明(Oir_SizeX)
{
outputs[1] = 万能转码(取指针(inputs[1])->SizeX());
}
struct MexFunction :public Function
{
void operator()(ArgumentList& outputs, ArgumentList& inputs)
{
API索引{
// OirReader
Oir_CreateReader,
Oir_DeleteReader,
Oir_SizeX,
//……在此列出所有API函数名称
};
try
{
API调用;
}
catch (const Image5D异常& 异常)
{
//处理已知异常
}
catch (...)
{
//处理未知异常
}
}
};
Function* 创建Mex函数()
{
return new MexFunction();//必须用new创建此对象指针,因为 clear mex 时将用delete析构
}
void 销毁Mex函数(Function* 函数指针)
{
delete 函数指针;
}
FAQ
某些计算机/编译器可能存在中文编码错误问题。你需要设置中文编码为UTF-8。对于 Windows 11,可以在任务栏搜索intl.cpl,转到【管理\非Unicode程序的语言\更改系统区域设置】,勾选【Beta版:使用 Unicode UTF-8 提供全球语言支持】。
Product | Versions Compatible and additional computed target framework versions. |
---|---|
native | native is compatible. |
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated | |
---|---|---|---|
7.0.2 | 103 | 10/25/2024 | |
7.0.1 | 118 | 8/29/2024 | |
7.0.0 | 88 | 7/29/2024 | |
6.2.0 | 224 | 4/13/2024 | |
6.1.1 | 329 | 11/30/2023 | |
6.1.0 | 131 | 11/30/2023 | |
6.0.0 | 268 | 9/1/2023 | |
5.0.0 | 346 | 8/21/2023 | |
4.0.0 | 381 | 8/4/2023 | |
3.0.0 | 231 | 7/15/2023 | |
2.0.1 | 315 | 3/12/2023 | |
2.0.0 | 277 | 3/5/2023 | |
1.2.3 | 373 | 12/17/2022 | |
1.2.2 | 447 | 9/17/2022 | |
1.2.1 | 412 | 8/30/2022 | |
1.1.0 | 387 | 8/22/2022 | |
1.0.3 | 464 | 8/12/2022 | |
1.0.2 | 405 | 8/10/2022 | |
1.0.1 | 438 | 8/10/2022 | |
1.0.0 | 421 | 8/10/2022 |
万能转码标量转换检查空数组,异常类型改为 enum class