Delphi与MATLAB数据接口方案探讨

来源:互联网 发布:linux 查看软件版本 编辑:程序博客网 时间:2024/04/28 06:24

source:http://www.delphibbs.com/keylife/iblog_show.asp?xid=2109

概述:本文阐述了如何利用Delphi灵活强大方便的编程能力和Matlab强大的科学计算能力,降低编写复杂算法的难度和时间,使Delphi算法程序的开发效率大大提高,收到事半功倍的效果。

关键字:Matlab、科学计算、接口、数据交换

引 言:
    最近在搞一个数字图像加密的课题,其中涉及到大量的矩阵及其他科学运算,在用Delphi实现时,可以想象在编写算法的时候遇到了大量的问题,所以提出了这样一个想法:能否将后台运算模块通过某种接口的方式交由其他成熟的科学计算软件处理,而用Delphi从事其前台开发,这样将大大降低编写复杂算法的难度和缩短开发周期。相信这也是广大开发人员在遇到大量科学计算时所急待的问题,在论坛上也屡有人问起,笔者参阅了大量资料后归纳整理了几种方案,希望对大家有益^_^

                               Delphi与MATLAB数据接口方案探讨

前言:
    Delphi作为一种功能强大的编程工具,具有易学易用、开发效率高,界面制作美观方便等优点,因此被很多程序员所青睐。Pascal作为历史上第一种结构化的高级语言,在从事复杂算法编写方面也有着诸多优点,可是在软件开发快速运作的今天,用Pascal原始开发一些复杂的算法,不仅编译效率不高而且也影响开发进度,因此Delphi如何与科学计算软件相结合,从而高效地完成编程任务成为一个困扰很多程序员的问题。而Matlab就正是一种高效率的科学工程计算语言,它在矩阵运算、数值计算、数字信号处理、系统识别、自动控制、神经网络、图形显示等方面比其它语言有难以比拟的优势。将Delphi和Matlab相结合,利用Delphi灵活强大方便的编程能力,Matlab强大的科学计算能力就可以开发出功能强大、操作灵活的软件。
    以下给出5类数据接口方案供大家参考:
    (为了便于大家浏览,几种方案将分贴显示)

方案一 采用数据中转方式实现Delphi与Matlab交流.....................................2楼
方案二 基于DDE技术的动态数据交换..................................................3楼
方案三 创造ActiveX对象实现数据交流................................................4楼
方案四 利用动态链接库技术实现二者交流.............................................5楼
方案五 利用mideva编译脱离matlab环境的动态链接库(注:此方法笔者尚未成功).........6楼


 

2003-8-18 21:21:00   
查看评语???   

 2003-8-18 21:22:54   

                      方案一 采用数据中转方式实现Delphi与Matlab交流

一、基本思路

    Matlab输入数据的方法很多,其中利用M文件,直接把数据按元素列表方式引入Matlab工作内存方法,不仅语法简单,而且运行时只要输入文件名,Matlab就会自动按顺序执行M文件中的语句;Matlab数据输出的方法也有很多种,为了便于与Delphi应用程序接口,我们可以利用Matlab的指令Save输出数据。如指令Save outfile.dat x y -ascii -double,可将变量x和y以16位ASCII码形式存入outfile.dat文件。
   基于上述matlab数据输入,输出的方法,我们可以利用M文件为中转,实现Delphi和Matlab的数据交换,当是要注意以下几点:
 (1)由于Matlab的基本数据单位是矩阵,所以在delphi应用程序中我们只有通过文件变量,将参与运算的数据输出成M文本文件,以创建和保存矩阵数据。
 (2)由于Delphi应用程序在前台运行,因此在应用程序中调用Windows函数WinExec,即可执行Matlab.exe。
 (3)由于M文件,只有在Matlab集成环境中才能被识别和自动执行,而根据后台的要求不能显示地进入Matlab集成环境,考虑到Matlab环境变量由matlabrc.m文件定义,因此我们可以通过对matlabrc.m文件的修改(将Matlab要完成的输入数据、进行计算、输出数据过程、编写成M文件,加入matlabrc.m中),从而实现Matlab的后台运作。


