Matlab与VC混合编程之二

Matlab与VC混合编程之二

1. 待解决的新问题

在《Matlab与VC混合编程之一》中我们已经实现了在VC中调用Matlab编译M文件生成的COM的方法,并成功的进行了计算并绘制一个点。接下来的问题是如何向COM接口函数传递一个向量或者一个矩阵。为什么需要学习向COM接口函数传递一个向量或者一个矩阵?很多学科都涉及向量和矩阵的相关计算,尤其是矩阵,经常需要求取矩阵的特征值、特征向量、行列式、逆矩阵、LU分解等。并且向量和矩阵也是Matlab计算的基本单元。现在我们就来研究如何向COM接口函数传递向量或者矩阵,如何获取COM接口函数计算的结果。

2. 编写相关的M文件并生成COM

为了演示如何显现标量、向量和矩阵如何在VC和COM中传递,我们首先需要编写使用这些类型作为输入输出变量的M文件。

a. 输入输出变量都是向量

function y = myCross(a, b)
%实现向量叉乘,返回向量
y = cross(a, b);

b. 输入是矩阵,输出是标量

function y = myDet(X)
%实现求矩阵行列式,返回标量
y = det(X);

c. 输入是矩阵,输出是矩阵

function Y = myInv(X)
%实现求矩阵的逆矩阵,返回矩阵
Y = inv(X);

现在,按照《Matlab与VC混合编程之一》“4.将Matlab函数变成COM”的方法,将这3个M文件编译成COM。COM组件名字是MyCalc,将类集合中给出的默认类名改为MyCalc,具体是将默认的类名删掉,添加类名MyCalc。这一步的工作已经完成。接下来是将COM文件导入到VC工程中。

3. 建立VC工程导入COM文件

我们新建一个基于对话框的MFC工程TestMyCalc作为示例。按照《Matlab与VC混合编程之一》“5.在VC中使用COM”中前5步(a、b、c、d、e)的步骤对TestMyCalc工程添加Button,COM相关文件,编写代码等工作。不同的是步骤b中添加的文件是mwcomtypes.h,mycalc_idl.h,mycalc_idl_i.c,步骤c在相应的mycalc_idl_i.c中查找,步骤d添加相应的mycalc_idl.h头文件。OnButtonTest()中函数流程即四个步骤完全相同,个别的命名有所改变。详细代码见附录的a部分。

4. 了解VARIANT

在VC中,我们一般使用一个double型变量表示标量,一维double数组表示向量,二维double数组表示矩阵。那么怎么把这些变量写入VARIANT型变量并放入COM接口函数,又怎么从VARIANT型变量中读出计算的结果?要回答这两个问题,首先要了解VARIANT类型。

《Matlab与VC混合编程之一》中已经说了,COM是不依赖于特定的开发语言。VAIRANT是COM使用的类型,当然也应该不依赖于特定的开发语言。不同开发语言有不同的变量类型,同一种变量类型的类型关键字也可能不同。为了进行统一描述,VARIANT类型包含了两部分,一部分是VARIANT包含的数据的类型,另一部分是该数据的类型。可以把VARIANT看成是一个包装盒,封装了语言相关的变量的值和变量的类型。

比如我们在VC中定义一个VARIANT变量,VARIANT a; 那么a.vt的部分用来描述变量类型(value_type)。如果想让a封装double型变量b,那么赋值a.vt = VT_R8;其中VT_R8表示double,因为double型数据是实数(Real),一个double型变量占用8个字节(即sizeof(double)的结果是8)。之后将给数据部分赋值a.dblval=b;其中dblval表示double value。

