Matlab与VC混合编程之三
1. 有没有优雅的使用方式?
在《Matlab与VC混合编程之二》中我们学会了如何使用向量或者矩阵在VC与Matlab COM组件之间传递参数。在第二章的实现中,标量和向量的使用方式勉强可以接受,矩阵的使用方式简直令人发狂。而我们的大多数科学计算或者工程计算都需要用到矩阵,如何改进对矩阵(二维数组)的访问方式,是本章要回答的问题。在本章中,我们使用用C++的类来封装这种复杂性,把标量和向量作为矩阵的特殊情况,使用类来表达矩阵的概念。经过封装之后,以后再使用时就直接调用类的接口,实现了简单优雅访问Matlab生产的COM组件。首先讲解一些C++的类基本概念,之后讲解矩阵了matrix的实现方式,最后在附录中给出完整的源代码。
2. 基本概念
a.需要几个类来封装
使用类来表示概念和针对接口编程,是设计一个良好的类需要遵循的原则。在VC与Matlab COM的访问中,我们需要“矩阵”这一概念。使用Matlab时会注意到matlab的几乎所有函数的输入输出参数都是矩阵,向量和标量只是矩阵的特殊形式,向量是只有一行(行向量)或者只有一列(列向量)的矩阵,标量是只有一行一列的矩阵。理解了这一点之后,我们就只需要一个概念,即矩阵。所以我们只需要实现或者定义一个类matrix。
b.类的接口有哪些
类的接口指类的共有方法(public函数)。类的接口有哪些,是由使用该类的客户的上下文(环境)来确定的。也就是说在定义类的接口时要站在客户(使用类的人或者代码)的角度去考虑问题。在这里,使用matrix时,我们需要能够将数据赋值到matrix中,能够将matrix作为输入参数传递给Matlab COM的接口函数去完成计算,能够将matrix作为输出参数传递给Matlab COM的接口函数从而获得计算的结果,能够将matrix中的数据获取出来。
如图1所示。
写成伪代码的形式如下:
double dx[2][2] = {1,2,3,4}; //客户需要计算的数据
matrix mx, my; //matrix变量的定义
mx.setMatrix(2,2,dx);//将客户数据赋值到matrix中
long varNum = 1;
pInterface->calcSin(varNum, &my, mx); //matrix作为输入参数和输出参数,假定这里的COM函数是求正弦值
double dy[2][2] = {0}; //客户需要获取结果数据
my.getData(dy);
站在客户的角度,才能设计出优良的类(抽象、概念)。
3. matrix的实现方式
a. matrix接口初步
根据第二节的分析,我们的matrix的接口应该是这样的:
class matrix
{
matrix(); //默认构造函数
matrix(int nRows, int nCols); //构造nRows行、nCols列的矩阵,所有元素初始化为0
matrix(int nRows, int nCols, double* pdArray);//构造nRows行、nCols列的矩阵,矩阵元素为数组的内容
matrix(const matrix& other); //拷贝构造函数,允许使用matrix对象构造
void setMatrix(int nRows, int nCols, double* pdArray); //设置矩阵的大小和数据
void setSize(int nRows, int nCols); //设置矩阵的大小,以后再设置数据
void setData(double* pdArray, ); //设置数据
Size getSize(); //返回矩阵的大小
void getData(double* pdArray); //获得矩阵的数据
operator VARIANT(); //类型转换运算符,允许在函数的输入参数中使用VARIANT的地方使用matrix
VARIANT* operator&(); //取地址运算符重载,允许在函数的输出参数中使用VARIANT的地方使用matrix
}
b.增加一维数组和二维数组的描述
i.一维数组和二维数组的区别
在《Matlab与VC混合编程之二》中我们提到了VARIANT存储数组时是按照列优先的顺序来存储的。如果是一维数据,不管是按照行优先原则还是列优先的原则,访问其内部元素的顺序是相同的。比如double a[3]={1,2,3}。若按照行优先来存储,先存储第一行,那么在内存中的顺序是1,2,3.若按照列优先来存储,先存储第一列,1,再存储第二列2,最后存储第三列3,元素在内存中的顺序依然是1,2,3.所以对于一维数组,不需要进行数据转换。
对于二维数据(矩阵),则必须进行数据转换。仍然使用《Matlab与VC混合编程之二》中的例子。一个3行2列的数组(矩阵)
double array[3][2] = {1, 2,
按照行优先的顺序存储,在VC中是先存第1行{1,2},再存第2行{3,4},再存第3行{5,6}。即在内存中的顺序是1,2,3,4,5,6.
如果直接将array的数据拷贝到VARIANT的SAFEARRAY中,那么SAFEARRAY在内存中的顺序仍然是1,2,3,4,5,6.但由于SAFEARRAY是按照列优先来存放和访问的。SAFEARRAY的第一维是列,列数是2,第二维是行,行数是3。SAFEARRAY在进行访问时,先访问第1列{1,2,3},再访问第2列{4,5,6}。即这时将SAFEARRAY写成矩阵的形式是
[1 4,
现在首先对array取转置,得到二维数组b[2][3]={1,3,5,
[1,2,
为了区分一维数组和二维数组,定义了枚举enum ArrayType{Dim1, Dim2}; 当使用matrix表示向量(行向量或者列向量,默认是列向量)或者标量时,使用Dim1,表示(Dimension 1,一维)。当使用matrix表示矩阵时,如果用户没有在外部对输入的二维数组使用转置,使用Dim2,matrix会在构造时进行装置;如果用户在外部自己实现了转置,那么使用Dim1,matrix不会再进行转置。即Dim1和Dim2的区别是Dim1构造时matrix时直接拷贝传入的数组的数据,Dim2将传入的数据转置后拷贝到内部的VARIANT。
ii. 数组维数的描述
使用了matrix的内部类struct Size来描述数组的行数和列数。
class matrix {
public:
struct Size
{
int m_nRows; //矩阵的行数
int m_nCols; //矩阵的列数
Size() : m_nRows(0), m_nCols(0){ }
Size(int nRows, int nCols) : m_nRows(nRows), m_nCols(nCols){ }
};
};
c. 兼容已有的程序
为了兼容已有的程序,需要增加VARIANT和matrix互相赋值的接口。使用构造函数和setMatrix函数重载来用VARIANT构造和设置matrix;使用类型转换运算符来将matrix转换为VARIANT。
d. 完整接口的matirx
经过以上分析,具备较完备接口的matrix的定义如下:
class matrix
{
public:
//Dim1:一维数组,表示向量;Dim2:二维数组,表示矩阵。将标量看成是只有一个元素的向量或者矩阵。
enum ArrayType{Dim1, Dim2};
struct Size
{
int m_nRows; //矩阵的行数
int m_nCols; //矩阵的列数
Size() : m_nRows(0), m_nCols(0){ }
Size(int nRows, int nCols) : m_nRows(nRows), m_nCols(nCols){ }
};
public:
matrix();
matrix(int nRows, int nCols); //构造nRows行、nCols列的矩阵,所有元素初始化为0
//构造nRows行、nCols列的矩阵,所有元素初始化为一维数组或二维数组的内容
matrix(int nRows, int nCols, double* pdArray, ArrayType arrType);
matrix(const VARIANT& var); //用VARIANT构造
matrix(const matrix& other); //拷贝构造函数,允许使用matrix对象构造
void setMatrix(int nRows, int nCols, double* pdArray, ArrayType arrType); //设置矩阵的大小和数据
void setMatirx(const VARIANT& var); //用VARIANT设置矩阵
void setSize(int nRows, int nCols); //设置矩阵的大小,默认构造后再设置数据
void setData(double* pdArray, ArrayType arrType); //默认构造后再设置数据
Size getSize(); //返回矩阵的大小
void getData(double* pdArray, ArrayType arrType); //获得矩阵的数据
public: //matrix 与 VARIANT的转换
operator VARIANT(); //类型转换运算符,允许在函数的输入参数中使用VARIANT的地方使用matrix
VARIANT* operator&(); //取地址运算符重载,允许在函数的输出参数中使用VARIANT的地方使用matrix
matrix& operator=(const matrix& other); //重载赋值运算符,允许matrix之间相互赋值
virtual ~matrix();
};
4. 优雅的使用matrix类
为了演示matrix的使用,这里使用matrix重写了《Matlab与VC混合编程之二》中的OnButtonTest()。
{
}
5. 优雅的背后,matrix封装类
在第4节,我们使用matrix类的接口重写了《Matlab与VC混合编程之二》中VC与Matlab COM的接口函数进行交互的程序。可以明显看到,使用了matrix后,程序代码和程序逻辑得到很大的改善。那么,matrix的接口是怎么实现的。附录8给出优雅的背后,matrix的完整实现代码。
6. 有没有完整的说明matlab COM?
我们采用COM组件方式的matlab与VC混合编程给予了详细的介绍。这里距离完整还差一点,就是我们的程序怎么发布。在Matlab7.0及以下版本中,在Matlab COM Builder窗口中点击菜单Component->Package component;在在Matlab2007b及以上版本中,点击工具栏Build后面的Package按钮。弹出Package Files对话框(Matlab 7.0版本),见图3,将“Include MCR”选中,点击左下方的Create,等待几十秒,完成COM组件封装。在Matlab的当前目录Current Directary窗口下,找到distrib文件夹下的组件名的可执行程序YourComName.exe,将该文件复制出来,跟你的VC程序编译链接生成的可执行程序.exe放在一起打包发布就行了。
7.其他的VC和Matlab混合编程的方式呢?
对于matlab与VC混合编程的实现方法之一“采用COM组件方式”,我们给予了完整而详细的介绍。根据存在必有其意义的想法,其他的混合编程方式一定有其存在的意义。那么其他方式是怎么实现的,有没有优点,接下来的章节会继续探讨。8.附录
// matrix.h: interface for the matrix class and matrix-related functions.
// 头文件
//
#ifndef MATRIX_H_H
#define MATRIX_H_H
//封装了与matlab com进行交互的数据。可以把所有matlab的函数的输入参数和返回值看成是矩阵形式。
//作者:张坤
//时间:2013.06.06
class matrix
{
public:
public:
public:
private:
};
#endif
// matrix.cpp: implementation of the matrix class.
//源文件
//
#include "stdafx.h"
#include "matrix.h"
// 功能:构造0行、0列的空矩阵
// 输入:无
// 输出:无
// 影响:matrix为空矩阵
// 作者:张坤
// 时间:2013.06.06
matrix::matrix()
{
}
// 功能:析构
// 输入:无
// 输出:无
// 影响:无
// 作者:张坤
// 时间:2013.06.06
matrix::~matrix()
{
}
// 功能:构造nRows行、nCols列的矩阵,所有元素初始化为0
// 输入:nRows, 矩阵的行数; nCols,矩阵的列数
// 输出:无
// 影响:matrix所有元素初始化为0
// 作者:张坤
// 时间:2013.06.06
matrix::matrix(int nRows, int nCols)
{
}
// 功能:拷贝构造函数,允许使用matrix构造
// 输入:matrix对象
// 输出:无
// 影响:matrix大小和所有元素与输入参数相同
// 作者:张坤
// 时间:2013.06.06
matrix::matrix(const matrix& other)
{
}
// 功能:重载赋值运算符,允许matrix之间相互赋值
// 输入:matrix对象
// 输出:无
// 影响:matrix大小和所有元素与输入参数相同
// 作者:张坤
// 时间:2013.06.06
matrix& matrix::operator=(const matrix& other)
{
}
// 功能:拷贝另一个matrix对象到this对象
// 输入:matrix对象
// 输出:无
// 影响:matrix大小和所有元素与输入参数相同
// 作者:张坤
// 时间:2013.06.06
void matrix::copy(const matrix& other)
{
}
// 功能:用指定的var构造矩阵
// 输入:var:VARIANT型的数据
// 输出:无
// 影响:将matrix的大小和所有元素初始化为var的大小和元素
// 作者:张坤
// 时间:2013.06.12
matrix::matrix(const VARIANT& var)
{
}
// 功能:构造nRows行、nCols列的矩阵,所有元素初始化为一维数组或二维数组的内容
// 输入:nRows, 矩阵的行数; nCols,矩阵的列数,pdArray一维数组或二维数组强制转换的结果,
//
// 输出:无
// 影响:matrix所有元素初始化为一维数组或二维数组的内容
// 注意:如果matrix表示一个向量,使用一维数组构造,arrType设为matrix::Dim1
//
// 作者:张坤
// 时间:2013.06.06
matrix::matrix(int nRows, int nCols, double* pdArray, ArrayType arrType)
{
}
// 功能:设置nRows行、nCols列的矩阵,元素为一维数组或二维数组的内容
// 输入:nRows, 矩阵的行数; nCols,矩阵的列数,pdArray一维数组或二维数组强制转换的结果,
// arrType,一维数组或二维数组的标识
// 输出:无
// 影响:matrix所有元素初始化为一维数组或二维数组的内容
// 注意:如果matrix表示一个向量,使用一维数组构造,arrType设为matrix::Dim1
//
// 作者:张坤
// 时间:2013.06.06
void matrix::setMatrix(int nRows, int nCols, double* pdArray, ArrayType arrType)
{
}
// 功能:获得指定var的大小
// 输入:varSize引用:var的大小,是返回值;var:欲获得大小的VARIANT
// 输出:无
// 影响:无
// 作者:张坤
// 时间:2013.06.12
void matrix::getVariantSize(Size& varSize, const VARIANT& var) //获得该var的大小
{
}
// 功能:用指定的var设定矩阵
// 输入:var:VARIANT型的数据
// 输出:无
// 影响:将matrix的大小和所有元素设置为var的大小和元素
// 作者:张坤
// 时间:2013.06.12
void matrix::setMatirx(const VARIANT& var) //用VARIANT设置矩阵
{
}
// 功能:设置矩阵的所有元素为一维数组或二维数组的内容
// 输入:nRows, 矩阵的行数; nCols,矩阵的列数
// 输出:无
// 影响:matrix为空,只是定义了大小,必须之后再调用setData()
// 注意:这里用一维数组构造时,形参的格式必须按照列优先的顺序构造。
// 作者:张坤
// 时间:2013.06.06
void matrix::setSize(int nRows, int nCols)
{
}
// 功能:设置矩阵的所有元素为一维数组或二维数组的内容
// 输入:pdArray一维数组或二维数组强制转换的结果,
//
// 输出:无
// 影响:matrix所有元素设置为一维数组或二维数组的内容
// 注意:这里用一维数组构造时,形参的格式必须按照列优先的顺序构造。
// 作者:张坤
// 时间:2013.06.06
void matrix::setData(double* pdArray, ArrayType arrType)
{
}
// 功能:获得矩阵的数据, 返回值矩阵可以为一维数组或二维数组
// 输入:pdArray一维数组或二维数组强制转换的结果,
//
// 输出:无
// 影响:无
// 注意:这里用一维数组构造时,形参的格式是按照列优先的顺序排列的。
// 作者:张坤
// 时间:2013.06.07
void matrix::getData(double* pdArray, ArrayType arrType)
{
}
// 功能:获得矩阵的维数
// 输入:pdArray一维数组或二维数组强制转换的结果,
//
// 输出:无
// 影响:无
// 注意:这里用一维数组构造时,形参的格式是按照列优先的顺序排列的。
// 作者:张坤
// 时间:2013.06.07
matrix::Size matrix::getSize()
{
}
// 功能:将nRow行、nCols列的pdin矩阵转置成pdout矩阵
// 输入:pdin待转置的矩阵,nRows、nCols为pdin的行数和列数
//
// 输出:无
// 影响:无
// 注意:该函数可以实现行优先与列优先访问时的数据变换,在getData,setData
//
// 作者:张坤
// 时间:2013.06.11
void matrix::transpose(int nRows, int nCols, double* pdin, double* pdout)
{
}
本文是作者原创,转载必须保证文章的完整性并标明出处(blog.sina.com.cn/zhangkunhn),请尊重作者,支持原创。