通过 Matlab mex 函数调用持久化 C++ class 实例
Persist C++ class instance through Matlab mex function calls
目前正在想办法解决这个问题。
现在我正在尝试为 C++ 库创建 mex 函数,该库通过 Windows 10 PC 上的串行端口与微控制器通信,以便我可以在 matlab 中调用该库中的函数。我目前正在研究如何通过多个 matlab mexFunction 调用来保留我的 class 实例。
到目前为止,我唯一能想到的就是围绕 class 编写一个包装器,声明一个指向我的 class 实例的全局外部唯一指针,并将其包含在我的 mexFunction( ) 文件。
谁能告诉我这是否可行,如果可行,matlab/C++ 究竟如何处理 mexFunction 文件及其方法调用?我不确定 class 实例的范围。
一个具体的例子可能是...
如果我在 .cpp 文件中声明一个指向对象的外部唯一指针并将其包含在我的 mexFunction 文件中,会发生什么情况?在调用多个不同的 mexFunctions 来操纵该对象的 matlab 脚本中,指针是否会保持在范围内?
如果我需要重新表述问题或提供更多信息,请告诉我。
是的,你可以做到这一点。如果 MEX 文件都 link 到同一个共享库 (DLL),那么它们都可以访问其中定义的全局变量。您需要在共享库中定义您的全局对象,而不是在 MEX 文件之一中。
MEX 文件在第一次执行后会一直加载到内存中,直到您调用 clear functions
(或 clear all
)。当从内存中清除共享对象时,全局对象将被破坏。为防止意外清除您的状态,您可以使用 mexLock
锁定内存中的 MEX 文件之一。我建议使用一个“初始化”MEX 文件,它构造对象并将自身锁定在内存中。使用特殊参数,您可以使其解锁并销毁对象。
这是一个例子:
libXYZ.dylib
/ libXYZ.so
/ XYZ.dll
-- 一个共享库,包含一个std::shared_ptr<XYZ>
.
XYZ_set.mex...
-- 初始化 XYZ
对象并将自身锁定在内存中的 MEX 文件。 libXYZ
共享库的链接。
XYZ_get.mex...
-- 另一个 MEX 文件 link 发送到 libXYZ
共享库并访问另一个创建的 XYZ
对象MEX 文件。
XYZ_lib.h
:
#include <memory>
#include <iostream>
struct XYZ {
XYZ(double a);
~XYZ();
double get();
private:
double a_;
};
extern std::unique_ptr<XYZ> XYZ_data;
XYZ_lib.cpp
:
#include "XYZ_lib.h"
std::unique_ptr<XYZ> XYZ_data;
XYZ::XYZ(double a) : a_(a) {
std::cout << "Constructing XYZ with " << a_ << '\n';
}
XYZ::~XYZ() {
std::cout << "Destructing XYZ, value was " << a_ << '\n';
}
double XYZ::get() {
return a_;
}
XYZ_set.cpp
:
#include "XYZ_lib.h"
#include <mex.h>
/// \brief An output stream buffer for MEX-files.
///
/// Creating an object of this class replaces the stream buffer in `std::cout` with the newly
/// created object. This buffer will be used as long as the object exists. When the object
/// is destroyed (which happens automatically when it goes out of scope), the original
/// stream buffer is replaced.
///
/// Create an object of this class at the beginning of any MEX-file that uses `std::cout` to
/// print information to the *MATLAB* terminal.
class streambuf : public std::streambuf {
public:
streambuf() {
stdoutbuf = std::cout.rdbuf( this );
}
~streambuf() {
std::cout.rdbuf( stdoutbuf );
}
protected:
virtual std::streamsize xsputn( const char* s, std::streamsize n ) override {
mexPrintf( "%.*s", n, s );
return n;
}
virtual int overflow( int c = EOF ) override {
if( c != EOF ) {
mexPrintf( "%.1s", &c );
}
return 1;
}
private:
std::streambuf* stdoutbuf;
};
void mexFunction( int, mxArray*[], int nrhs, const mxArray* prhs[] ) {
streambuf buf; // Allows std::cout to work in MEX-files
// Always do lots of testing for correct input in MEX-files!
if (nrhs!=1) {
mexErrMsgTxt("Requires 1 input");
}
if (mxIsChar(prhs[0])) {
// Assume it's "-unlock" or something like that. Unlock MEX-file
mexUnlock();
std::cout << "XYZ can now be cleared from memory\n";
} else {
// Here we create new data
if (!mxIsDouble(prhs[0]) || mxIsEmpty(prhs[0])) {
mexErrMsgTxt("Expected double input");
}
double a = *mxGetPr(prhs[0]);
XYZ_data = std::unique_ptr<XYZ>(new XYZ(a));
// If the MEX-file is not locked, lock it
if (!mexIsLocked()) {
mexLock();
}
}
}
(抱歉这里的streambuf
class,是噪音,但我想用它,这样你就可以看到共享库中的构造函数和析构函数被调用了。)
XYZ_get.cpp
:
#include "XYZ_lib.h"
#include <mex.h>
void mexFunction( int, mxArray* plhs[], int, const mxArray* [] ) {
if (XYZ_data) {
plhs[0] = mxCreateDoubleScalar(XYZ_data->get());
} else {
mexErrMsgTxt("XYZ not initialized!");
}
}
正在编译:
在 shell 中(我使用的是 MacOS,因此使用 dylib
扩展名,根据需要进行调整):
g++ -std=c++11 -Wall -fpic XYZ_lib.cpp -shared -o libXYZ.dylib
在 MATLAB 中:
mex XYZ_set.cpp libXYZ.dylib
mex XYZ_get.cpp libXYZ.dylib
运行:
>> XYZ_get
Error using XYZ_get
XYZ not initialized!
>> XYZ_set(4)
Constructing XYZ with 4
>> XYZ_set(6)
Constructing XYZ with 6
Destructing XYZ, value was 4
>> XYZ_get
ans =
6
>> clear all
>> XYZ_set -unlock
XYZ can now be cleared from memory
>> clear all
Destructing XYZ, value was 6
如您所见,XYZ_get
访问了 new
由 XYZ_set
编辑的对象中的值。 clear all
通常会清除内存中的所有内容,但此处会保留锁定的 MEX 文件。 XYZ_set -unlock
使用字符串参数调用它,这会导致它自行解锁。 clear all
现在也从内存中清除 MEX 文件,现在 XYZ
对象被销毁。
我需要在这里提一下,C++ 没有一致的 ABI,只有在使用相同的编译器编译共享库时才会加载这些 MEX 文件。
另一种通常更简单的方法是仅创建一个 MEX 文件(静态 link 使用您的 C++ 代码编辑)和一堆调用 MEX 文件的 M 文件。 M 文件提供了很好的界面(也可以进行输入检查),而 MEX 文件位于 private/
目录中,没有人可以弄乱它。 MEX 文件仍然可以执行锁定操作,因此它可以保留在每次调用中保留的对象。
目前正在想办法解决这个问题。
现在我正在尝试为 C++ 库创建 mex 函数,该库通过 Windows 10 PC 上的串行端口与微控制器通信,以便我可以在 matlab 中调用该库中的函数。我目前正在研究如何通过多个 matlab mexFunction 调用来保留我的 class 实例。
到目前为止,我唯一能想到的就是围绕 class 编写一个包装器,声明一个指向我的 class 实例的全局外部唯一指针,并将其包含在我的 mexFunction( ) 文件。
谁能告诉我这是否可行,如果可行,matlab/C++ 究竟如何处理 mexFunction 文件及其方法调用?我不确定 class 实例的范围。
一个具体的例子可能是...
如果我在 .cpp 文件中声明一个指向对象的外部唯一指针并将其包含在我的 mexFunction 文件中,会发生什么情况?在调用多个不同的 mexFunctions 来操纵该对象的 matlab 脚本中,指针是否会保持在范围内?
如果我需要重新表述问题或提供更多信息,请告诉我。
是的,你可以做到这一点。如果 MEX 文件都 link 到同一个共享库 (DLL),那么它们都可以访问其中定义的全局变量。您需要在共享库中定义您的全局对象,而不是在 MEX 文件之一中。
MEX 文件在第一次执行后会一直加载到内存中,直到您调用 clear functions
(或 clear all
)。当从内存中清除共享对象时,全局对象将被破坏。为防止意外清除您的状态,您可以使用 mexLock
锁定内存中的 MEX 文件之一。我建议使用一个“初始化”MEX 文件,它构造对象并将自身锁定在内存中。使用特殊参数,您可以使其解锁并销毁对象。
这是一个例子:
libXYZ.dylib
/libXYZ.so
/XYZ.dll
-- 一个共享库,包含一个std::shared_ptr<XYZ>
.XYZ_set.mex...
-- 初始化XYZ
对象并将自身锁定在内存中的 MEX 文件。libXYZ
共享库的链接。XYZ_get.mex...
-- 另一个 MEX 文件 link 发送到libXYZ
共享库并访问另一个创建的XYZ
对象MEX 文件。
XYZ_lib.h
:
#include <memory>
#include <iostream>
struct XYZ {
XYZ(double a);
~XYZ();
double get();
private:
double a_;
};
extern std::unique_ptr<XYZ> XYZ_data;
XYZ_lib.cpp
:
#include "XYZ_lib.h"
std::unique_ptr<XYZ> XYZ_data;
XYZ::XYZ(double a) : a_(a) {
std::cout << "Constructing XYZ with " << a_ << '\n';
}
XYZ::~XYZ() {
std::cout << "Destructing XYZ, value was " << a_ << '\n';
}
double XYZ::get() {
return a_;
}
XYZ_set.cpp
:
#include "XYZ_lib.h"
#include <mex.h>
/// \brief An output stream buffer for MEX-files.
///
/// Creating an object of this class replaces the stream buffer in `std::cout` with the newly
/// created object. This buffer will be used as long as the object exists. When the object
/// is destroyed (which happens automatically when it goes out of scope), the original
/// stream buffer is replaced.
///
/// Create an object of this class at the beginning of any MEX-file that uses `std::cout` to
/// print information to the *MATLAB* terminal.
class streambuf : public std::streambuf {
public:
streambuf() {
stdoutbuf = std::cout.rdbuf( this );
}
~streambuf() {
std::cout.rdbuf( stdoutbuf );
}
protected:
virtual std::streamsize xsputn( const char* s, std::streamsize n ) override {
mexPrintf( "%.*s", n, s );
return n;
}
virtual int overflow( int c = EOF ) override {
if( c != EOF ) {
mexPrintf( "%.1s", &c );
}
return 1;
}
private:
std::streambuf* stdoutbuf;
};
void mexFunction( int, mxArray*[], int nrhs, const mxArray* prhs[] ) {
streambuf buf; // Allows std::cout to work in MEX-files
// Always do lots of testing for correct input in MEX-files!
if (nrhs!=1) {
mexErrMsgTxt("Requires 1 input");
}
if (mxIsChar(prhs[0])) {
// Assume it's "-unlock" or something like that. Unlock MEX-file
mexUnlock();
std::cout << "XYZ can now be cleared from memory\n";
} else {
// Here we create new data
if (!mxIsDouble(prhs[0]) || mxIsEmpty(prhs[0])) {
mexErrMsgTxt("Expected double input");
}
double a = *mxGetPr(prhs[0]);
XYZ_data = std::unique_ptr<XYZ>(new XYZ(a));
// If the MEX-file is not locked, lock it
if (!mexIsLocked()) {
mexLock();
}
}
}
(抱歉这里的streambuf
class,是噪音,但我想用它,这样你就可以看到共享库中的构造函数和析构函数被调用了。)
XYZ_get.cpp
:
#include "XYZ_lib.h"
#include <mex.h>
void mexFunction( int, mxArray* plhs[], int, const mxArray* [] ) {
if (XYZ_data) {
plhs[0] = mxCreateDoubleScalar(XYZ_data->get());
} else {
mexErrMsgTxt("XYZ not initialized!");
}
}
正在编译:
在 shell 中(我使用的是 MacOS,因此使用 dylib
扩展名,根据需要进行调整):
g++ -std=c++11 -Wall -fpic XYZ_lib.cpp -shared -o libXYZ.dylib
在 MATLAB 中:
mex XYZ_set.cpp libXYZ.dylib
mex XYZ_get.cpp libXYZ.dylib
运行:
>> XYZ_get
Error using XYZ_get
XYZ not initialized!
>> XYZ_set(4)
Constructing XYZ with 4
>> XYZ_set(6)
Constructing XYZ with 6
Destructing XYZ, value was 4
>> XYZ_get
ans =
6
>> clear all
>> XYZ_set -unlock
XYZ can now be cleared from memory
>> clear all
Destructing XYZ, value was 6
如您所见,XYZ_get
访问了 new
由 XYZ_set
编辑的对象中的值。 clear all
通常会清除内存中的所有内容,但此处会保留锁定的 MEX 文件。 XYZ_set -unlock
使用字符串参数调用它,这会导致它自行解锁。 clear all
现在也从内存中清除 MEX 文件,现在 XYZ
对象被销毁。
我需要在这里提一下,C++ 没有一致的 ABI,只有在使用相同的编译器编译共享库时才会加载这些 MEX 文件。
另一种通常更简单的方法是仅创建一个 MEX 文件(静态 link 使用您的 C++ 代码编辑)和一堆调用 MEX 文件的 M 文件。 M 文件提供了很好的界面(也可以进行输入检查),而 MEX 文件位于 private/
目录中,没有人可以弄乱它。 MEX 文件仍然可以执行锁定操作,因此它可以保留在每次调用中保留的对象。