比如我们在VC中定义另一个VARIANT变量,VARIANT b;如果想让b封装double型数组,那么赋值b.vt = VT_R8|VT_ARRAY; 这里的数组没有指出维数,可以是任意维的,它真正的维数是靠b的数据部分来描述。给b的数据部分b.parray赋值比较麻烦。这里数据部分是一个指向数组的指针(pointer to array)。查看b.parray的类型,其类型为(SAFEARRAY *),指向SAFEARRAY的指针。SAFEARRAY顾名思义,是一个安全数组的类型。通过查看MSDN,可知其详细信息,不过我们不用知道的这么详细,为了方便使用SAFEARRAY,VC特意定义了一些函数供我们使用。常用的有:SafeArrayCreate创建之,SafeArrayAccessData得到数据的指针和访问权限,成块的存取包含的数据,SafeArrayUnaccessData不再有权限访问数据,SafeArrayDestroy销毁之。与成块的访问数据对应的还有一种单个元素的访问方式,SafeArrayPutElement单个的放入元素,SafeArrayGetElement单个的读入元素。这种访问方式没有成块的访问方式效率高。我这里使用的成块的访问方式。想深入了解SafeArray及其访问方式的可以参考http://blog.csdn.net/sheismylife/article/details/234547 《SAFEARRAY使用实例》该博文对这些方面给予了深入的介绍。下面给出创建、写入、读出包装了一维、二维数组的VARIANT变量方法。

5. 一维数组的使用

a. 创建

 方式一,更通用

 VARIANT b;
 b.vt = VT_R8|VT_ARRAY;
 SAFEARRAYBOUND sfbound[1];  //一维数组时使用
 sfbound[0].lLbound = 0;     //该维数组的起始下标0
 sfbound[0].cElements = 3;   //该维数组的大小3
 b.parray = SafeArrayCreate(VT_R8, 1, sfbound);//double型,一维,维数描述

 方式二,更简单

 VARIANT b;
 b.vt = VT_R8|VT_ARRAY;
 b.parray = SafeArrayCreateVector(VT_R8, 0, 3););//一维double型,起始下标0,大小3。

b. 写入数据

 double array1[3] = {1, 2, 3};    //待存入的一维数组
 double* buf = NULL;             
 SafeArrayAccessData(b.parray, (void**)&buf);  //指针指向SafeArray的数据区
 memcpy(buf, array1, sizeof(double)*3);        //将array1的3个double数据向指针指向的数据区拷贝
 SafeArrayUnaccessData(b.parray);              //解除指针的指向

c. 读出数据,与写入数据的方式类似,只是数据拷贝的方向相反

 double array2[3] = {0};
 double* buf2 = NULL;
 SafeArrayAccessData(b.parray, (void**)&buf2);  //指针指向SafeArray的数据区
 memcpy(array2, buf2, sizeof(double)*3);        //将指针指向的数据区的3个double数据向array2拷贝
 SafeArrayUnaccessData(b.parray);              //解除指针的指向

 上述的3个过程实现了将一维数组(向量){1,2,3}从array1放入VARIANT并读到array2的过程。 

6. 二维数组的使用

a. 创建

 VARIANT b;
 b.vt = VT_R8|VT_ARRAY;
 SAFEARRAYBOUND sfbound[2]; //二维数组时使用
 sfbound[0].lLbound = 0; //该维数组的起始下标0
 sfbound[0].cElements = 3; //该维数组的大小3

 sfbound[1].lLbound = 0; //该维数组的起始下标0
 sfbound[1].cElements = 2; //该维数组的大小2
 b.parray = SafeArrayCreate(VT_R8, 2, sfbound);//double型,二维,维数描述

b. 写入数据

 这里尤其要注意:VC二维数组的内存布局是行优先的,而SAFEARRAY是列优先的。

 举例如下:一个3行2列的数组double array1[3][2] = {1, 2, 3, 4, 5, 6};在VC中是先存第1{1,2},再存第2{3,4},再存第3{5,6}。写成矩阵的形式是:

[1 2;

 3 4;

 5 6]

而SAFEARRAY的存放是这样的,先存第1{1,2,3},再存第2{4,5,6}。写成矩阵的形式是

[1 4;

 2 5;

 3 6]。