二、Delphi如何将数据传递给Matlab

    这里举例说明更直观一些^_^
      例:进行矩阵运算(b'*b) -1 *b'*y', 其中b是t*2矩阵,y是1*t矩阵。

    在Delphi应用程序中,可以采用以下的程序段将参与运算的矩阵b、y保存成M文件,这样Matlab就可以通过执行相应的M文件,获得参与运算的矩阵b、y。应用程序段如下:(程序段中fb、fy是事先定义好的textfile类型的文件变量,b[i,j]、y[i]是事先定义好的数组变量;'c:/matlabfile/delphioutb.m'是保存矩阵b的M文件路径及文件名,'c:/matlabfile/delphiouty.m'是保存矩阵y的M文件路径及文件名)

*********创建保存矩阵b的M文件*********
//注:在Matlab中整个输人矩阵必须以"[]"为其首尾,矩阵的行与行之间必须用分号";"或回车位隔离,矩阵元素必须由逗号","或空格分离。
assign(fb,'c:/matlabfile/delphioutb.m');
Rewrite(Fb);
write(fb,'b[');
for i:=0 to t-2 do    
for j:=0 to 1 do      
  begin              
    Write(Fb,b[imk]);
    if j<1 then Write(Fb,',')
           else if (i<t-2) and (i=1) then write(Fb,';')
                                     else write(fb,']');
  end;
CloseFile(Fb)

*********创建保存矩阵y的M文件*********
assign(fy,'c:/matlabfile/delphiouty.m');
Rewrite(Fy);
write(fy,'y=[');
for i:=1 to t-1 do
  if i<t-1 then write(Fy,y[i],',')
           else write(fy,y[i],']');
CloseFile(Fy);


三、Matlab如何接受应用程序传递来的数据并进行计算

   前面介绍过,由于M文件只有在Matlab集成环境中才能被识别和自动执行,而根据后台运作的要求不能显示地进人Matlab集成环境。但我们可以通过对matlabrc.m文件的修改,将Matlab要完成的操作编写成M文件,加人matlabrc.m中。下面笔者仍依前面例题的计算要求,来说明如何编写Matlab接收数据、进行计算、数据输出的M文件(取名为c:/matlabfile/Mymatlabfile.m).
1、用记事本输入以下代码,保存在自己的目录(如c:/matlabfile)下,取名为Mymatlabfile.m。
 delphioutb //保存矩阵b的M文件
 delphiouty //保存矩阵y的M文件
 m=((b')*b')*(y')
 save c:/matlabfile/matlaboutfile.dat m -ascii -double
 quit
2、打开matlabrc.m将其保存为一个备份文件matlabrcbak.m,然后对原文件进行编辑,将下列语句加在文件最后:
if exist('c:/matlabfile/Mymatlabfile.m')
 Mymatlabfile
end
3、完成上述内容后,只要在应用程序中启动MATLAB,就会完成相应操作。


四、在Delphi中如何启动Matlab

 将前面步骤处理完后,我们可以用以下程序隐式启动Matlab。
copyfile('c:/dsm/matlabfile/Mymatlabrc.m','c:/matlab/toolbox/local/matlabrc.m',false);
winexec('c:/matlab/bin/matlab.exe',SW_MINIMIZE);


五、Delphi如何接受Matlab输出结果

  Matlab将计算结果通过save c:/matlabfile/matlaboutfile.dat m -ascii -double进行输出,所以我们可以通过下面程序获取计算结果,并将结果通过double类型变量mid放进数组a[i]中:(Fa为textfle类型变量)
if FileExists('c:/matlabfile/matlaboutfile.dat') then AssignFile(Fa,'c:/matlabfile/matlaboutfile.dat');
Reset(Fa);
i:=0;
While not Eof(Fa) do
  begin
    read(Fa,mid);
    a[i]:=mid;
    i:=i+1;
  end;
closefile(Fa);
   另外,在应用上述步骤完成接口后,还需要在应用程序中添加以下代码,以还原Matlab原始环境参数设定:
copyfile('c:/matlabfile/matlabrcbak.m','c:/matlab/toolbox/local/matlabrc.m',false);


六、方案总结
   
   此方案利用文件形式进两个应用程序之间的数据传递,方法比较直观,在运行调试的时候可以较方便地传递数据的内容。但是,由于采用数据中转的方式,所以在有大量数据频繁传递的时候,运行速度受到很大影响。

 

 2003-8-18 21:23:57   

                         方案二    基于DDE技术的动态数据交换


一、基本思路

    方案一需要用文件形式来进行两者的数据传递,那么是否有两者直接数据传递的解决方案呢,答案是肯定的。我们知道DDE是基于Windows的一种消息机制,在客户机和服务器程序间通过互相传通消息进行“对话”,它允许两个或两个以上的应用程序之间进行实时的数据交换。根据这点,我们可以利用DDE来实现Delphi和matlab的数据传输。

二、DDE应用协议

    DDE中提出对话的一方称为客户程序(client),而作出反应的一方称为服务器程序(server)。DDE服务器负责维护其他Windows程序所使用的数据,而 DDE客户机则负责从服务器获取数据。应用程序之间的动态数据交换必须遵守DDE协议,DDE协议主要有三层,应用程序层(Application)、对话主题层(Topic)和对话项目层(Item),各层的定义分别为:
    1 应用程序层(Application)
    应用程序层主要是建立对话的通道,DDE对话通常是由客户程序引起的,包括初始化有关参数、准备开始对话等。
    2 对话主题层(Topic)
    对话主题是DDE服务器所能识别的数据单元,通常是一个文件名、窗体名,在Delph 中还可以是Serverconv组件的名字、对于Matiab服务器,主题主要有两个:System和Engine,详见表一。
    3 对话项目层(Item)
    对话项目层是用来确定客户程序和服务器程序进行动态数据交换的内容,如发送数据、接受数据或者数据库中的字段、表格中的单元等.在Delphi中应用程序中,就是DdeServerItem组件的名字。在Matlab中两个主题System和Engine分别包含有Systems,Format,Topics,EngEvalstring,EngSringResult,EngFigureResult等项目,在表一中可以查看详细情况.
    Madab和Delphi都支持DDE技术,既可以作为客户程序。又可以作为服务器程序、这里我们仅以Delph作为客户程序,Matlab作为服务器程序进行探讨,Delphi客户机通过DDE组件提供的函数与Matlab的DDE服务器模块进行对话,完成数据的动态交换.


三、Delphi 的DDE组件

        表1 Matlab DDE服务器的组成
               System主题
        项目:              功能:
        Systems           包含System 主题所支持的全部项目列表
        Format            包含Matlab DDE 服务器所支持的全部数据格式名的字符串列表
        Topics            包含Matlab DDE服务器所支持的全部主题名的列表
               Engine主题
        项目:              功能:
        EngEvalString     发送命令到Matlab服务器中执行
        EngStringResult   从Matlab服务器中请求获取文本格式的数据
        EngFigureResult   从Matlab服务器中请求获取图形格式的数据
        <matrix>          从Matlab服务器中请求获取矩阵数据

    Delphi提供了四个用于DDE的组件:Ddeclicntconv,Ddeclienlitem,Ddeserverconv和Ddeserverconv,其中前两个是用作客户程序,后两个是用作服务器程序.在将Delphi作为客户程序时,必须用到Ddeclientconv和Ddeclientitem组件、Ddeclientconv用于客户端同服务器建立对话和确立对话主题.重要的属性和方法有ConnectMode,Ddeservice,Ddetopic,Setlink,Pokedata.ExecuteMacro 等:Ddeclientitem用于客户端注册对话项目常用的属性和方法有Ddeconv,Ddeltem,Lines,对话时与服务器程序密切相关,向服务器端发送的数据和从服务器端返回的数据往往都在于此组件的lines属性中。它们的具体用法和定义可以从Delphi帮助文件中查到。


四、Matlab DDE服务器

    在将Matlab作为服务器访问时,必须提供服务器的名字、主题和项目.在Win32系统中,Matlab的DDE服务器名为Matlab,支持两个对话主题:System和Engine,每个主题下面有几个项目,通过它们可以完成不同的任务.从表一中可以查着其对应的功能.
    Madab作为服务器时,支持三种数据传输格式:文本格式、元文件图、XLTallle、其中文本格式用于存放空字符结尾的字符率数据格式.元文件图用于存放图形数据的格式,XLTable是为支持Excel所使用的一种特殊的数据格式.


五、Delphi和Matlab的DDE实现过程

    DDE技术已经是非常成熟的windows进程间通信机制,Delphi和Matlab之间动态数据交换中我们将Delphi作为客户机,Matlab用作服务器。首先启动Delphi客户机,确立对话主题,然后连接Matlab服务器并建立DDE对话.在Delphi中,同服务器建立连接有自动和手动两种方式,可以通过declientconv组件的ConnectMode属性进行设置。前者必须调用Ddeclientconv组件的Openlink方法建立DDE对话,后者在连接时可以自动建立对话.确立对话主题并建立对话后 就可以根据不同的项目内容,在Delphi和Matlab之间实现不同数据的动态交换了,如向Matlab服务器发送命令和数据,向服务器请求返回数据,包括图形数据和文本数据等。

六、方案总结
    直接在Matlab与Delphi间实现数据传输,提高了运行速度,但是步骤较为烦杂,有时候会莫名其妙的出错-_-b

 

 2003-8-18 21:24:29   

                         方案三   创造ActiveX对象实现数据交流


一、基本思路  

    ActiveX是由Microsoft制定的一种独立于编程语言的组件集成协议,它不受开发环境的限制。ActiveX控件能够在不同的环境中使用,包括Delphi,C++Builder,VC++,VB和Internet Explore等。Matlab和Delphi都支持ActiveX技术,因此,可以利用ActiveX自动化服务器和自动化控制器技术,实现Matlab 和 Delphi接口。

二、Matlab 自动化服务器

    自动化服务器是一种可以由其他应用程序编程驱动的组件,其核心是要包含一个或多个供其他程序创建和连接的基于IDispatch的接口。Matlab作为自动化服务器时,可以被Windows平台上任何作为自动化控制器的应用程序使用。Matlab ActiveX对象在系统注册表中定义的名字(ProgID)为Matlab.Application.5 或者Matlab.Application.Single。用Matlab.Application.5作为ProgID创建Matlab自动化服务器时,系统将不再另外初始化其他服务器,而是作为共享服务器完成所有的论求。使用Matlab.Application.Single时,Matlab自动化服务器将作为一个单独的服务器使用,而不与其他程序共享。对Matlab自动化服务器的使用,系统提供了五个函救:Excute,PutFullMatrix,GetFullMatrix,MinimizeCommandWindow,MaximizeCommandWindow。其中,
Excute用于执行Matlab命令,PutFullMatrix和GetFullMatrix用于数据传递,MinimizeCommandWind和MaximizeCommandWindow用于界面窗口煤作。


三、Delphi自动化控制器

   Delphi5提供两个函数用于ActiveX接口和对象编程:GetActiveOleObject和CreateOleObject,其格式为:
    function GetActiveOleObject(const ClassName:string):IDispatch;
    function CreateOleObject(const ClassName:string):IDispatch;
其中GetActiveOleObject用于访问当前运行的ActiveX对象表,返回指定的ActiveX对象;CreateOleObject用来创建指定的、宋初始化的ActiveX对象。可以通过调用这两个函数,创建一个Matlab 自动化服务器的对象实例。


四、实例程序

    这里给出一个具体的例子来说明在Delphi中利用 ActiveX术实现Matlab的自动化。首先在unit的uses语句中加入uses ComObj。然后加入下列语句:
      procedure TForm1.Button1Click(Sender:Tobject);
      var matlab:variant;
      begin
        try
         matlab:=GetActiveOleObject('Matlab.Application');
        except
         matlab:=CreateOleObject('Matlab.Application');
         matlab:=CreateOleObject('Matlab.Application.5');
         matlab.execute('a=[1 2 3 4 5 6 7 8];');
         matlab.execute('b=[2 3 4 4 5 6 2 2]');
         matlab.execute('plot(a,b)');
        end
      end;
 

五、方案总结
   
    此方案采用win32程序下较为通用的ActiveX方式进行数据交换,实现起来简单明了。但是需要注意,每次程序运行的时候,在创建对象的时候会花费很长的一段时间,会生成一个matlab窗口(这个令人很不爽!)。笔者还遇到一个问题,不知道是不是特例,就是同样的图像数据在Matlab下运行后的结果和通过Delphi运行后的结果有时候有点不一样,比如我现在正在搞的图像加密,在两个环境加密后的图像质量就是不同(算法一样),莫名其妙……

 

 2003-8-18 21:25:18   

                        方案四 利用动态链接库技术进行数据交流


一、基本思路

    Matlab本身并没有提供与Delphi的应用程序接口,但是提供了基于win32平台的VC++应用程序接口,包括Matlab引擎(engine),C/C++函数库。可以利用Matlab与VC++的接口函数,通过matlab引擎进行指令处理和数据传递,编写出C++语言的动态连接库,作为Matlab与Delphi的接口,然后在Delphi中进行C++动态库函数的调用。Matlab引擎的C语言调用函数见表1。

表1:Matlab引擎的C语言调用函数
函数名                               功  能
EngOpen                           打开Matlab引擎
EngEvalString                     在Matlab引擎中执行一条命令        
EngPutArray                       将数据放入Matlab引擎
EngGetArray                       从Matlab引擎中取出数据
EngClose                          关闭Matlab引擎

    在使用表1所示的Matlab引擎函数时,要用到Matlab数组(mxArray),Matlab引擎利用这个数据类型来进行各种数据处理工作。Matlab中的各种变量,包括标量、矢量、矩阵、字符串等都是用这种Matlab数组来进行存储的。Matlab提供了C语言下的Matlab数组(mxArray)的格式,定义了它的数据结构。Delphi下没有对应的Matlab数组,但利用动态连接库可以巧妙地解决这个问题。


二、动态连接库的设计

    在我们设计的软件中,要将Delphi中的数据送入Matlab进行分析,选择合适的处理函数后进行运算,然后将结果送回Delphi。为了实现上述目的,设计了相应的几个函数。这些函数都放在DpLab.dll动态连接库中。DpLab.dll的基本组成如下:
(1)头函数
#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "engine.h"  //在Matlab/extern/include下,将它拷入当前目录
engine *ep=NULL;     //Matlab引擎,全局变量
mxArray *PA=NULL;    //Matlab数组指针。可根据需要定义多个mxArray指针变量

(2)打开Matlab引擎函数:
extern"C" declspec(dllexport)int MatOpenEng(far char *cc)
{
  if(ep) return 0;//如果已打开则退出
  if(!(ep=engOpen(cc))) return -1;
  else return 1;
}
如果Matlab安装在本地计算机上,字符串cc中的内容为“/0”,如果系统为客户机/服务器(Client/Server)的工作方式,Matlab安装在主机“host”上,可输入'//host/Matlib'。

(3)关闭Matlab引擎的函数
extern"C" declspec(dllexport)int MatCloseEng(void)
{
 if(ep)
   {
    if(PA) mxDestroyArray(PA);//释放PA
    engClose(ep);
    ep=NULL;
    return1;
   }
  else return0;
}

(4)执行Matlab命令函数:
extern"C" declspec(dllexport)int MatExec(far char *cc)
{
 if(!ep)return0;
 engEvalString(ep,cc);//cc中放Matlab命令return 1;
}

(5)根据已知数据创建mxArray变量:
//注:cc:Matlab变量名  num:数据的个数 dd:已知数据
extern"C" declspec(dllexport)int MatCreateDoubleD(char *cc,int num,double dd[])
{
 if(!ep) return 0;
 if(num<1) return -1;
  PA=mxCreatedoubleMatrix(1,num,mxREAL);
  mxSetName(PA,fh[Getfh(cc)]);//指定Matlab中变量名,它必须是静态字符串!
  memcpy((char *)mxGetPr(PA),(char *)dd,num * sizeof(double));
  engPutArray(ep,PA);
  return1;
}


(6)获取Matlab数据:
//注:cc:Matlab变量名 num:数据的个数 dd:返回数据
extern"C" declspec(dllexport)int MatGetdoubleData(char *cc,int num,double dd[])
{
  mxArray result;
  double pData;
  int i;
  if(!ep)return0;
  if(num<1)return-1;
  result=engGetArray(ep,fh[Getfh(cc)]);  
  if(result)
    {  
     pData=(double )mxGetData(result);        
     for(i=0;i<num;i++) dd[i]=pData[i];  
     mxDestroyArray(result);  
     return1;
    }
  return 0;
}
还可以根据需要定义其它一些函数如获取变量名函数intGetfh(charcc[])等等。这些函数写好以后,可以用VC进行编译,将编译好的动态连接库DpLab.dll复制到Windows/system下。


三、各种函数在Delphi中的运用

   1、为了在Delphi中使用动态连接库函数,首先要对这些函数作如下声明:
function MatOpenEng(p:PChar):Integer;stdcall;external'DpLab.dll';
function MatCloseEng:Integer;stdcall;external'DpLab.dll';
function MatExec(p:PChar):Integer;stdcall;external'DpLab.dll';
function MatCreateDoubleD(p:Pointer;num:Integer;dd:Array of double):Integer;stdcall;external'DpLab dll';
function MatGetdoubleData(p:pointer;num:Integer;dd:Array of double):Integer;stdcall;external'DpLab dll';
函数声明了以后,就可以方便地使用它们了。
打开Matlab引擎用:MatOpenEng(PChar(/0'));
关闭Matlab引擎用:MatCloseEng;

   2、下面的程序给出了从Matlab中获取数据的方法,函数MatGetDoubleData通过变量cc指定Matlab中的数据变量名,该变量应是已经定义好的:
procedure TForm1Button2Click(Sender:Tobject);
var dd:Array[0..10] of double;cc:char;i:integer;
begin
    cc:='D';//Matlab中的变量名
    MatGetDoubleData(@cc,10,dd);
    ListBox1.clear;//在一个列表框内显示读出的数据
    for i:=0 to 4 do ListBox1.items.add(format('%.2f,[dd[i]]));
end;

  3、下面给出Delphi在Matlab中用sin(r)/r函数画出三维图形的程序,其余情况大家触类旁通吧^_^
procedure TForm1Button3Click(Sender:Tobject);
begin
    MatExec(PChar('x=-8: 5:8;'));
    MatExec(PChar('y=x',';'));
    MatExec(PChar('X=ones(size(y))*x;'));
    MatExec(PChar('Y=y*ones(size(x));'));
    MatExec(PChar('R=sqrt(X.^2+Y.^2)+eps;'));
    MatExec(PChar('Z=sin(R)/R;'));
    MatExec(PChar('mesh(Z);'));
end;


四、方案总结

    其实思路和上一种方案很像,不同的是是通过调用dll中的matlab应用程序接口指令实现数据传输,本方案所提供的动态连接库实际上是一个示范性的例子,根据实际需要还可以开发出功能更强大的函数,以满足不同应用场合的需要。就是要花费一定时间编写相关的dll咯^_^

 

 2003-8-18 21:25:54   

                    方案五 利用mideva编译脱离matlab环境的动态链接库
               
           
一、基本思路

    如果你看过了以上的几种方案,就会发现它们都必需依赖于MATLAB环境,那么如何能够彻底脱离MATLAB环境呢?这也是我最近一直在研究的,但是天不随人意5555555555,虽然找到了相关资料可是却和实际情况相差太远。本来想等全部搞定之后再发这篇文章的,后来转念一想不如让大家共同研究,也许某位达人能参透其中奥妙。此方法是利用一个叫Mideva的软件,是Mathtools公司推出的一种Matlab编译开发软件平台(注:据笔者掌握的最新情况,Mathtools已经被Mathwork收购了),Mediva具有编译转换功能,能够将Matlab函数成编写的Matlab程序转换为c++形式的DLL,然后在Delphi中调用动态库函数,而不必再依赖Malab环境,前提是必须要有两个动态链接库mdv4300和ago4300。


二、编译步骤

利用Mideva平台实现Delphi与M文件混合编程步骤如下:
   1、编写M文件
   2、在Mideva中执行M文件
   3、生成动态链接库(DLL)
   4、Delphi中调用DLL函数
   

三、方案总结
    如果真能实现,那将省去许多不必要的麻烦,可是笔者的问题在于用Mideva编译过后的DLL,Delphi无法识别-_-b,而且根本找不到mdv4300和ago4300两个动态链接库。如果那位仁兄能够成功,望请告知,不胜感激。
   

 

 2003-8-18 21:26:46   

后记:

    Matlab和Delphi都是功能强大的计算机语言,利用接口技术可以充分利用Delphi高效、便捷的可视化开发环境和Matlab在数值计算、算法设计等领域的优势,提高程序的开发效率,大量节省在繁杂数学运算编程上花费的时间,能够快速开发出界面友好的算法程序和数值分析、数据处理软件。同时应该看到各种接口方案都有其不足之处,还有待大家共同研究。
   
    我的EMAIL,欢迎大家共同探讨:yellowfish2001@sina.com

原创粉丝点击