admin 管理员组

文章数量: 1086019


2024年4月16日发(作者:vector java)

C语言环境中调用Matlab程序指南

甄梓宁

znzhen@

Matlab在计算方面功能强大、编写简单,但是要运行Matlab程序必须装有Matlab并且用户

界面也不够完善,因此除了计算的其他部分采用C等更规范完备的语言进行编写是较好的选

择。本文就对如何在C程序中调用Matlab程序作说明。

在C程序中调用Matlab程序有两大类方法。第一种是调用Matlab引擎,第二种是将m文

件打包成dll文件在C语言环境下调用。前者虽然易于实现,可以实时监控程序的运行,但

是独立性差,需要安装完整版Matlab,且每次调用都会启动进程;后者则实现

复杂,调试麻烦,但只需安装MCR(Matlab Component Runtime),耗费资源较少。关于MCR,

请见第四章的说明。至于两种方法需要在C环境中如何配置请见第五章。

而反过来若要在Matlab下调用C程序则一般使用Matlab自带的mex工具,在此不作介绍。

一、调用Matlab引擎

调用Matlab引擎可以在WIN32、MFC中使用,它的原理实际上相当于打开一个精简版

的Matlab然后往里面输命令。下面是调用Matlab中的加法程序add.m的例子。

先在Matlab的work目录下创建add.m文件并编写程序如下:

function s = add (a, b)

s = a+b;

在C程序中,首先打开精简版的Matlab:(所需头文件,引用库等见第五章)

Engine *ep = engOpen (NULL);

编译运行后,会自动打开一个命令行监控窗口,输入pwd就可以看到当前的工作目录,

于是需要先将工作目录转换至存放add.m的目录:

engEvalString (ep, ”cd ....work”);

engEvalString是往Matlab里输命令的函数,显然我们的目标是成功运行:

engEvalString (ep, ”s=add(a,b)”);

当然,目前Matlab中并没有a和b两个变量,因此需要在C中初始化这两个变量并转

换成Matlab基本变量类型mxArray,才能将它们输入到Matlab中。关于mxArray,在

第三章会有详细说明。下面是对a=1,b=2的转换过程:

1

double aval=1, bval=2;

mxArray *a=mxCreateDoubleMatrix(1,1,mxREAL);

mxArray *b=mxCreateDoubleMatrix(1,1,mxREAL);

mxSetPr(a,&aval);

mxSetPr(b,&bval);

于是可以往Matlab里输入了:(双引号内是在Matlab里的变量名称)

engPutVariable(ep,"a",a);

engPutVariable(ep,"b",b);

运行add.m:

engEvalString (ep, ”s=add(a,b)”);

编译运行后可以在监控窗口中查看s,确认是3无误。接下来就是怎样把s从Matlab读

入C中的问题了,也很方便:

mxArray *s=engGetVariable(ep,"s");

显示一下:

printf("s=%f",*mxGetPr(s));

最后不要忘记关闭Matlab:

engClose(ep);

用到的几个mxArray也需要释放,在此不述,具体请见第三章。

到此,调用Matlab引擎的全过程就结束了。不管m文件的函数形式有多复杂,都万变

不离其宗,改变的只有变量与mxArray相互转换那一部分,具体请见第三章。

另外,Matlab还提供了将监控窗口中的显示存入某buffer的函数。可以如下设置:

char buffer[1000];

engOutputBuffer(ep,buffer,1000);

这样,所有显示都会存入buffer中,这对调试很有帮助。如果想取消这个功能,则:

engOutputBuffer(ep,NULL,0);

最后还可以将监控窗口关闭或重新打开:

engSetVisible(ep,false);

engSetVisible(ep,true);

以上便是调用Matlab引擎的全部内容。这种方法步骤简单,调试也方便,所以在一般

场合下使用已经足够好了。

2

二、打包m文件至dll

打包m文件至dll相对于调用Matlab引擎,唯一也是最重要的一点好处是便于发布。

在一台没有安装Matlab的电脑中,利用最少的资源运行Matlab程序只有这种方法。

该方法根据dll的不同又可分为两类:一是打包成COM或.NET组件的dll,二是传统的

dll。前者除了在WIN32、MFC中还可在.NET中运行。本文仅介绍较为简单的打包成传

统dll的方法。

下面这个例子将自己编写的加法程序add.m及乘法程序multi.m打包至并在C

程序中调用。add.m如第一章所示,multi.m如下所示。

function s = multi( a, b )

s = a*b;