显然两者是不等价的。直接把这样的数据交给Matlab生成的COM组件去执行肯定达不到正确的结果。所以在对SAFEARRAY读写时需要下标变换。变换的规则是将double数组和SAFEARRAY都看做一维数组,double数组按行优先的顺序访问,行优先的规则是[i][j] = [i*nCols +j];SAFEARRAY数组按照列优先的顺序访问,列优先的规划是[i][j] = [i + j*nRows]。其中数组的大小是nRows行,nCols列。

 考察上面的例子,存入SAFEARRAY时,令buf[i + j*3] = ptarray1[i*2 + j]。其中ptarray1是指向二维数组的指针,double* ptarray1 = (double*)array1; buf是指向SAFEARRAY数据区的指针。当i从0到2,j从0到1遍历时,ptarray1的数据在内存中的排序是{1,2,3,4,5,6},经过变化后放入buf,buf的数据在内存中的排序是{1,3,5,2,4,6}。存入SAFEARRAY时按照列优先来写数据,故第1列是{1,3,5},第2列是{2,4,6},写成矩阵的形式是:

[1 2;

 3 4;

 5 6]。这个数据正好是需要的矩阵,交给Matlab生成的COM组件去执行能够得到正确的结果。下面给出实现代码。

 double array1[3][2] = {1, 2, 3, 4, 5, 6};
 double* ptarray1 = (double*)array1;
 double* buf = NULL;
 SafeArrayAccessData(b.parray, (void**)&buf); //指针指向SafeArray的数据区
 //数据变换
 for (int j = 0; j < 2; ++j)  //ptrray1的第j列
 {
  for (int i = 0; i < 3; ++i)//ptarray1的第i行
  {
   buf[i + j*3] =  ptarray1[i*2 + j];   //行优先转换为列优先
  }
 }
 SafeArrayUnaccessData(b.parray); //解除指针的指向

iii.读出数据,与写入数据的方式类似,只是数据拷贝的方向相反

 double array2[3][2] = {0};
 double* ptarray2 = (double*)array2;
 double* buf2 = NULL;
 SafeArrayAccessData(b.parray, (void**)&buf2); //指针指向SafeArray的数据区
 //数据变换
 for (j = 0; j < 2; ++j) //ptrray1的第j列
 {
  for (int i = 0; i < 3; ++i)//ptarray1的第i行
  {
   ptarray2[i*2 + j] = buf[i + j*3]; //列优先转换为行优先
  }
 }
 SafeArrayUnaccessData(b.parray); //解除指针的指向

 

上述的3个过程实现了将二维数组(矩阵)[1,2;

                                      3,4;

                                      5,6]从array1放入VARIANT并读到array2的过程。 

7. 回答Action_1到Action_6的问题

经过了对第4、5、6节的学习,我们就能够回答Action_1到到Action_6的问题了。详细的回答见附录b部分。

8. 有没有可以改进的空间?

标量、向量的方式还可以接受,对矩阵的处理简直令人发狂,难道每次面临矩阵的运算和处理都要使用这么麻烦的方式处理,有没有可以改进的访问方式?答案是肯定的,我们可以用C++的类来封装这种复杂性,以后再使用时就直接调用类的接口,可以很简单很优雅的使用Matlab生产的COM组件了。具体的实现方式,将在第三部分继续分解。

9. 附录

a. OnButtonTest()的源码,其中Action_1到Action_6需要填充。

