转自:matlab与C/C++混合编程——在Windows/Linux上调用Matlab编译的动态库文件_sinat_18131557的博客-CSDN博客
date | version | comments |
---|---|---|
2019/9/9 | V0.1 | Init |
2019/9/27 | V0.2 | 添加报错信息写入log的实现 |
文章目录
-
-
- MATLAB生成Dll文件调用
-
-
- 生成dll文件
- 调用dll文件
-
- MATLAB生成.so文件调用
-
-
- Linux安装Matlab
- 生成.so文件
- 调用.so在Linux实现matlab代码内容
- 添加调试的写入log文件的功能
-
-
主要是想使用MATLAB的m文件,生成可以使用C/C++调用的文件,两种方式
- 生成dll文件,在Windows下调用,使用VS构建调用的工程
- 生成so文件在Linux下调用,调用代码使用g++编译
MATLAB生成Dll文件调用
在Windows上安装Matlab过程略,在Matlab中创建函数:
function myplot(y,mytitle)
%UNTITLED 此处显示有关此函数的摘要plot(y);title(mytitle);saveas(gcf,'myplot.jpg');
end
这样写的好处在于传递了数组类型,还传递了字符串类型,使用了绘图功能,能全面看下变量怎么传递的。
然后在APP栏目下找到Library Compiler进去,或者在命令行中输入deploytool1:
得到如下界面:
上方选择C/C++ Shared Library,EXPORTED FUNCTIONS的加号点一下,选中要输出的文件,Library Name可以更加需要选择改或者不改,另外这里只有一个文件需要转化,如果这个文件中调用了另外一个自己写的函数中的内容,需要把这个函数放在Files required for your library to run中,当右边的Package变为绿色就可以点击Package,选择保存目录,等待一会让就好了。完成后会得到三个文件,选择for_redistribution_files_only进入就有了如下文件:
打开vs创建一个新的C++的控制台工程。第一步将解算方案平台改为x64,不然后面可能会报错:
然后配置2:
项目-> 项目属性-> VC++目录->包含目录添加…\MATLAB\R2016a\extern\include
库目录添加…C:\Program Files\MATLAB\R2016a\extern\lib\win64\microsoft
在链接器里面添加这几项(这里只用到了myplot.lib 和mclmcrrt.lib,资料上其他应用有用到其他几个):
myplot.lib (自己的lib文件)
mclmcr.lib (以下都是matlab的文件)
mclmcrrt.lib
libmx.lib
libmex.lib
libmat.lib
libeng.lib
把Matlab生成的.dll,.h,.lib文件复制到项目的源文件的目录下(放到存放cpp文件目录里面),在项目里包含.h文件。首先打开.h文件看下:
...
extern LIB_myplot_C_API
bool MW_CALL_CONV myplotInitialize(void);extern LIB_myplot_C_API
void MW_CALL_CONV myplotTerminate(void);extern LIB_myplot_C_API bool MW_CALL_CONV mlfMyplot(mxArray* y, mxArray* mytitle);
...
主要是这3个函数是需要使用的。
- myplotInitialize 初始化
- myplotTerminate 关闭函数
- mlfMyplot(mxArray* y, mxArray* mytitle) 运行函数
由于C/C++没有mxArray*这样的数据类型,需要进行数据转化,y是数组(一维矩阵),mytitle是个字符串 所以可以用这样的方式创建,(可参考3):
mxArray *dest_ptr =mxCreateDoubleMatrix(rows,cols, mxREAL);
mxArray* mytitle = mxCreateString(const char *str)
同时含有必要的初始化,调用代码如下:
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
//#include "stdafx.h"
#include "myplot.h"
#include "string.h"int main()
{// 初始化mclmcr,如果没有这两句可能会有访问错误的问题mclmcrInitialize();if (!mclInitializeApplication(NULL, 0)){return -1;}printf("mclmcr Initialized!\n");//初始化应用if (!myplotInitialize()){return -1;}printf("myplot Initialized!\n");// 初始化数据并做数据转化double data[5] = { 1,2,3,4,5 };mxArray *y = mxCreateDoubleMatrix(5, 1, mxREAL);memcpy((void*)mxGetPr(y), (void*)data, sizeof(data));const char *title = "mytile";mxArray* mytitle = mxCreateString(title);printf("data Initialized!\n");//调用主函数mlfMyplot(y, mytitle);// 关闭应用myplotTerminate();system("pause");return 0;
}
如果发生这样的异常:
- mclmcr一定要初始化
mclmcrInitialize();if (!mclInitializeApplication(NULL, 0)){return -1;}
- 按Ctrl+Alt+E将Win32 Exceptions的异常取消勾选。
如果没有异常,能在工程目录下看到myplot.jpg文件。
MATLAB生成.so文件调用
生成.so文件需要在Linux环境下安装MATLAB。
Matlab下载地址:https://ww2.mathworks.cn/downloads/web_downloads/?s_iid=hp_ff_t_downloads
在Linux下尽量选择MATLAB版本与MATLAB Runtime的版本一致,如都是2019a,不然可能在编译时候或者编译以后有libstdc++.so的版本错误问题,选择版本以后可以直接选择Linux系统进行下载,我使用的学校邮箱注册,可以直接使用学术license。下载完成,解压出来后,cd到文件夹下直接
sudo ./install
就可以像Windows上安装软件一样点下一步下一步了。
如果只需要跑代码,不用这个写/调试的机器,只需要安装Matlab Runtime就可以了。在官网下载,安装方式同Matlab一样,使用sudo ./install
命令就可以,安装完成以后,提示将几个变量添加到环境变量里面去:
我的是这样的:/usr/local/MATLAB/MATLAB_Runtime/v96/runtime/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/v96/bin/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/v96/sys/os/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/v96/extern/bin/glnxa64
,不同机器与版本,这个路径不一样,按照显示的添加就行。
使用terminal输入:
sudo gedit /etc/profile
输入密码后,在打开的文件最后添加:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/MATLAB/MATLAB_Runtime/v93/runtime/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/v93/bin/glnxa64:/usr/local/MATLAB/MATLAB_Runtime/v93/sys/os/glnxa64
由于libmyplot.h文件中还包含了一个mclmcrrt.h文件,使用g++编译时候,还需要把这个文件的路径添加到CPLUS_INCLUDE_PATH
中,所以还要打开/etc/profile
文件在最后添加一行:
export CPLUS_INCLUDE_PATH=/usr/local/MATLAB/MATLAB_Runtime/v96/extern/include:$CPLUS_INCLUDE_PATH
这个文件在…/extern/include里面,前面的路径是安装地址。
到这里,Linux安装完成~
生成.so的文件必须要为函数,不能是脚本,假设现在需要转化的函数为:
function myplot(y,mytitle)
%UNTITLED 此处显示有关此函数的摘要plot(y);title(mytitle);saveas(gcf,'myplot.jpg');
end
就是把输入的y通过plot绘图出来,并且,这个图的title是mytitle这个字符串。写成这样是为了说明2种数据类型的转化关系。
在matlab的APP栏中,找到Library Compiler进去,或者在命令行中输入:deploytool然后选择Library Compiler进入到如下界面:
上方选择C/C++ Shared Library,EXPORTED FUNCTIONS的加号点一下,选中要输出的文件,Library Name可以更加需要选择改或者不改,当右边的Package变为绿色就可以了。
点击Package。选择保存工程的文件夹,等待一会儿就好了。打开所在目录得到了:
打开for_redistribution_files_only就可以看到.h和.so文件了。
另外也可在matlab中使用mcc命令生成.so文件4。
mcc -W cpplib:myplot -T link:lib myplot.m -c
但是这样生成的代码函数名不一样,参数类型也不一样,需要自己研究研究。
同样地,写一个cpp文件:
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
//#include "myplot.h"
#include "string.h"int main()
{// 初始化mclmcr,如果没有这两句可能会有访问错误的问题mclmcrInitialize();if (!mclInitializeApplication(NULL, 0)){return -1;}printf("mclmcr Initialized!\n");//初始化应用if (!libmyplotInitialize()){return -1;}printf("myplot Initialized!\n");// 初始化数据并做数据转化double data[5] = { 1,2,3,4,5 };mxArray *y = mxCreateDoubleMatrix(5, 1, mxREAL);memcpy((void*)mxGetPr(y), (void*)data, sizeof(data));const char *title = "mytile";mxArray* mytitle = mxCreateString(title);printf("data Initialized!\n");//调用主函数mlfMyplot(y, mytitle);// 关闭应用libmyplotTerminate();return 0;
}
要注意初始化应用,关闭应用,主程序的函数名,在libmyplot.h文件中能找到对应的函数名字。
在打开一个terminal,输入使用g++编译的指令:
g++ sotest.cpp -o sotest -L. -lmyplot
g++
使用g++编译方式编译,sotest.cpp
编译的源文件为sotest.cpp,-o sotest
输出sotest文件作为可执行文件,-L. -lmyplot
指定需要链接的库,也就是当前文件目录下的libmyplot.so文件。按理说能成功,结果:
/usr/bin/ld: /tmp/ccXUI6xA.o: undefined reference to symbol 'mxCreateDoubleMatrix_800_proxy'
/usr/local/MATLAB/MATLAB_Runtime/v96/runtime/glnxa64/libmwmclmcrrt.so.9.6: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status
网上找资料定位到error adding symbols: DSO missing from command line
这个问题是libmwmclmcrrt.so.9.6文件没找到,可是已经添加了LD_LIBRARY_PATH,有点不明所以,既然你说你没找到,那就给你指定文件吧5:
g++ sotest.cpp -o sotest -L. -lmyplot -L/usr/local/MATLAB/R2019a/runtime/glnxa64/ -lmwmclmcrrt
运行成功,没有报错,但是也什么都没有输出,这个时候能看到多了一个sotest文件:
这时候运行sotest文件能得到结果:
如果libmyplotInitialize()
初始化失败的话,切换到sudo权限运行。
sudo -s
由于在Linux上跑,通常需要记录下出错的问题,把错误信息写到log文件中。写了一个写入log文件的函数,每一个月的信息存放一个文件,如果文件不存在,就创建一个:
#include "time.h"
#include <fstream>
#include <iostream>void WriteLog(const char* text)
{char LOG_FILE[1024]={0};// get the time nowtime_t now=time(0);tm *ltm = localtime(&now);snprintf(LOG_FILE,1024,"./log/log_%04d%02d.txt", ltm->tm_year+1900,ltm->tm_mon+1);// if LOG file not exist, then create it!fstream f1;f1.open(LOG_FILE,ios::in);if(!f1){f1.close();f1.open(LOG_FILE,ios::out);f1.close();}// write info in log file!FILE *fp;fp=fopen(LOG_FILE, "a+");if (fp != NULL){char ltime[1024] = { 0 };strftime(ltime,1024,"%Y-%m-%d %H:%M:%S ", ltm); fwrite(ltime, sizeof(char), strlen(ltime), fp);fwrite(text, sizeof(char), strlen(text), fp);fwrite("\r\n", sizeof(char), 2, fp);fclose(fp);printf("%s\n",text);}}
并且,有参数传进来的话,也希望把出错时候的参数保存下来,所以主函数需要接受参数:
int main(int argc, char **argv)
{...
}
argc表示参数的个数,argv表示参数,其中argv[0]是命令本身,从1开始表示参数。把参数信息整合出来:
void formatLogInfo(int argc, char **argv, char * info)
{for (int i=1;i<argc;i++){char tmp[1024]={0};snprintf(tmp,1024,"%s parameter[%d]=%s",info,i,argv[i]);snprintf(info,1024,"%s",tmp);}
}
所以最终的运行CPP文件:
#include "myplot.h"
#include "string.h"#include "time.h"
#include <fstream>
#include <iostream>using namespace std;void WriteLog(const char* text)
{char LOG_FILE[1024]={0};// get the time nowtime_t now=time(0);tm *ltm = localtime(&now);snprintf(LOG_FILE,1024,"./log/log_%04d%02d.txt", ltm->tm_year+1900,ltm->tm_mon+1);// if LOG file not exist, then create it!fstream f1;f1.open(LOG_FILE,ios::in);if(!f1){f1.close();f1.open(LOG_FILE,ios::out);f1.close();}// write info in log file!FILE *fp;fp=fopen(LOG_FILE, "a+");if (fp != NULL){char ltime[1024] = { 0 };strftime(ltime,1024,"%Y-%m-%d %H:%M:%S ", ltm); fwrite(ltime, sizeof(char), strlen(ltime), fp);fwrite(text, sizeof(char), strlen(text), fp);fwrite("\r\n", sizeof(char), 2, fp);fclose(fp);printf("%s\n",text);}}void formatLogInfo(int argc, char **argv, char * info)
{for (int i=1;i<argc;i++){char tmp[1024]={0};snprintf(tmp,1024,"%s parameter[%d]=%s",info,i,argv[i]);snprintf(info,1024,"%s",tmp);}
}int main(int argc, char **argv)
{// 初始化mclmcr,如果没有这两句可能会有访问错误的问题mclmcrInitialize();if (!mclInitializeApplication(NULL, 0)){char info[1024]="mclInitializeApplication failed!";formatLogInfo(argc,argv,info);WriteLog(info);return -1;}printf("mclmcr Initialized!\n");//初始化应用if (!libmyplotInitialize()){char info[1024]="libmyplotInitialize failed!";formatLogInfo(argc,argv,info);WriteLog(info);return -2;}printf("myplot Initialized!\n");// 初始化数据并做数据转化double data[5] = { 1,2,3,4,5 };mxArray *y = mxCreateDoubleMatrix(5, 1, mxREAL);memcpy((void*)mxGetPr(y), (void*)data, sizeof(data));const char *title = "mytile";mxArray* mytitle = mxCreateString(title);printf("data Initialized!\n");//调用主函数if(!mlfMyplot(y, mytitle)){char info[1024]="mlfMyplot function failed! Please check the parameter(s)!";formatLogInfo(argc,argv,info);WriteLog(info);return -3;}// 关闭应用libmyplotTerminate();return 0;
-
matlab函数编译dll,vs调用该dll的方法, https://blog.csdn.net/a15216111693/article/details/79232288 ↩︎
-
填坑VS2017与MATLAB2016b混合编程(生成dll方式), https://blog.csdn.net/qq_20515461/article/details/81229726 ↩︎
-
mxArray数据类型, https://blog.csdn.net/snowfoxmonitor/article/details/79121178 ↩︎
-
Linux下c++调用自己编写的matlab函数:通过mcc动态链接库.so实现, https://www.it610.com/article/5486435.htm ↩︎
-
error adding symbols: DSO missing from command line(在CMakeList中的解决方法), https://blog.csdn.net/lzRush/article/details/84579692 ↩︎