第一步,设置Matlab中使用的C语言编译器:

mbuild –setup

弹出如下提示:

Please choose your compiler for building standalone MATLAB applications:

Would you like mbuild to locate installed compilers [y]/n?

选y的话会列出Matlab默认的几个编译器及其默认路径。如果没有完全符合的话则重

来选n,就会出现更多的选择:

Select a compiler:

[1] Borland C++Builder version 6.0

[2] Borland C++Builder version 5.0

[3] Borland C/C++ (free command line tools) version 5.5

[4] Lcc C version 2.4.1

[5] Microsoft Visual C/C++ version 8.0

[6] Microsoft Visual C/C++ version 7.1

[7] Microsoft Visual C/C++ version 6.0

[0] None

Compiler:

根据个人喜好输入相应编译器序号后,就会要求确认路径。若没有,则显示如下:

The default location for xxxxxxx compilers is C:xxxxxxx, but that directory does not exist on

this machine.

Use C:xxxxxxx anyway [y]/n?

选n输入其应该在的路径即可。

3

第二步,在Matlab中将add.m及multi.m文件编译为:

mcc -W lib:calc -T link:lib add.m multi.m

第一个选项-W的格式是“dll类型:生成的dll名称”,dll类型可以是C下的“lib”,C++

下的“cpplib”,COM下的“com”以及.NET下的“dotnet”。

第二个选项-T是生成类型,可以是“link:exe”生成exe文件,以及我们所用的“link:lib”

生成dll文件。

正常运行后会生成一排文件,其中有用的是以下这几个:

:编译中被排斥的项目列表。

calc.h和:C程序编写时需要。

和:C程序执行时需要。

在中有两类被排斥的项目。一类是Excluded by M-file compilabilty

rules,这类排斥是由于m文件使用的Matlab Toolbox函数中调用了被排斥的函数导致

的。一种解决办法是将需要的Toolbox函数进行简化,尽量只使用最基本的Matlab函数。

另一类是Excluded because they ship with the MCR,这类排斥没有影响,无视即可。

第三步,在C程序中调用中的函数:

首先将calc.h、、、四个文件拷贝至C程序同一目录中,并在程序

中包含calc.h以及在链接的附加依赖项中添加。

随后,可以在calc.h文件中找到我们需要使用的几个函数:

calcInitialize():初始化程序。

calcTerminate():关闭

mlfAdd(int nargout, mxArray** s, mxArray* a, mxArray* b):调用中的加法程序。

mlfMulti(int nargout, mxArray** s, mxArray* a, mxArray* b):调用中的乘法程序。

另外,初始化的时候还可以改用calcInitializeWithHandlers设置消息处理函数,这在本章

最后详细说明。下面是调用加法程序的C程序:(所需头文件,引用库等见第五章)

//初始化

calcInitialize();

//a=1, b=2转换为mxArray格式

double aval=1, bval=2;

mxArray *a=mxCreateDoubleMatrix(1,1,mxREAL);

mxArray *b=mxCreateDoubleMatrix(1,1,mxREAL);

mxSetPr(a,&aval);

mxSetPr(b,&bval);

4

//调用中的加法程序

mxArray *s=NULL;

mlfAdd(1,&s,a,b);

//显示结果

printf("s=%f",*mxGetPr(s));

//释放mxArray

mxDestroyArray(a);

mxDestroyArray(b);

mxDestroyArray(s);

//关闭

calcTerminate();

整个调用过程跟调用Matlab引擎非常类似,在程序开头运行dll初始化程序很重要,在

编程时容易忽略掉这一步导致出错。另外,在调用加法的函数mlfAdd中,第一个参量

是输出的个数,而输出都需要取mxArray*的地址,也就是mxArray**。关于mxArray的

结构,会在第三章详细说明。

以上便是m文件打包至dll并调用的全过程。

接下来,介绍一下消息处理函数。前面已经提到,采用打包dll方法调用Matlab函数会

导致调试麻烦,因此一个好的消息处理函数是必不可少的。而前面采用的dll初始化程

序calcInitialize()是不使用消息处理函数的,下面我们将使用calcInitializeWithHandlers函

数设置消息处理函数。

calcInitializeWithHandlers函数有两个输入参量:一个是error_handler,一个是print_

handler。前者是由Matlab中error函数触发的,说明调用函数彻底失败了;后者由除

了前者以外的一切显示输出触发。这两个函数的参量列表是由mclOutputHandlerFcn定

义的,格式如下:

typedef int (*mclOutputHandlerFcn)(const char *s)