void CTestMyCalcDlg::OnButtonTest()
{
 // TODO: Add your control notification handler code here
 ::CoInitialize(NULL);  //a.初始化COM。
 IMyCalc *pICalc = NULL;  //声明一个接口
 
 //b.创建COM接口。
 HRESULT hr = ::CoCreateInstance(CLSID_MyCalc,  //你使用的COM对象的ID(CLaSs ID) 
  NULL, 
  CLSCTX_ALL, 
  IID_IMyCalc, //你使用的COMCOM对象接口的ID(Interface ID) 
  (LPVOID*)&pICalc);//这里用&pIdraw使pIdraw指向接口的地址
 if (FAILED(hr))  //错误检查并处理
 
  //。。。
  return;
 }
 
 //c.使用COM接口,这一段使用的数据类型是VARIANT
 long nvar = 1;  //输出变量的个数
 //i. 使用mycross
 VARIANT ca,cb,cy; //ca,cb输入参数,cy输出参数
 //对x,y初始化
 VariantInit(&ca);
 VariantInit(&cb);
 VariantInit(&cy);
 //对向量ca,cb赋值,以double型为例
 //...(怎么做?Action_1
 hr = pICalc->mycross(nvar, &cy, ca, cb); //输出变量使用&cy形式才能得到返回值。
 //从cy获取计算结果
 //...(怎么做?Action_2

 //ii. 使用mydet
 VARIANT dx,dy; //dx输入参数,dy输出参数
 //对x,y初始化
 VariantInit(&dx);
 VariantInit(&dy);
 //对向量dx赋值,以double型为例
 //...(怎么做?Action_3
 hr = pICalc->mydet(nvar, &dy, dx); //输出变量使用&dy形式才能得到返回值。
 //从dy获取计算结果
 //...(怎么做?Action_4

 //iii. 使用myinv
 VARIANT ix,iy; //ix输入参数,iy输出参数
 //对x,y初始化
 VariantInit(&ix);
 VariantInit(&iy);
 //对向量ix赋值,以double型为例
 //...(怎么做?Action_5
 hr = pICalc->myinv(nvar, &iy, ix); //输出变量使用&iy形式才能得到返回值。
 //从iy获取计算结果
 //...(怎么做?Action_6 
 
 //d.退出COM。
 CoUninitialize();
}

 b.OnButtonTest()的源码,其中Action_1到Action_6已经用正确的代码填充。

