MATLAB.MexTools 6.2.0

dotnet add package MATLAB.MexTools --version 6.2.0
NuGet\Install-Package MATLAB.MexTools -Version 6.2.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MATLAB.MexTools" Version="6.2.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add MATLAB.MexTools --version 6.2.0
#r "nuget: MATLAB.MexTools, 6.2.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// 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 Compatible and additional computed target framework versions.
native native is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
6.2.0 149 4/13/2024
6.1.1 276 11/30/2023
6.1.0 79 11/30/2023
6.0.0 215 9/1/2023
5.0.0 271 8/21/2023
4.0.0 304 8/4/2023
3.0.0 191 7/15/2023
2.0.1 276 3/12/2023
2.0.0 238 3/5/2023
1.2.3 331 12/17/2022
1.2.2 375 9/17/2022
1.2.1 364 8/30/2022
1.1.0 353 8/22/2022
1.0.3 383 8/12/2022
1.0.2 371 8/10/2022
1.0.1 403 8/10/2022
1.0.0 389 8/10/2022

万能转码标量转换检查空数组,异常类型改为 enum class