一般,print_handler可以根据需要编写,而error_handler则最好弹出对话框提示错误。

参考程序如下:(需要注意的是消息处理函数必须为静态的)

static int error_handler(const char *msg)

{

printf("Error: %sn", msg);

return 0;

}

5

相应的,dll的初始化改为:

calcInitializeWithHandlers(error_handler,NULL);

最后,讨论一下对Matlab程序的修改生效问题。当调用Matlab的C程序编写完成并生

成exe、lib或dll后,突然发现Matlab程序哪里需要修改的时候,如果还要从一开始到

最后再重新生成一遍,就实在太不人性化了,所以接下来说说修改生效的方法。

调用Matlab引擎的话,非常好办。因为相当于往精简版的Matlab中输命令行,所以只

要修改相应的m文件就立即生效。而打包dll的话,只需进行前文中的第二步,将.dll

和.ctf两个文件拷至C程序的同一目录下即可。

当然,假如Matlab函数的参量列表都要修改的话,不论哪种方法都要重新生成一遍。

三、mxArray与mat文件读写

mxArray是Matlab基本变量类型,mat是Matlab基本数据文件类型,因此放在一起介

绍。

1、 mxArray概述

mxArray可以说是万能数据类型,它可以存整型矩阵、布尔型矩阵、浮点数矩阵、复数

矩阵、字符矩阵、结构矩阵、Sparse矩阵以及Cell矩阵。但是它的结构却非常简单:首

先是矩阵的基本信息,如矩阵类型、维数、各维的长度、矩阵元素是否复数等,然后是

指向数据区的指针。数据区根据矩阵类型的不同最多可以有四块,而每块都是一个一维

的数组,也就是说不管矩阵是几维的,其数据都根据Matlab制定的映射规则存放在了

一维数组中。下面是各类型矩阵的创建、赋值与取值函数列表,具体用法请参看Matlab

帮助。

矩阵

类型

矩阵元

素类型

创建函数

(不赋值)

赋值函数 取值函数

 mxCreateChar  mxArrayToString

 mxGetChars

 mxGetString

Char

wchar_t  mxCreateCharArray

MatrixFromString

 mxCreateString

Double

double

 mxCreateDoubleMatrix

 mxCreateDoubleScaler

 mxSetPr

 mxSetPi

 mxGetPr

 mxGetPi

 mxCreateLogicalArray

Logical

bool

 mxCreateLogicalMatrix

 mxCreateLogicalScaler

 mxSetData  mxGetLogicals

6

int或

double

 mxCreateNumericArray

 mxCreateNumericMatrix

 mxSetData

 mxSetImagData

 mxGetData

 mxGetImagData

Numeric

 mxSetPr  mxGetPr

 mxGetPi

 mxGetIr

 mxGetJc

Sparse

double  mxCreateSparse

 mxSetPi

 mxSetIr

 mxSetJc

Sparse

Logical

bool

 mxCreateSparse

LogicalMatrix

 mxSetData

 mxSetIr

 mxSetJc

 mxGetData

 mxGetIr

 mxGetJc

Cell

*mxArray

 mxCreateCellArray

 mxCreateCellMatrix

 mxSetCell  mxGetCell

 mxSetField

Struct

*mxArray

 mxCreateStructArray

 mxCreateStructMatrix

 mxSetFieldByNumber

 mxAddField

 mxRemoveField

 mxGetField

 mxGetFieldByNumber

mxArray使用示例可以参见第二章的程序,其调用了mxCreateDoubleMatrix、mxSetPr、

mxGetPr三个函数。

下面是关于使用mxArray的一些说明。因为很长很详细,所以可以跳过。

关于矩阵类型:根据矩阵元素类型可将矩阵分为两类,一类是直接存放值的矩阵,如

Double、Logical等等;另一类是如Cell、Struct等存放矩阵指针*mxArray的矩阵。对于

一个Cell矩阵,其每个元素可以是任意类型矩阵,甚至可以是Cell矩阵本身,不过这并

不会导致循环。

关于矩阵维数:在创建矩阵时可以根据需要选择以“Scalar”结尾的标量(0维)创建

函数,以“Matrix”结尾的矩阵(0~2维)创建函数,以及以“Array”结尾的数组(0~N

维)创建函数。要注意的是,在存放double型数据时,Double和Sparse类型矩阵最多

只能支持2维,大于2维则使用Numeric矩阵。同样,存放bool型数据时,SparseLogical

最多2维,大于2维则使用Logical矩阵。