void CTestMyCalcDlg::OnButtonTest()
{
 // TODO: Add your control notification handler code here
 ::CoInitialize(NULL);  //a.初始化COM。
 IMyCalc *pICalc;  //声明一个接口
 
 //b.创建COM接口。
 HRESULT hr = ::CoCreateInstance(CLSID_MyCalc,  //你使用的COM对象的ID(CLaSs ID) 
  NULL, 
  CLSCTX_ALL, 
  IID_IMyCalc, //你使用的COMCOM对象接口的ID(Interface ID) 
  (LPVOID*)&pICalc);//这里用&pIdraw使pIdraw指向接口的地址
 if (FAILED(hr))  //错误检查并处理
 
  //。。。
  return;
 }
 
 //c.使用COM接口,这一段使用的数据类型是VARIANT
 long nvar = 1;  //输出变量的个数
 //i. 使用mycross
 VARIANT ca,cb,cy; //ca,cb输入参数,cy输出参数
 //对x,y初始化
 VariantInit(&ca);
 VariantInit(&cb);
 VariantInit(&cy);
 //对向量ca,cb赋值,以double型为例
 //...Action_1
 ca.vt = VT_R8|VT_ARRAY; 
 cb.vt = VT_R8|VT_ARRAY; 
 double array1[3] = {1, 0, 0};    //待存入的一维数组
 double array2[3] = {0, 1, 0};    //带存入的一维数组
 double* buf = NULL;
 ca.parray = SafeArrayCreateVector(VT_R8, 0, 3);
  cb.parray = SafeArrayCreateVector(VT_R8, 0, 3);
 SafeArrayAccessData(ca.parray, (void**)&buf);  //指针指向SafeArray的数据区
 memcpy(buf, array1, sizeof(double)*3);        //将array1的3个double数据向指针指向的数据区复制
 SafeArrayUnaccessData(ca.parray);              //解除指针的指向
 SafeArrayAccessData(cb.parray, (void**)&buf);  //指针指向SafeArray的数据区
 memcpy(buf, array2, sizeof(double)*3);        //将array1的3个double数据向指针指向的数据区复制
 SafeArrayUnaccessData(cb.parray);              //解除指针的指向
 hr = pICalc->mycross(nvar, &cy, ca, cb); //输出变量使用&cy形式才能得到返回值。
 //从cy获取计算结果
 //...Action_2
 double array3[3] = {0};
 SafeArrayAccessData(cy.parray, (void**)&buf);  //指针指向SafeArray的数据区
 memcpy(array3, buf, sizeof(double)*3);        //将array1的3个double数据向指针指向的数据区复制
 SafeArrayUnaccessData(cy.parray);              //解除指针的指向
 //array3结果是{0,0,1},结果正确。

 //ii. 使用mydet
 VARIANT dx,dy; //dx输入参数,dy输出参数
 //对x,y初始化
 VariantInit(&dx);
 VariantInit(&dy);
 //对向量dx赋值,以double型为例
 //...Action_3
 double array4[2][2] = {1, 0, 0, 4};
 double* ptarray4 = (double*)array4;
 SAFEARRAYBOUND sfbound[2]; //二维数组时使用
 sfbound[0].lLbound = 0; //该维数组的起始下标0
 sfbound[0].cElements = 2; //该维数组的大小2 
 sfbound[1].lLbound = 0; //该维数组的起始下标0
 sfbound[1].cElements = 2; //该维数组的大小2
 dx.vt = VT_R8|VT_ARRAY;
 dx.parray = SafeArrayCreate(VT_R8, 2, sfbound); //double型,二维,维数描述
 buf = NULL;
 SafeArrayAccessData(dx.parray, (void**)&buf); //指针指向SafeArray的数据区
 //数据变换
 for (int j = 0; j < 2; ++j)  //ptrray4的第j列
 {
  for (int i = 0; i < 2; ++i)//ptarray4的第i行
  {
   buf[i + j*2] =  ptarray4[i*2 + j];   //行优先转换为列优先
  }
 }
    SafeArrayUnaccessData(dx.parray); //解除指针的指向
 hr = pICalc->mydet(nvar, &dy, dx); //输出变量使用&dy形式才能得到返回值。
 //从dy获取计算结果
 //...Action_4
 double dresult = 0;
 if (dy.vt == VT_R8)
 {
         dresult = dy.dblVal;
 }
 //dresult = 4,结果正确
 

 //iii. 使用myinv
 VARIANT ix,iy; //ix输入参数,iy输出参数
 //对x,y初始化
 VariantInit(&ix);
 VariantInit(&iy);
 //对向量ix赋值,以double型为例
 //Action_5
 double array5[3][3] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
 double* ptarray5 = (double*)array5;
 SAFEARRAYBOUND sfbound2[2]; //二维数组时使用
 sfbound2[0].lLbound = 0; //该维数组的起始下标0
 sfbound2[0].cElements = 3; //该维数组的大小2 
 sfbound2[1].lLbound = 0; //该维数组的起始下标0
 sfbound2[1].cElements = 3; //该维数组的大小2
 ix.vt = VT_R8|VT_ARRAY;
 ix.parray = SafeArrayCreate(VT_R8, 2, sfbound2); //double型,二维,维数描述
 buf = NULL;
 SafeArrayAccessData(ix.parray, (void**)&buf); //指针指向SafeArray的数据区
 //数据变换
 for (j = 0; j < 3; ++j)  //ptrray4的第j列
 {
  for (int i = 0; i < 3; ++i)//ptarray4的第i行
  {
   buf[i + j*3] =  ptarray5[i*3 + j];   //行优先转换为列优先
  }
 }
    SafeArrayUnaccessData(ix.parray); //解除指针的指向
 hr = pICalc->myinv(nvar, &iy, ix); //输出变量使用&iy形式才能得到返回值。
 //从iy获取计算结果
 //...Action_6
 double array6[3][3] = {0};
 double* ptarray6 = (double*)array6;
 buf = NULL;
 SafeArrayAccessData(iy.parray, (void**)&buf); //指针指向SafeArray的数据区
 //数据变换
 for (j = 0; j < 3; ++j)  //ptrray4的第j列
 {
  for (int i = 0; i < 3; ++i)//ptarray4的第i行
  {
   ptarray6[i*3 + j] =  buf[i + j*3];   //列优先转换为行优先
  }
 }
    SafeArrayUnaccessData(iy.parray); //解除指针的指向
 //array6结果是{{1,0,0}, {0,1,0}, {0,0,1}},结果正确。


 //d.退出COM。
 CoUninitialize();
}

本文是作者原创,转载必须保证文章的完整性并标明出处(blog.sina.com.cn/zhangkunhn),请尊重作者,支持原创。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值