转自: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的m文件,生成可以使用C/C++调用的文件,两种方式
- 生成dll文件,在Windows下调用,使用VS构建调用的工程
- 生成so文件在Linux下调用,调用代码使用g++编译
MATLAB生成Dll文件调用
生成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进入就有了如下文件:
调用dll文件
打开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。
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文件
生成.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
但是这样生成的代码函数名不一样,参数类型也不一样,需要自己研究研究。
调用.so在Linux实现matlab代码内容
同样地,写一个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
添加调试的写入log文件的功能
由于在Linux上跑,通常需要记录下出错的问题,把错误信息写到log文件中。写了一个写入log文件的函数,每一个月的信息存放一个文件,如果文件不存在,就创建一个:
#include "time.h"
#include <fstream>
#include <iostream>
void WriteLog(const char* text)
{
char LOG_FILE[1024]={0};
// get the time now
time_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 now
time_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???
|