关于复数数据:存放int或double型的各类矩阵中可以选择数据是否为复数。一般是在

创建函数的最后一个参量上输入“mxREAL”代表实数,“mxCOMPLEX”代表复数。

关于数据区:赋值函数其本质是改变指向数据区的指针。前面已经提到最多有四块数据

区,分别是“Data”或“Pr”代表实数数据,“ImagData”或“Pi”代表虚数数据,而主

7

要用于存放大型稀疏矩阵的Sparse或SparseLogical数据区中还有“Ir”和“Jc”两块描

述元素位置。其中Data/ImagData可以是任意类型数据,而Pr/Pi只能是double型。

关于数据存放顺序:因为是将矩阵的所有数据

用一维数组存放,所以在初始化一维数组的时

候首先需要知道其每个数据对应于矩阵中的

哪个位置。右图是Matlab对存放顺序的说明。

每一页中的行是第一维,列是第二维,而页数

是第三维,数组以图中的字母顺序排列。当然,

Matlab提供了mxCalcSingleSubscript函数可以

获得矩阵中任意位置的序号。

最后,是一些比较用的上的函数:

 mxIsxxxxxxx/mxGetClassName

前者判断是否是某类型矩阵,后者返回矩阵的类型至字符串。

 mxGetNumberOfElements

获取矩阵的元素总数。

 mxDestroyArray

创建的mxArray用完后利用此函数释放。

2、 mat文件读写函数

Matlab提供的mat读写函数非常人性化,下面以一个具体例子说明。在这个例子中首

先将一个矩阵写入文件再读出显示。

首先写一个2x3的整型矩阵A:

int Aval[6]={1,2,3,4,5,6};

mxArray *A=mxCreateNumericMatrix(2,3,mxINT32_CLASS,mxREAL);

mxSetData(A,Aval);

以写入方式打开mat文件:

MATFile *mfp=matOpen(".","w");

如果该文件需要在Matlab6或Matlab6.5版本下打开时,参量”w”应该改为”wL”。接下来

将矩阵A写入文件:

matPutVariable(mfp,"A",A);

关闭mat文件并释放A:

matClose(mfp);

mxDestroyArray(A);

8

上面是写入mat文件的完整过程。现在我们以读取方式打开mat文件:

mfp=matOpen(".","r");

获取矩阵至B:

mxArray *B=matGetVariable(mfp,"A");

显示一下第一行第三列数据:

int subs[2]={0,2};

printf("val=%d",

((int*)mxGetData(B))[mxCalcSingleSubscript(B,2,subs)]);

最后关闭mat文件并释放B不述。

在不知道mat文件的内容的情况下,可以使用matGetNextVariable函数一个个获取文件

内的变量及相应变量名。而另外两个函数matGetVariableInfo和matGetNextVariableInfo

返回的mxArray不包括数据区,只有矩阵的基本信息。

另外,利用matDeleteVariable函数可以删除文件中的变量。

四、关于MCR

MCR主要是为没装Matlab的客户端设计的,在上面可以运行基于Matlab的程序,可以

说MCR对于Matlab就相当于.NET Framework对于Visual Studio 2005的作用。另外其体

积只有100M,安装起来非常方便。其过程分三步:

1、 安装包的生成

在Matlab中输入:

buildmcr (路径名, 文件名)

则在该路径产生一zip文件,在发布的文件中包含该zip文件即可。

2、 拷贝至客户端

在客户端安装时,将发布文件中的zip文件解压至任意目录,记其解压后的根目录

为MCRPath。

3、 修改环境变量

在系统环境变量”Path”中添加”MCRPathruntimewin32”。

五、C语言环境下的设置

首先是在C语言环境的设置中“包含文件”一项添加“Matlab或MCR根目录extern

include”,以及在“库文件”一项中添加“Matlab或MCR根目录externlibwin32

microsoft”。

9

随后再根据程序中使用的基于Matlab的函数确定需要包含的头文件以及在链接设置的

附加依赖项中添加的库文件。我们使用的函数共有四类,如下表所示:

Matlab引擎

相关函数

mxArray

相关函数

mat文件

相关函数

mex

相关函数

函数开头字母

“eng”

“mx”

“mat”

“mex”

对应头文件

engine.h

matrix.h

mat.h

mex.h

对应库文件

其中以”mex”开头的mex相关函数用于在Matlab下调用C程序,在此也一并列出,仅

供参考。

10


本文标签: 矩阵 文件 函数 程序 调用