Tuesday, May 6, 2008

DAO and MFC

This article describes MFC''''s implementation of Microsoft Data Access Objects (DAO). Topics covered include:

Note Whether you use the MFC DAO classes or the MFC ODBC classes depends on your situation and your needs. For a discussion of the differences between the two and guidance on choosing one, see the article Database Topics (DAO).

How MFC Encapsulates DAO

The MFC DAO classes treat DAO much as the MFC classes for programming Windows treat the Windows API: MFC encapsulates, or "wraps," DAO functionality in a number of classes that correspond closely to DAO objects. Class CDaoWorkspace encapsulates the DAO workspace object, class CDaoRecordset encapsulates the DAO recordset object, class CDaoDatabase encapsulates the DAO database object, and so on.

MFC''''s encapsulation of DAO is thorough, but it is not completely one-for-one. Most major DAO objects do correspond to an MFC class, and the classes supply generally thorough access to the underlying DAO object''''s properties and methods. But some DAO objects, including fields, indexes, parameters, and relations, do not. Instead, the appropriate MFC class provides an interface, via member functions, through which you can access, for example:

  • The fields of a recordset object
  • The indexes or fields of a table
  • The parameters of a querydef
  • The relations defined between tables in a database

Mapping of DAO Objects to MFC Classes

The following tables show how DAO objects correspond to MFC objects. MFC Classes and Corresponding DAO Objects shows the MFC classes and the DAO objects they encapsulate. How MFC Manages DAO Objects Not Mapped to Classes shows how MFC deals with DAO objects that do not map directly to an MFC class.

Note MFC now supports DAO 3.5. MFC DAO classes work either with DAO 3.0 or DAO 3.5, but have not been designed to take advantage of any new DAO 3.5 features, including ODBCDirect.

MFC Classes and Corresponding DAO Objects

ClassDAO objectRemarks
CDaoWorkspaceWorkspaceManages a transaction space and provides access to the database engine.
CDaoDatabaseDatabaseRepresents a connection to a database.
CDaoTableDefTabledefUsed to examine and manipulate the structure of a table.
CDaoQueryDefQuerydefUsed to store queries in a database. You can create recordsets from a querydef or use it to execute action or SQL pass-through queries.
CDaoRecordsetRecordsetUsed to manage a result set, a set of records based on a table or selected by a query.
CDaoExceptionErrorMFC responds to all DAO errors by throwing exceptions of this type.
CDaoFieldExchangeNoneManages exchange of data between a record in the database and the field data members of a recordset.

How MFC Manages DAO Objects Not Mapped to Classes

DAO objectHow MFC manages it
FieldObjects of classes CDaoTableDef and CDaoRecordset encapsulate fields and supply member functions for adding them, deleting them, and examining them.
IndexObjects of classes CDaoTableDef and CDaoRecordset encapsulate indexes and supply member functions for managing them. Tabledefs can add, delete, and examine indexes. Tabledefs and recordsets can set or get the currently active index.
ParameterObjects of class CDaoQueryDef encapsulate parameters and supply member functions for adding them, deleting them, examining them, and getting and setting their values.
RelationObjects of class CDaoDatabase encapsulate relations and supply member functions for adding them, deleting them, and examining them.

DAO Objects Not Exposed in MFC

MFC and DAO do not supply abstractions for some objects used within Microsoft Access: Application, Container, Control, Debug, Document, Form, Module, Report, Screen, and Section. If you create a Microsoft Access database and manipulate it from an MFC application, you can''''t access those objects through code.

MFC doesn''''t supply classes or interfaces to the DAO group and user objects — to work with DAO security, you must write your own code.

MFC also doesn''''t encapsulate DAO property objects, except that the MFC DAO classes do give you access to the properties of all exposed objects.

MFC does give you access to DAO''''s DBEngine object, through class CDaoWorkspace.

Accessing the Unexposed DAO Objects

The unexposed objects listed above can be accessed in two ways:

  • Outside the MFC classes by using the non-MFC C++ classes provided in the DAO SDK.
  • Inside the MFC classes by calling DAO directly through a DAO interface pointer supplied by one of the MFC classes. For information, see Technical Note 54.

Key Differences Between MFC and DAO

MFC''''s version of data access objects differs from the underlying structure of DAO in some ways.

How MFC Accesses the Database Engine

DAO has a DBEngine object that represents the Microsoft Jet database engine. The DBEngine object provides properties and methods you can use to configure the database engine.

In MFC, there is no DBEngine object. Access to important properties of the database engine is supplied via class CDaoWorkspace. To set or get these properties, call any of the static member functions of CDaoWorkspace. For more information, see the articles DAO Workspace: The Database Engine and DAO Workspace: Accessing Properties of the Database Engine.

MFC Flattening of the DAO Object Hierarchy

Because MFC doesn''''t supply a class for every DAO object, the effect is that the DAO object hierarchy is somewhat "flattened" in MFC. The main examples of this flattening are:

MFC and DAO Security

MFC does not encapsulate the DAO user and group objects in any way, which means that MFC doesn''''t provide DAO''''s security functionality.

You can still use DAO security from your MFC applications, but you will have to call DAO directly, using the m_pDAOWorkspace data member of class CDaoWorkspace. That member is a pointer to an interface that gives access to a DAO workspace object''''s methods and properties. For information about calling DAO directly, see Technical Note 54.

MFC does allow password protection via various MFC classes. For example, when you create a CDaoWorkspace object, you can specify a password to protect the database(s) that the workspace contains. To use this functionality, a SYSTEM.MDW file must be available to the database engine on the machine running your application. If no SYSTEM.MDW file is available to the database engine, your application cannot use any of the security features. For information about the SYSTEM.MDW file, see the topic "Permissions Property" in DAO Help.

Further Reading About the MFC DAO Classes

To learn more about using the MFC DAO classes, see the following articles (in the order listed here):

Tip From any of the MFC help topics in this documentation set, you can get to a topic called DAO: Where Is..., which helps you navigate online to the topics that you need. The topic is always available via the See Also button in the topic window.

Monday, May 5, 2008

DAO


DAO (Data Access Objects) 数据访问对象是第一个面向对象的接口,它显露了 Microsoft Jet 数据库引擎(由 Microsoft Access 所使用),并允许 Visual Basic 开发者通过 ODBC 象直接连接到其他数据库一样,直接连接到 Access 表。DAO 最适用于单系统应用程序或小范围本地分布使用。

DAO就是Database Access Objects,数据访问对象的英文缩写。在VB中提供了两种与Jet数据库引擎接口的方法:Data控件和数据访问对象(DAO)。Data控件只给出有限的不需编程而能访问现存数据库的功能,而DAO模型则是全面控制数据库的完整编程接口。Data控件将常用的DAO功能封装在其中,它与DAO控件的关系就好象内存与CACHE之间的关系一样,所以这两种方法并不是互斥的,实际上,它们常同时使用。

DAO数据访问对象(DataAccessObjects)是一种面向对象的界面接口。通过DAO/Jet功能可以访问ISAM数据库,使用DAO/ODBCDirect功能可以实现远程RDO功能。使用DAO的程序编码非常简单,DAO提供丰富的游标(Cursor)类型的结果集和非游标(Cursor-Less)类型的结果集,同DDL(数据描述语言)的功能很类似。现在vb数据库编程大都使用ADO,很少人还用DAO.

DAO是集合,对象,方法和属性;它用对象集合来处理数据库,表,视图和索引等。使用DAO编程,可以访问并操作数据库,管理数据库的对象和定义数据库的结构等。

DAO模型是设计关系数据库系统结构的对象类的集合。它们提供了完成管理一个关系型数据库系统所需的全部操作的属性和方法,这其中包括创建数据库,定义表、字段和索引,建立表间的关系,定位和查询数据库等。

Visual Basic中的数据库编程就是创建数据访问对象,这些数据访问对象对应于被访问物理数据库的不同部分,如数据库、表、字段和索引等,同时用这些对象的属性和方法来实现对数据库的操作,以便在Visual Basic窗体中使用绑定和非绑定控件来显示操作结果并接收用户输入。

Visual Basic通过DAO和Jet引擎可以识别三类数据库:

■ VisualBasic数据库

也就是*.MDB数据库

■ 外部数据库

它们是使用几种流行格式的“索引顺序访问方法(ISAM)”数据库,这些流行格式包括Btrieve、dBASEIII、dBASEIV、 Microsoft FoxPro versions2.0和2.5以及Paradox versions 3.x和4.0。在Visual Basic中能够创建和操作所有以上格式的数据库。也可以访问文本文件数据库和Microsoft Excel或Lotus1-2-3电子表格。

■ ODBC数据库

包括符合ODBC标准的客户/服务器数据库,如Microsoft SQL Server。要在Visual Basic中创建真正的客户/服务器应用程序,可以使用ODBC Direct直接把命令传递给服务器处理。也就是说Visual Basic还支持其它不使用Jet数据库引擎的数据访问方法。数据访问对象的ODBC Direct模式允许使用同样的对象模型和语法绕过Jet引擎直接访问ODBC数据。

由上面的叙述可知,对于编程人员来讲,你只须了解DAO的使用,对具体的数据库系统无须做深入的探讨,就可对几乎任何一种数据库进行操作,“以不便应万变”,而对数据库文件自身的物理操作,将由相应的数据库引擎把数据访问对象上的这些操作转换为对数据库文件自身的物理操作,这些引擎来处理所有与各种数据库的接口。

一、数据访问对象DAO的结构

上一章我们知道了DAO是什么,它可以干什么,以及简单的了解了它是如何实现这一功能,这一章我们讲逐步学习DAO的结构。在将讲这一章之前,首先讲两个题外话:

面向对象编程

大家对面向对象编程一定不陌生,但究竟什么是面向对象编程,很多人一下子也说不出个所以然来;在VB的DAO中又是如何体现面向对象的呢,恐怕即便是使用了一段VB的网友也很难说清。这也难怪,VB本身并不是个完全的面向对象的编程环境,它的很多如动态数组等就不是面向对象的,但在DAO中是完全的面向对象的。其实面向对象的最初思想就是将数据和操作封装在一起,形成对象,而在DAO中正是体现了这一点,它将数据的值作为属性,数据的查询作为方法,数据值的变化作为事件,完全封装在DAO对象中,希望诸位盟友在使用中注意,逐步领悟面向对象的编程方法。

数据库前端开发系统和后台系统

经常由朋友说他会DELPHI,会PB,实际上用前端和后台的概念来看,他们都只是会一种前端开发工具而已,严格说起来并不能算会数据库的开发。人们往往只看到前台应用的功能而忽视了后台系统的应用,或者认为这两者根本就是一回事。前端后台概念用在单机上,就是VB与Access,用在网络中就是VB与SQL Sever,只会VB或DELPHI是不能称为掌握数据库编程的,而且在编程中,要善于利用后台数据库提供功能,这样的程序运行起来更快,程序更精练。

题外话讲完,现在切入正题。DAO的结构如图所示:
 

以上是DAO整体结构图,下面将分章节逐步讲述每个对象的使用


二、数据访问对象DAO的功能

1. DAO用来封装Data Source的..就比如,Connection conn = DAOFacotry.createConnection()..
就可以把Driver. URL. username, passpword这一些放在DAO中
以后要更改数据库的类型.比如要把MSSQL换成Oracle的话..只需要更改DAOFacory里面的getConnection()里面的Driver.URL.之类的..

2. DAO也是把对数据库的操作(比如最基本的CRUD操作)全部封装在里面..
比如说你要你要插入一个新的用户..那么.在DAO中我们只需要提供一个insertUser(User user)这一个方法就可以了..具体的操作是在DAO中实现的...
那么对于要调用DAO的时候.我们只要知道insertUser(User)是用来插入一个新的用户...而不需要知道是如何实现的..


一般 DAO是与Abstract Factory模式一起来用的...

Factory来建立数据库和定位具体的DAO(比如说是UserDao.. CustomerDao..)..一般将getConnection设置为static..也可以把HibernateSessionFactory这一个公共类放在这一AbstractFactory类中去...

public class DAOFactory {
private static final SessionFactory sessionFacotory;
// 定义一个TrheadLocal .
static Session currentSession().....
public UserDao getUserDAO() { return new UserDaoImpl(sesssion);}
pulbic OtherDao getOtherDAO() { return new OtherDaoImpl(session);}
......
}

public interface UserDao {
public insertUser(FormBean)
public updateUser(FormBean);
}
然后就实现DAO的接口: (Struts的FormBean...VO来的..)
public class UserDaoImpl implements UserDao {
privateSession session;
public UserDaoImpl(Session session){
this.session = session;
}...
public insertUser(FormBean) {
..//..
session.save(UserPO);
..//..
return FormBean;
}
public FormBean updateUser(FormBean) {
..//..
session.update(UserPO);
..//..
return FormBean;
}
}
最后定义你的PO:
public class UserPO {
String firstname, lastname, password..........
}


Huhmmmm..........

(Copyright 2003-2005 www.sohozu.com All Rights Reserved)

三、实战应用——使用数据访问对象DAO

1 概述

Visual C++提供了对DAO的封装,MFC DAO类封装了DAO(数据库访问对象)的大部分功能,从面Visual C++程序就可以使用Visual C++提供的MFC DAO类方便的访问Microsoft Jet 数据库,编制简洁、有Visaul C++特色的数据库应用程序。

数据库访问对象(DAO)提供了一种通过程序代码创建和操纵数据库的机制。多个DAO对象构成一个体系结构,在这个结构里,各个DAO对象协同工作。DAO支持以下四个数据库选项:


打开访问数据库(MDB文件)——MDB文件是一个自包含的数据库,它包括查询定义、安全信息、索引、关系,当然还有实际的数据表。用户只须指定MDB文件的路径名。

直接打开ODBC数据源——这里有一个很重要的限制。不能找开以Jet引擎作为驱动程序的ODBC数据源;只可以使用具有自己的ODBC驱动程序DLL的数据源。

用Jet引擎找开ISAM型(索引顺序访问方法)数据源(包括dBase,FoxPro,Paradox,Btrieve,Excel或文本文件)——即使已经设置了ODBC数据源,要用Jet引擎来访问这些文件类型中的一种,也必须以ISAM型数据源的方式来找开文件,而不是以ODBC数据源的方式。

给ACCESS数据库附加外部表——这实际上是用 DAO访问ODBC数据源的首选方法。首先使用ACCESS把ODBC表添加到一个MDB文件上,然后依照第一选项中介绍的方法用DAO找开这个MDB文件就可以了。用户也可以用ACCESS把IASM文件附加到一个MDB文件上。

2 应用DAO编程

2.1 打开数据库
CDaoWorkspace对象代表一个DAO Workspace对象,在MFC DAO体系结构中处于最高处,定义了一个用户的同数据库的会话,并包含打开的数据库,负责完成数据库的事务处理。我们可以使用隐含的workspace对象。

CDaoDatabase对象代表了一个到数据库的连接,在MFC中,是通过CDaoDatabase封装的。

在构造CDaoDatabase对象时,有如下两种方法:

创建一个CDaoDatabase对象,并向其传递一个指向一个已经找开的CdaoWorkspace对象的指针。

创建一个CDaoDatabase对象,而不明确地指定使用的workspace,此时,MFC将创建一个新的临时的CDaoWorkspace对象。

如下代码所示:

CDaoDatabase db;

db.Open(“test.mdb”,FALSE,FALSE,_T(“”);

其中参数一包括要打开的文件的全路径名。


2.2 查询记录
一个DAO recordset对象,代表一个数据记录的集合,该集合是一个库表或者是一个查询的运行结果中的全部记录。CDaoRecorset对象有三种类型:表、动态集、快照。

通常情况下,我们在应用程序中可以使用CDaoRecordset的导出类,这一般是通过ClassWizard或AppWizard来生成的。但我们也可以直接使用CDaoRecordset类生成的对象。此时,我们可以动态地绑定recordset对象的数据成员。

如下代码所示:

COleVariant var;

long id;

CString str;

CDaoRecordset m_Set(&db);

m_Set.Open(“查询的SQL语句”);

while(!m_Set.IsEOF())

{
/*处理
m_Set.GetFieldValue(“ID”,var);

id=V_I4(var);
m_Set.GetFieldValue(“Name”,var);
str=var.pbVal;
*/
m_Set.MoveNext();

}

m_Set.Close();



2.3 添加记录

添加记录用AddNew函数,此时用SetFieldValue来进行赋值。

如下代码所示:

m_pDaoRecordset->AddNew ();
sprintf(strValue,"%s",>m_UserName );
m_pDaoRecordset->SetFieldValue ("UserName",strValue);
sprintf(strValue,"%d",m_PointId );
m_pDaoRecordset->SetFieldValue ("PointId",strValue);
dataSrc.SetDateTime (m_UpdateTime .GetYear ), m_UpdateTime .GetMonth ), m_UpdateTime .GetDay (),
m_UpdateTime .GetHour (),m_UpdateTime .GetMinute (),m_UpdateTime .GetSecond ());
valValue=dataSrc;
m_pDaoRecordset->SetFieldValue ("UpdateTime",valValue);
sprintf(strValue,"%f",m_pRecordset->m_OldValue );
m_pDaoRecordset->SetFieldValue ("OldValue",strValue);
sprintf(strValue,"%f",m_pRecordset->m_NewValue );
m_pDaoRecordset->SetFieldValue ("NewValue",strValue);
m_pDaoRecordset->Update ();

此时,要注意,日期时间型数据要用SetDataTime函数来赋值,这里面要用到COleVariant类型数据,具体用法可以参考有关帮助。



2.4 修改记录

修改记录用Edit()函数,把记录定位到要修改的位置,调用Edit函数,修改完成后,调用Update函数。

如下代码所示:

m_Set.Edit();
m_Set.SetFieldValue(“列名”,”字符串”);
m_Set.Update();

2.5 删除记录

删除记录用Delete()函数,使用后不需调用Update()函数。


2.6 统计记录

可以使用如下代码来统计记录数:

COleVariant varValue;
CDaoRecordset m_Set(&db);
m_Set.Open(dbOpenDynaset,”SQL语句”);
varValue=m_Set.GetFieldValue(0);
m_lMaxCount=V_I4(&varValue);
m_Set.Close();


如果是统计一张表中总记录,可以使用CDaoTableDef对象,如下代码所示:
CDaoTableDef m_Set(&gUseDB);
Count=m_Set.GetRecordCount();
m_Set.Close();

不能用CDaoRecordset对象的GetRecordCount()来取得记录数。

3 总结

使用DAO技术可以便我们方便的访问Microsoft Jet引擎数据库,由于Microsoft Jet不支持多线程,因此,必须限制调用到应用程序主线程的所有DAO。(来源CNET Networks )
从功能简单的数据库(如Jet Engine)到复杂的大型数据库系统(如oracle),VC++6.0都提供了一些编程接口。本文主要介绍以下五种:

1.ODBC API;

2.MFC ODBC类;

3.MFC DAO类;(数据访问对象)

4.MFC的OLE/DB;

5.ActiveX数据对象(ADO)。

1.开放数据库连接(ODBC API):

提供了一个通用的编程接口,允许程序与多种不同的数据库连接。它为Oracle, SQL Server,MS Excel等都提供了驱动程序,使得用户可以使用SQL语句对数据库进行直接的底层功能操作。在使用ODBC API时,用户须引入的头文件为"sql.h","sqlext.h","sqltypes.h"。用ODBC API创建数据库应用程序遵循一定的基本步骤:

第一步是分配ODBC环境,使一些内部结构初始化。完成这一步,须分配一个SQLHENV类型的变量在ODBC环境中做句柄使用。

第二步是为将要使用的每一个数据源分配一个连接句柄,由函数SQLALLocHandle()完成。

第三步是使用SQLConnect()把连接句柄与数据库连接,可以先通过SQLSetConnectAttr()设置连接属性。

然后就可以进行SQL语句的操作,限于篇幅,相关的函数就不具体介绍了,读者可以参考相关书籍。

操作完成后,用户取回相应的结果,就可以取消与数据库的连接。

最后需要释放ODBC环境。

ODBC API的特点是功能强大丰富,提供了异步操作,事务处理等高级功能,但相应的编程复杂,工作量大。

2.MFC ODBC类:

MFC1.5后的版本里引入封装了ODBC功能的类。通过这些类提供与ODBC的接口,使得用户可以不须处理ODBC API中的繁杂处理就可以进行数据库操作。主要的MFC ODBC类如下。

CDatabase类:一个CDatabase对象表示一个到数据源的连接,通过它 可以操作数据源。应用程序可使用多个CDatabase对象:构造一个对象并调用OpenEx()成员函数打开一个连接。接着构造CRecordSet对 象以操作连接的数据源,并向CDatabase对象传递记录集构造程序指针。完成使用后用Close()成员函数销毁CDatabase对象。一般情况下 并不需要直接使用CDatabase对象,因为CRecordSet对象可以实现大多数的功能。但是在进行事务处理时,CDatabase就起到关键作 用。事务(Transaction)指的是将一系列对数据源的更新放在一起,同时提交或一个也不提交,为的是确保多用户对数据源同时操作时的数据正确性。

CRecordSet类:一个CRecordSet对象代表一个从数据源选择的一组 记录的集合-记录集。记录集有两种形式:snapshot和dynaset。前者表示数据的静态视图,后者表示记录集与其他用户对数据库的更新保持同步。 通过CRecordSet对象,用户可以对数据库中的记录进行各种操作。

CRecordView类:CRecordView对象是在空间中显示数据库记录的 视图。这种视图是一种直接连到一个CRecordSet对象的格式视图,它从一个对话框模板资源创建,并将CRecordSet对象的字段显示在对话框模 板的控件里。对象利用DDX和RFX机制,使格式上的控件和记录集的字段之间数据移动自动化,也就是说,用户甚至不要编写一行代码就可以实现简单的数据库 记录查看程序。

CDBException类:由Cexception类派生,以三个继承的成员变量反映对数据库操作时的异常:

m_nRetCode:以ODBC返回代码(SQL_RETURN)的形式表明造成异常的原因。

m_strError:字符串,描述造成抛出异常的错误原因。

m_strStateNativeOrigin:字符串,用以描述以ODBC错误代码表示的异常错误。

MFC数据库类成员函数都能抛出CDBException类型的异常,所以在代码对数据库进行操作后监测异常是正确做法。

MFC ODBC类在实际开发中应用最广,因为它功能丰富,操作相对简便。

3.MFC DAO(数据访问对象)编程:

DAO用于和微软的Access数据库接口。在数据库应用程序如果只需与Access数据库接口时,使用DAO编程较方便。其主要类如下。

CDaoWorkspace:CDaoWorkspace对象可以让一个用户管理从登陆到离开期间,指定的密码保护的数据库会话全过程。大多数情况下不要多个工作区也不要创建明确的工作区对象。因为在打开数据库和记录集对象时,它们可以使用DAO缺省工作区。

CDaoDatabase:代表一个连接,类似上述CDatabase类。

CDaoRecordSet:用来选择记录集并操作,类似上述CRecordSet类。

CDaoRecordView:类似上述CRecordView类。

CDaoException:类似上述CDBException类。

CDaoTableDef:表示基本表或附加表的定义。每个DAO数据库对象包括一个称为TableDef的收集,包含所有存储的DAO表定义对象。CDaoTableDef对象可以用来控制表定义。

CDaoQueryDef:CDaoQueryDef对象表示了一个查询定义(querydef)。

CDaoFieldExchange:支持数据库类使用的DAO字段交换(DFX)例程。也可处理事务,类似MFC ODBC类。

MFC DAO仅用来支持Access数据库,应用范围相对固定。

4.OLE DB:

OLE DB在数据提供程序和用户之间提供了灵活的组件对象模型(COM)接口,这种灵活性有时会使得操作复杂化。OLE DB框架定义了应用的三个基本类。

数据提供程序Data Provider:拥有自己的数据并以表格形式显示数据的应用程序。提供OLE DB的行集COM接口,期显示范围可以从单一数据表格的简单提供者知道更复杂的分布式数据库系统。

使用者Consumers:使用OLE DB接口对存储在数据提供程序中的数据进行控制的应用程序。用户应用程序归为使用类。

服务提供程序Service Provider:是数据提供程序和使用者的组合。服务提供程序没有自己的数据,但使用OLE DB使用者接口来访问存储在数据提供程序中的数据。然后,服务提供程序通过打开数据提供程序接口使得数据对使用者有效。服务提供程序常用于向应用程序提供 高层次服务,比如高级分布式查询。

OLE DB编程时,用户使用组件对象开发应用程序。这些组件有:

枚举器:用于列出可用的数据源;

数据源:代表单独的数据和服务提供程序,用于创建对话;

对话:用于创建事务和命令;

事务:用于将多个操作归并为单一事务处理;

命令:用于向数据源发送文本命令(SQL),返回行集;

错误:用于获得错误信息。

5.ActiveX数据对象(ADO):是微软提供的面向对象的接 口,与OLE DB类似,但接口更简单,具有更广泛的特征数组和更高程度的灵活性。ADO基于COM,提供编程语言可利用的对象,除了面向VC++,还提供面向其他各种 开发工具的应用,如VB,VJ等。ADO在服务器应用方面非常有用,特别是对于动态服务器页面ASP(Active Server Page)。

ADO对象结构类似于OLE DB,但并不依靠对象层次。大多数情况下,用户只需要创建并只使用需要处理的对象。下面的对象类组成了ADO接口。

Connection:用于表示与数据库的连接,以及处理一些命令和事务。

Command:用于处理传送给数据源的命令。

Recordset:用于处理数据的表格集,包括获取和修改数据。

Field:用于表示记录集中的列信息,包括列值和其他信息。

Parameter:用于对传送给数据源的命令之间来回传送数据。

Property:用与操作在ADO中使用的其他对象的详细属性。

Error:用于获得可能发生的错误的详细信息。

在VC++使用ADO需要进行COM操作,详细方法在此就不赘述了。

在当今流行的分布式开发环境下,VC++6.0在数据库开发方面有较强的优势,学会在不同的场合选用不同的技术,对开发人员来说是必要的技术。

(T113)

从功能简单的数据库(如Jet Engine)到复杂的大型数据库系统(如oracle),VC++6.0都提供了一些编程接口。本文主要介绍以下五种:

1.ODBC API;

2.MFC ODBC类;

3.MFC DAO类;(数据访问对象)

4.MFC的OLE/DB;

5.ActiveX数据对象(ADO)。

1.开放数据库连接(ODBC API):

提供了一个通用的编程接口,允许程序与多种不同的数据库连接。它为Oracle, SQL Server,MS Excel等都提供了驱动程序,使得用户可以使用SQL语句对数据库进行直接的底层功能操作。在使用ODBC API时,用户须引入的头文件为"sql.h","sqlext.h","sqltypes.h"。用ODBC API创建数据库应用程序遵循一定的基本步骤:

第一步是分配ODBC环境,使一些内部结构初始化。完成这一步,须分配一个SQLHENV类型的变量在ODBC环境中做句柄使用。

第二步是为将要使用的每一个数据源分配一个连接句柄,由函数SQLALLocHandle()完成。

第三步是使用SQLConnect()把连接句柄与数据库连接,可以先通过SQLSetConnectAttr()设置连接属性。

然后就可以进行SQL语句的操作,限于篇幅,相关的函数就不具体介绍了,读者可以参考相关书籍。

操作完成后,用户取回相应的结果,就可以取消与数据库的连接。

最后需要释放ODBC环境。

ODBC API的特点是功能强大丰富,提供了异步操作,事务处理等高级功能,但相应的编程复杂,工作量大。

2.MFC ODBC类:

MFC1.5后的版本里引入封装了ODBC功能的类。通过这些类提供与ODBC的接口,使得用户可以不须处理ODBC API中的繁杂处理就可以进行数据库操作。主要的MFC ODBC类如下。

CDatabase类:一个CDatabase对象表示一个到数据源的连接,通过它 可以操作数据源。应用程序可使用多个CDatabase对象:构造一个对象并调用OpenEx()成员函数打开一个连接。接着构造CRecordSet对 象以操作连接的数据源,并向CDatabase对象传递记录集构造程序指针。完成使用后用Close()成员函数销毁CDatabase对象。一般情况下 并不需要直接使用CDatabase对象,因为CRecordSet对象可以实现大多数的功能。但是在进行事务处理时,CDatabase就起到关键作 用。事务(Transaction)指的是将一系列对数据源的更新放在一起,同时提交或一个也不提交,为的是确保多用户对数据源同时操作时的数据正确性。

CRecordSet类:一个CRecordSet对象代表一个从数据源选择的一组 记录的集合-记录集。记录集有两种形式:snapshot和dynaset。前者表示数据的静态视图,后者表示记录集与其他用户对数据库的更新保持同步。 通过CRecordSet对象,用户可以对数据库中的记录进行各种操作。

CRecordView类:CRecordView对象是在空间中显示数据库记录的 视图。这种视图是一种直接连到一个CRecordSet对象的格式视图,它从一个对话框模板资源创建,并将CRecordSet对象的字段显示在对话框模 板的控件里。对象利用DDX和RFX机制,使格式上的控件和记录集的字段之间数据移动自动化,也就是说,用户甚至不要编写一行代码就可以实现简单的数据库 记录查看程序。

CDBException类:由Cexception类派生,以三个继承的成员变量反映对数据库操作时的异常:

m_nRetCode:以ODBC返回代码(SQL_RETURN)的形式表明造成异常的原因。

m_strError:字符串,描述造成抛出异常的错误原因。

m_strStateNativeOrigin:字符串,用以描述以ODBC错误代码表示的异常错误。

MFC数据库类成员函数都能抛出CDBException类型的异常,所以在代码对数据库进行操作后监测异常是正确做法。

MFC ODBC类在实际开发中应用最广,因为它功能丰富,操作相对简便。

3.MFC DAO(数据访问对象)编程:

DAO用于和微软的Access数据库接口。在数据库应用程序如果只需与Access数据库接口时,使用DAO编程较方便。其主要类如下。

CDaoWorkspace:CDaoWorkspace对象可以让一个用户管理从登陆到离开期间,指定的密码保护的数据库会话全过程。大多数情况下不要多个工作区也不要创建明确的工作区对象。因为在打开数据库和记录集对象时,它们可以使用DAO缺省工作区。

CDaoDatabase:代表一个连接,类似上述CDatabase类。

CDaoRecordSet:用来选择记录集并操作,类似上述CRecordSet类。

CDaoRecordView:类似上述CRecordView类。

CDaoException:类似上述CDBException类。

CDaoTableDef:表示基本表或附加表的定义。每个DAO数据库对象包括一个称为TableDef的收集,包含所有存储的DAO表定义对象。CDaoTableDef对象可以用来控制表定义。

CDaoQueryDef:CDaoQueryDef对象表示了一个查询定义(querydef)。

CDaoFieldExchange:支持数据库类使用的DAO字段交换(DFX)例程。也可处理事务,类似MFC ODBC类。

MFC DAO仅用来支持Access数据库,应用范围相对固定。

4.OLE DB:

OLE DB在数据提供程序和用户之间提供了灵活的组件对象模型(COM)接口,这种灵活性有时会使得操作复杂化。OLE DB框架定义了应用的三个基本类。

数据提供程序Data Provider:拥有自己的数据并以表格形式显示数据的应用程序。提供OLE DB的行集COM接口,期显示范围可以从单一数据表格的简单提供者知道更复杂的分布式数据库系统。

使用者Consumers:使用OLE DB接口对存储在数据提供程序中的数据进行控制的应用程序。用户应用程序归为使用类。

服务提供程序Service Provider:是数据提供程序和使用者的组合。服务提供程序没有自己的数据,但使用OLE DB使用者接口来访问存储在数据提供程序中的数据。然后,服务提供程序通过打开数据提供程序接口使得数据对使用者有效。服务提供程序常用于向应用程序提供 高层次服务,比如高级分布式查询。

OLE DB编程时,用户使用组件对象开发应用程序。这些组件有:

枚举器:用于列出可用的数据源;

数据源:代表单独的数据和服务提供程序,用于创建对话;

对话:用于创建事务和命令;

事务:用于将多个操作归并为单一事务处理;

命令:用于向数据源发送文本命令(SQL),返回行集;

错误:用于获得错误信息。

5.ActiveX数据对象(ADO):是微软提供的面向对象的接 口,与OLE DB类似,但接口更简单,具有更广泛的特征数组和更高程度的灵活性。ADO基于COM,提供编程语言可利用的对象,除了面向VC++,还提供面向其他各种 开发工具的应用,如VB,VJ等。ADO在服务器应用方面非常有用,特别是对于动态服务器页面ASP(Active Server Page)。

ADO对象结构类似于OLE DB,但并不依靠对象层次。大多数情况下,用户只需要创建并只使用需要处理的对象。下面的对象类组成了ADO接口。

Connection:用于表示与数据库的连接,以及处理一些命令和事务。

Command:用于处理传送给数据源的命令。

Recordset:用于处理数据的表格集,包括获取和修改数据。

Field:用于表示记录集中的列信息,包括列值和其他信息。

Parameter:用于对传送给数据源的命令之间来回传送数据。

Property:用与操作在ADO中使用的其他对象的详细属性。

Error:用于获得可能发生的错误的详细信息。

在VC++使用ADO需要进行COM操作,详细方法在此就不赘述了。

在当今流行的分布式开发环境下,VC++6.0在数据库开发方面有较强的优势,学会在不同的场合选用不同的技术,对开发人员来说是必要的技术。

(T113)

什么是DAO

10.8.1 什么是DAO

DAO(Database Access Object)使用Microsoft Jet数据库引擎来访问数据库。Microsoft Jet为象Access和Visual Basic这样的产品提供了数据引擎。

与ODBC一样,DAO提供了一组API供编程使用。MFC也提供了一组DAO类,封装了底层的API,从而大大简化了程序的开发。利用MFC的DAO类,用户可以编写独立于DBMS的应用程序。

DAO是从Visual C++4.0版开始引入的。一般地讲,DAO类提供了比ODBC类更广泛的支持。一方面,只要有ODBC驱动程序,使用Microsoft Jet的DAO就可以访问ODBC数据源。另一方面,由于DAO是基于Microsoft Jet引擎的,因而在访问Access数据库(即*.MDB文件)时具有很好的性能。

10.8.2 DAO和ODBC的相似之处

DAO类与ODBC类相比具有很多相似之处,这主要有下面几点:

二者都支持对各种ODBC数据源的访问。虽然二者使用的数据引擎不同,但都可以满足用户编写独立于DBMS的应用程序的要求。

DAO提供了与ODBC功能相似的MFC类。例如,DAO的CDaoDatabase类对应ODBC的CDatabase类,CDaoRecordset对应CRecordset,CDaoRecordView对应CRecordView,CDaoException对应CDBException。这些对应的类功能相似,它们的大部分成员函数都是相同的。

AppWizard和ClassWizard对使用DAO和ODBC对象的应用程序提供了类似的支持。

由于DAO和ODBC类的许多方面都比较相似,因此只要用户掌握了ODBC,就很容易学会使用DAO。实际上,用户可以很轻松地把数据库应用程序从ODBC移植到DAO。

Visual C++随盘提供了一个名为DaoEnrol的例子,该例实际上是Enroll的一个DAO版本。读者可以打开DaoEnrol工程看一看,它的源代码与Enroll的极为相似。读者可以按照建立Enroll的步骤来建立DaoEnrol,其中只有若干个地方有差别,这主要有以下几点:

选取的数据源不同。在用AppWizard创建DaoEnrol时,以及在用ClassWizard创建CDaoRecordset类的派生类时,在Database Options对话框中应该选择DAO而不是ODBC。而且DAO的数据源是通过选择一个.MDB文件来指定的,即点击“...”按钮后在文件对话框中选择要访问的.MDB文件。

记录集的缺省类型不同。ODBC记录集的缺省类型是快照(Snapshot),而DAO则是动态集(Dynaset)。

参数化的方式不同。DAO记录集的m_strFilter和m_strSort中的参数不是“?”号,而是一个有意义的参数名。例如,在下面的过滤器中有一个名为CourseIDParam的参数。
m_pSet->m_strFilter ="CourseID = CourseIDParam";
在DoFieldExchange函数中,有下面两行:
pFX->SetFieldType(CDaoFieldExchange::param);
DFX_Text(pFX, _T("CourseIDParam"), m_strCourseIDParam);
DFX函数的第二个参数也是CourseIDParam。

处理异常的方式不同。例如,在删除记录时,对异常的处理如下所示:

try
{
m_pSet->Delete();
}
catch(CDaoException* e)
{
AfxMessageBox(e->
m_pErrorInfo->m_strDescription);
e->Delete();
}

除了上述差别外,AppWizard和ClassWizard也隐藏了一些细微的不同之处,例如,DAO记录集是使用是DFX数据交换机制(DAO record field exchange)而不是RFX,在DAO记录集的DoFieldExchange中使用的是DFX函数而不是RFX函数。

10.8.3 DAO的特色

  DAO可以通过ODBC驱动程序访问ODBC数据源。但DAO是基于Microsoft Jet引擎的,通过该引擎,DAO可以直接访问Access、FoxPro、dBASE、Paradox、Excel和Lotus WK等数据库。CDaoDatabase类可以直接与这些数据库进行连接,而不必在ODBC管理器中注册DSN。例如,下面的代码用来打开一个FoxPro数据库:

CDaoDatabase daoDb;

daoDb.Open( “”,FALSE,FALSE,"FoxPro 2.5;DATABASE=c:\\zyf");

CDaoDatabase::Open函数用来连接某个数据库,该函数的声明为:

virtual void Open( LPCTSTR lpszName, BOOL bExclusive = FALSE, BOOL
bReadOnly = FALSE, LPCTSTR lpszConnect = _T("") );
throw( CDaoException, CMemoryException );

参数bExclusive如果为TRUE,则函数以独占方式打开数据库,否则就用共享方式。如果bReadOnly为TRUE,那么就以只读方式打开数据库。如果要打开一个Access数据库,则可以在lpszName参数中指定MDB文件名。如果要访问非Access数据库,则应使该参数为“”,并在lpszConnect中说明一个连接字符串。连接字符串的形式一般为 “数据库类型;DATABASE=路径(文件)”,例如 “dBASE III;DATABASE=c:\\MYDIR”

  Open函数也可以打开一个ODBC数据源,但这需要相应的ODBC驱动程序,并需要在ODBC管理器中注册DSN。此时lpszConnect的形式为 “ODBC;DSN=MyDataSource”。显然,用DAO访问象FoxPro这样的数据库时,直接打开比把它当作ODBC数据源打开要省事。

  支持DDL是DAO对数据库编程良好支持的一个重要体现。DDL(Data Definition Language)在SQL术语中叫做“数据定义语言”,它用来完成生成、修改和删除数据库结构的操作。ODBC类只支持DML(Data Manipulation Language,数据操作语言),不支持DDL,所以用ODBC类只能完成数据的操作,不能涉及数据库的结构。要执行DDL操作,只有通过ODBC API。而DAO类同时提供了对DML和DDL的支持,这意味着程序可以使用DAO类方便的创建数据库及修改数据库的结构。

  与ODBC相比,DAO提供了一些新类来加强其功能,这些新类包括:

CDaoTableDef类提供了对表的结构的定义。调用CDaoTableDef::Open可以获得表的结构定义。调用CDaoTableDef::Create可以创建一张新表,调用CDaoTableDef:: CreateField可为表添加字段,调用CDaoTableDef::CreateIndex可以为表添加索引。调用CDaoTableDef::Append可以把新创建的表保存到数据库中。

CDaoQueryDef类代表一个查询定义(Query definition),该定义可以被存储到数据库中。

CDaoWorkspace提供了数据工作区(Workspace)。一个工作区可以包含几个数据库,工作区可以对所属的数据库进行全体或单独的事务处理,工作区也负责数据库的安全性。如果需要,程序可以打开多个工作区。

  DAO的另一个重要特色在于它对Access数据库提供了强大的支持。由于DAO是基于Microsoft Jet引擎的,所以DAO肯定要在Access数据库上多作一些文章。例如,调用CDaoDatabase::Create可以直接建立一个MDB文件,代码如下所示:
m_db.Create(“C:\\MYDIR\\MYDB.MDB”);

利用AppWizard和ClassWizard,用户可以方便地开发出性能优良的基于DAO的
Access数据库应用程序。

10.8.4 ODBC还是DAO

由于DAO可以访问ODBC数据源,下面几条可以作为DAO替代ODBC的理由:

在某些情况下可以获得更好的性能,特别是在访问Microsoft Jet(.MDB)数据库时。

与ODBC兼容
DAO允许数据有效检查
DAO允许用户说明表与表之间的关系

当然,DAO的出现并不意味着ODBC已经过时了。如果用户的工作必须严格限于ODBC数据源,尤其是在开发Client/Server结构的应用程序时,用ODBC有较好的性能。

Visual C++ ADO数据库编程入门

摘要 本文简要介绍了在VC++ 6.0中使用 ADO进行客户端数据库编程的基本步骤,以及常见问题的解决方法,可供入门级的参考之用。

  关键字 ADO VC++ 数据库编程

  ADO 是目前在Windows环境中比较流行的客户端数据库编程技术。ADO是建立在OLE DB底层技术之上的高级编程接口,因而它兼具有强大的数据处理功能(处理各种不同类型的数据源、分布式的数据处理等等)和极其简单、易用的编程接口,因而得到了广泛的应用。而且按微软公司的意图,OLE DB和ADO将逐步取代 ODBC和DAO。现在介绍ADO各种应用的文章和书籍有很多,本文着重站在初学者的角度,简要探讨一下在VC++中使用ADO编程时的一些问题。我们希望阅读本文之前,您对ADO技术的基本原理有一些了解。

  一、在VC++中使用ADO编程

  ADO实际上就是由一组Automation对象构成的组件,因此可以象使用其它任何Automation对象一样使用ADO。ADO中最重要的对象有三个:Connection、Command和Recordset,它们分别表示连接对象、命令对象和记录集对象。如果您熟悉使用MFC中的ODBC类(CDatabase、CRecordset)编程,那么学习ADO编程就十分容易了。

  使用ADO编程时可以采用以下三种方法之一:

  1、使用预处理指令#import

#import "C:\Program Files\Common Files\System\ADO\msado15.dll" \
no_namespace rename("EOF", "EndOfFile")

  但要注意不能放在stdAfx.h文件的开头,而应该放在所有include指令的后面。否则在编译时会出错。
程序在编译过程中,VC++会读出msado15.dll中的类型库信息,自动产生两个该类型库的头文件和实现文件msado15.tlh和msado15.tli(在您的Debug或Release目录下)。在这两个文件里定义了ADO的所有对象和方法,以及一些枚举型的常量等。我们的程序只要直接调用这些方法就行了,与使用MFC中的COleDispatchDriver类调用Automation对象十分类似。

  2、使用MFC中的CIDispatchDriver

  就是通过读取msado15.dll中的类型库信息,建立一个COleDispatchDriver类的派生类,然后通过它调用ADO对象。

  3、直接用COM提供的API

  如使用如下代码:





CLSID clsid;
HRESULT hr = ::CLSIDFromProgID(L"ADODB.Connection", &clsid);
if(FAILED(hr))
{...}
::CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (void **)
&pDispatch);
if(FAILED(hr))
{...}

  以上三种方法,第一和第二种类似,可能第一种好用一些,第三种编程可能最麻烦。但可能第三种方法也是效率最高的,程序的尺寸也最小,并且对ADO的控制能力也最强。

  据微软资料介绍,第一种方法不支持方法调用中的默认参数,当然第二种方法也是这样,但第三种就不是这样了。采用第三种方法的水平也最高。当你需要绕过ADO而直接调用OLE DB底层的方法时,就一定要使用第三种方法了。

  ADO编程的关键,就是熟练地运用ADO提供的各种对象(object)、方法(method)、属性(property)和容器(collection)。另外,如果是在MS SQL或Oracle等大型数据库上编程,还要能熟练使用SQL语言。
二、使用#import方法的编程步骤b>

  这里建议您使用#import的方法,因为它易学、易用,代码也比较简洁。

  1、添加#import指令

打开stdafx.h文件,将下列内容添加到所有的include指令之后:




#include <icrsint.h> //Include support for VC++
Extensions
#import "C:\Program Files\Common
Files\System\ADO\msado15.dll" \
no_namespace rename("EOF",
"adoEOF")

  其中icrsint.h文件包含了VC++扩展的一些预处理指令、宏等的定义,用于COM编程时使用。

  2、定义_ConnectionPtr型变量,并建立数据库连接

  建立了与数据库服务器的连接后,才能进行其他有关数据库的访问和操作。ADO使用Connection对象来建立与数据库服务器的连接,所以它相当于MFC中的CDatabase类。和CDatabase类一样,调用Connection对象的Open方法即可建立与服务器的连接。

  数据类型
_ConnectionPtr实际上就是由类模板_com_ptr_t而得到的一个具体的实例类,其定义可以到msado15.tlh、comdef.h
和comip.h这三个文件中找到。在msado15.tlh中有:





_COM_SMARTPTR_TYPEDEF(_Collection,
__uuidof(_Collection));

  经宏扩展后就得到了_ConnectionPtr类。_ConnectionPtr类封装了Connection对象的Idispatch接口指针,及一些必要的操作。我们就是通过这个指针来操纵Connection对象。类似地,后面用到的_CommandPtr和_RecordsetPtr类型也是这样得到的,它们分别表示命令对象指针和记录集对象的指针。

  (1)、连接到MS
SQL Server

  注意连接字符串的格式,提供正确的连接字符串是成功连接到数据库服务器的第一步,有关连接字符串的详细信息参见微软MSDN
Library光盘。

  本例连接字符串中的server_name,database_name,user_name和password在编程时都应该替换成实际的内容。





_ConnectionPtr pMyConnect=NULL;
HRESULT
hr=pMyConnect.CreateInstance(__uuidof(Connection)));
if(FAILED(hr))return;

_bstr_t
strConnect="Provider=SQLOLEDB;
Server=server_name;"
"Database=database_name; uid=user_name;
pwd=password;";
//connecting to the database server
now:
try{pMyConnect->Open(strConnect,"","",NULL);}
catch
(_com_error &e)
{
::MessageBox(NULL,e.Description(),"警告",MB_OK │
MB_ICONWARNING);
}

  注意Connection对象的Open方法中的连接字符串参数必须是BSTR或_bstr_t类型。另外,本例是直接通过OLE
DB Provider建立连接,所以无需建立数据源。

  (2)、通过ODBC Driver连接到Database
Server连接字符串格式与直接用ODBC编程时的差不多:





_bstr_t strConnect="DSN=datasource_name; Database=database_name;
uid=user_name;
pwd=password;";

  此时与ODBC编程一样,必须先建立数据源。

  3、定义_RecordsetPtr型变量,并打开数据集

  定义_RecordsetPtr型变量,然后通过它调用Recordset对象的Open方法,即可打开一个数据集。所以Recordset对象与MFC中的CRecordset类类似,它也有当前记录、当前记录指针的概念。如:





_RecordsetPtr m_pRecordset;
if(!FAILED(m_pRecordset.CreateInstance(
__uuidof( Recordset
)))
{
m_pDoc->m_initialized=FALSE;
return;
}

try{
m_pRecordset->Open(_variant_t("mytable"),
_variant_t((IDispatch
*)pMyConnect,true), adOpenKeyset,
adLockOptimistic,
adCmdTable);
}
catch (_com_error
&e)
{
::MessageBox(NULL,"无法打开mytable表。","提示",
MB_OK │
MB_ICONWARNING);
}

  Recordset对象的Open方法非常重要,它的第一个参数可以是一个SQL语句、一个表的名字或一个命令对象等等;第二个参数就是前面建立的连接对象的指针。此外,用Connection和Command对象的Execute方法也能得到记录集,但是只读的。
4、读取当前记录的数据

  我认为读取数据的最方便的方法如下:





try{
m_pRecordset->MoveFirst();

while(m_pRecordset->adoEOF==VARIANT_FALSE)
{
//Retrieve
column's value:
CString
sName=(char*)(_bstr_t)(m_pRecordset->Fields->GetItem
(_variant_t("name"))->Value);
short
cAge=(short)(m_pRecordset->Fields->GetItem
(_variant_t("age"))->Value);
//Do
something what you want to do:
......
m_pRecordset->MoveNext();

}
}//try
catch (_com_error &e)
{
CString
str=(char*)e.Description();
::MessageBox(NULL,str+"\n又出毛病了。","提示",
MB_OK

MB_ICONWARNING);
}

  本例中的name和age都是字段名,读取的字段值分别保存在sName和cAge变量内。例中的Fields是Recordset对象的容器,GetItem方法返回的是Field对象,而Value则是Field对象的一个属性(即该字段的值)。通过此例,应掌握操纵对象属性的方法。例如,要获得Field
对象的Value属性的值可以直接用属性名Value来引用它(如上例),但也可以调用Get方法,例如:




CString
sName=(char*)(_bstr_t)(m_pRecordset->Fields->GetItem
(_variant_t("name"))->GetValue());

  从此例还可以看到,判断是否到达记录集的末尾,使用记录集的adoEOF属性,其值若为真即到了结尾,反之则未到。判断是否到达记录集开头,则可用BOF属性。

  另外,读取数据还有一个方法,就是定义一个绑定的类,然后通过绑定的变量得到字段值(详见后面的介绍)。

  5、修改数据

  方法一:






try{
m_pRecordset->MoveFirst();

while(m_pRecordset->adoEOF==VARIANT_FALSE)
{

m_pRecordset->Fields->GetItem
(_variant_t("姓名"))->Value=_bstr_t("赵薇");
......
m_pRecordset->Update();

m_pRecordset->MoveNext();

}
}//try

  改变了Value属性的值,即改变了字段的值。

  方法二:






m_pRecordset->Fields->GetItem
(_variant_t("姓名"))->PutValue(_bstr_t("赵薇"));

  方法三:就是用定义绑定类的方法(详见后面的介绍)。

  6、添加记录

  新记录添加成功后,即自动成为当前记录。AddNew方法有两种形式,一个含有参数,而另一个则不带参数。

  方法一(不带参数):






// Add new record into this
table:
try{
if(!m_pRecordset->Supports(adAddNew))
return;

m_pRecordset->AddNew();

m_pRecordset->Fields->GetItem
(_variant_t("姓名"))->Value=_bstr_t("赵薇");
m_pRecordset->Fields->GetItem
(_variant_t("性别"))->Value=_bstr_t("女");
m_pRecordset->Fields->GetItem
(_variant_t("age"))->Value=_variant_t((short)20);
m_pRecordset->Fields->GetItem
(_variant_t("marry"))->Value=_bstr_t("未婚");
m_pRecordset->Update();

}//try
catch (_com_error &e)
{
::MessageBox(NULL,
"又出毛病了。","提示",MB_OK │
MB_ICONWARNING);
}

  这种方法弄完了还要调用Update()。

  方法二(带参数):






_variant_t varName[4],narValue[4];
varName[0] =
L"姓名";
varName[1] = L"性别";
varName[2] = L"age";
varName[3] =
L"marry";
narValue[0]=_bstr_t("赵薇");
narValue[1]=_bstr_t("女");
narValue[2]=_variant_t((short)20);
narValue[3]=_bstr_t("未婚");

const
int nCrit = sizeof varName / sizeof varName[0];
// Create SafeArray
Bounds and initialize the array
SAFEARRAYBOUND
rgsaName[1],rgsaValue[1];
rgsaName[0].lLbound = 0;

rgsaName[0].cElements = nCrit;
SAFEARRAY *psaName =
SafeArrayCreate( VT_VARIANT, 1, rgsaName );
rgsaValue[0].lLbound =
0;
rgsaValue[0].cElements = nCrit;
SAFEARRAY *psaValue =
SafeArrayCreate( VT_VARIANT, 1, rgsaValue );
// Set the values for each
element of the array
HRESULT hr1=S_OK.hr2=S_OK;
for( long i = 0 ; i
< nCrit && SUCCEEDED( hr1 ) && SUCCEEDED( hr2 );i++)

{
hr1=SafeArrayPutElement(psaName,
&i,&varName[i]);
hr2=SafeArrayPutElement(psaValue,
&i,&narValue[i]); }

// Initialize and fill the
SafeArray
VARIANT vsaName,vsaValue;
vsaName.vt = VT_VARIANT │
VT_ARRAY;
vsaValue.vt = VT_VARIANT │ VT_ARRAY;
V_ARRAY(&vsaName)
= psaName;//&vsaName->parray=psaName;
//see definition in
oleauto.h file.
V_ARRAY(&vsaValue) = psaValue;

// Add a new
record:
m_pRecordset->AddNew(vsaName,vsaValue);

  这种方法不需要调用Update,因为添加后,ADO会自动调用它。此方法主要是使用SafeArray挺麻烦。

  方法三:就是用定义绑定类的方法(详见后面的介绍)。
7、删除记录

  调用Recordset的Delete方法就行了,删除的是当前记录。要了解Delete的其它用法请查阅参考文献。





try{
m_pRecordset->MoveFirst();

while(m_pRecordset->adoEOF==VARIANT_FALSE)
{
CString
sName=(char*)(_bstr_t)(m_pRecordset->Fields->GetItem
(_variant_t("姓名"))->Value);
if(::MessageBox(NULL,"姓名="+sName+"\n删除她吗?",
"提示",MB_YESNO

MB_ICONWARNING)==IDYES)
{
m_pRecordset->Delete(adAffectCurrent);

m_pRecordset->Update();
}
m_pRecordset->MoveNext();

}
}//try
catch (_com_error
&e)
{
::MessageBox(NULL,"又出毛病了。","提示",MB_OK │
MB_ICONWARNING);
}

  8、使用带参数的命令

  Command对象所代表的就是一个Provider能够理解的命令,如SQL语句等。使用Command对象的关键就是把表示命令的语句设置到CommandText属性中,然后调用Command对象的Execute方法就行了。一般情况下在命令中无需使用参数,但有时使用参数,可以增加其灵活性和效率。

  (1).
建立连接、命令对象和记录集对象

  本例中表示命令的语句就是一个SQL语句(SELECT语句)。SELECT语句中的问号?就代表参数,如果要多个参数,就多放几个问号,每个问号代表一个参数。






_ConnectionPtr Conn1;
_CommandPtr Cmd1;
ParametersPtr *Params1 =
NULL; // Not an instance of a smart pointer.
_ParameterPtr
Param1;
_RecordsetPtr Rs1;

try
{
// Create Connection
Object (1.5 Version)
Conn1.CreateInstance( __uuidof( Connection )
);
Conn1->ConnectionString = bstrConnect;
Conn1->Open(
bstrEmpty, bstrEmpty, bstrEmpty, -1 );
// Create Command
Object
Cmd1.CreateInstance( __uuidof( Command )
);
Cmd1->ActiveConnection = Conn1;
Cmd1->CommandText =
_bstr_t("SELECT * FROM mytable WHERE age<

?");
}//try

  要注意命令对象必须与连接对象关联起来才能起作用,本例中将命令对象的ActiveConnection属性设置为连接对象的指针,即为此目的:





Cmd1->ActiveConnection = Conn1;

  (2).
创建参数对象,并给参数赋值






// Create Parameter Object
Param1 = Cmd1->CreateParameter(
_bstr_t(bstrEmpty),
adInteger,
adParamInput,
-1,
_variant_t(
(long) 5) );
Param1->Value = _variant_t( (long) 5
);
Cmd1->Parameters->Append( Param1
);

  用命令对象的方法来创建一个参数对象,其中的长度参数(第三个)如果是固定长度的类型,就填-1,如果是字符串等可变长度的就填其实际长度。Parameters是命令对象的一个容器,它的Append方法就是把创建的参数对象追加到该容器里。Append进去的参数按先后顺序与SQL语句中的问号从左至右一一对应。

  (3).
执行命令打开记录集






// Open Recordset Object
Rs1 = Cmd1->Execute( &vtEmpty,
&vtEmpty2, adCmdText
);

  但要注意,用Command和Connection对象的Execute方法得到的Recordset是只读的。因为在打开Recordset之前,我们无法设置它的LockType属性(其默认值为只读)。而在打开之后设置LockType不起作用。

  我发现用上述方法得到记录集Rs1后,不但Rs1中的记录无法修改,即使直接用SQL语句修改同一表中任何记录都不行。

  要想能修改数据,还是要用Recordset自己的Open方法才行,如:






try{
m_pRecordset->Open((IDispatch *) Cmd1,
vtMissing,
adOpenStatic, adLockOptimistic,
adCmdUnspecified);
}
catch (_com_error
&e)
{
::MessageBox(NULL,"mytable表不存在。","提示",MB_OK │
MB_ICONWARNING);
}

  Recordset对象的Open方法真是太好了,其第一个参数可以是SQL语句、表名字、命令对象指针等等。

  9、响应ADO的通知事件

  通知事件就是当某个特定事件发生时,由Provider通知客户程序,换句话说,就是由Provider调用客户程序中的一个特定的方法(即事件的处理函数)。所以为了响应一个事件,最关键的就是要实现事件的处理函数。

  (1).
从ConnectionEventsVt接口派生出一个类

  为了响应_Connection的通知事件,应该从ConnectionEventsVt接口派生出一个类:






class CConnEvent : public ConnectionEventsVt
{
private:
ULONG
m_cRef;
public:
CConnEvent() { m_cRef = 0; };
~CConnEvent()
{};

STDMETHODIMP QueryInterface(REFIID riid, void **
ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG)
Release(void);
STDMETHODIMP raw_InfoMessage(
struct Error
*pError,
EventStatusEnum *adStatus,
struct _Connection
*pConnection);
STDMETHODIMP raw_BeginTransComplete(
LONG
TransactionLevel,
struct Error *pError,
EventStatusEnum
*adStatus,
struct _Connection *pConnection);
......
};

   (2). 实现每一个事件的处理函数(凡是带raw_前缀的方法都把它实现了):






STDMETHODIMP CConnEvent::raw_InfoMessage(
struct Error
*pError,
EventStatusEnum *adStatus,
struct _Connection
*pConnection)
{
*adStatus = adStatusUnwantedEvent;
return
S_OK;
};

  有些方法虽然你并不需要,但也必须实现它,只需简单地返回一个S_OK即可。但如果要避免经常被调用,还应在其中将adStatus参数设置为adStatusUnwantedEvent,则在本次调用后,以后就不会被调用了。
另外还必须实现QueryInterface,
AddRef, 和Release三个方法:






STDMETHODIMP CConnEvent::QueryInterface(REFIID riid, void ** ppv)

{
*ppv = NULL;
if (riid == __uuidof(IUnknown) ││
riid ==
__uuidof(ConnectionEventsVt)) *ppv = this;
if (*ppv == NULL)
return
ResultFromScode(E_NOINTERFACE);
AddRef();
return
NOERROR;
}
STDMETHODIMP_(ULONG) CConnEvent::AddRef() { return
++m_cRef; };
STDMETHODIMP_(ULONG) CConnEvent::Release()
{
if (0
!= --m_cRef) return m_cRef;
delete this;
return
0;
}

  (3). 开始响应通知事件






// Start using the Connection events
IConnectionPointContainer
*pCPC = NULL;
IConnectionPoint *pCP = NULL;

hr =
pConn.CreateInstance(__uuidof(Connection));
if (FAILED(hr))
return;

hr =
pConn->QueryInterface(__uuidof(IConnectionPointContainer),
(void
**)&pCPC);
if (FAILED(hr)) return;
hr =
pCPC->FindConnectionPoint(__uuidof(ConnectionEvents),
&pCP);
pCPC->Release();
if (FAILED(hr))
return;

pConnEvent = new CConnEvent();
hr =
pConnEvent->QueryInterface(__uuidof(IUnknown), (void **)
&pUnk);
if (FAILED(hr)) return rc;
hr = pCP->Advise(pUnk,
&dwConnEvt);
pCP->Release();
if (FAILED(hr))
return;

pConn->Open("dsn=Pubs;", "sa", "",
adConnectUnspecified);


  也就是说在连接(Open)之前就做这些事。

  (4).
停止响应通知事件





pConn->Close();
// Stop using the Connection events
hr =
pConn->QueryInterface(__uuidof(IConnectionPointContainer),
(void
**) &pCPC);
if (FAILED(hr)) return;
hr =
pCPC->FindConnectionPoint(__uuidof(ConnectionEvents),
&pCP);
pCPC->Release();
if (FAILED(hr)) return rc;
hr =
pCP->Unadvise( dwConnEvt );
pCP->Release();
if (FAILED(hr))
return;

  在连接关闭之后做这件事。
10、邦定数据

  定义一个绑定类,将其成员变量绑定到一个指定的记录集,以方便于访问记录集的字段值。

  (1). 从CADORecordBinding派生出一个类:





class CCustomRs : public CADORecordBinding
{
BEGIN_ADO_BINDING(CCustomRs)
ADO_VARIABLE_LENGTH_ENTRY2(3, adVarChar, m_szau_fname,
sizeof(m_szau_fname), lau_fnameStatus, false)
ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_szau_lname,
sizeof(m_szau_lname), lau_lnameStatus, false)
ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_szphone,
sizeof(m_szphone), lphoneStatus, true)
END_ADO_BINDING()

public:
CHAR m_szau_fname[22];
ULONG lau_fnameStatus;
CHAR m_szau_lname[42];
ULONG lau_lnameStatus;
CHAR m_szphone[14];
ULONG lphoneStatus;
};

  其中将要绑定的字段与变量名用BEGIN_ADO_BINDING宏关联起来。每个字段对应于两个变量,一个存放字段的值,另一个存放字段的状态。字段用从1开始的序号表示,如1,2,3等等。

  特别要注意的是:如果要绑定的字段是字符串类型,则对应的字符数组的元素个数一定要比字段长度大2(比如m_szau_fname[22],其绑定的字段au_fname的长度实际是20),不这样绑定就会失败。我分析多出的2可能是为了存放字符串结尾的空字符null和BSTR字符串开头的一个字(表示BSTR的长度)。这个问题对于初学者来说可能是一个意想不到的问题。

  CADORecordBinding类的定义在icrsint.h文件里,内容是:






class CADORecordBinding
{
public:
STDMETHOD_(const ADO_BINDING_ENTRY*, GetADOBindingEntries) (VOID) PURE;
};

BEGIN_ADO_BINDING宏的定义也在icrsint.h文件里,内容是:
#define BEGIN_ADO_BINDING(cls) public: \
typedef cls ADORowClass; \
const ADO_BINDING_ENTRY* STDMETHODCALLTYPE GetADOBindingEntries() { \
static const ADO_BINDING_ENTRY rgADOBindingEntries[] = {

ADO_VARIABLE_LENGTH_ENTRY2宏的定义也在icrsint.h文件里:
#define ADO_VARIABLE_LENGTH_ENTRY2(Ordinal, DataType, Buffer, Size, Status, Modify)\
{Ordinal, \
DataType, \
0, \
0, \
Size, \
offsetof(ADORowClass, Buffer), \
offsetof(ADORowClass, Status), \
0, \
classoffset(CADORecordBinding, ADORowClass), \
Modify},

#define END_ADO_BINDING宏的定义也在icrsint.h文件里:
#define END_ADO_BINDING() {0, adEmpty, 0, 0, 0, 0, 0, 0, 0, FALSE}};\
return rgADOBindingEntries;}

   (2). 绑定






_RecordsetPtr Rs1;
IADORecordBinding *picRs=NULL;
CCustomRs rs;
......
Rs1->QueryInterface(__uuidof(IADORecordBinding),
(LPVOID*)&picRs));
picRs->BindToRecordset(&rs);

  派生出的类必须通过IADORecordBinding接口才能绑定,调用它的BindToRecordset方法就行了。

  (3). rs中的变量即是当前记录字段的值






//Set sort and filter condition:
// Step 4: Manipulate the data
Rs1->Fields->GetItem("au_lname")->Properties->GetItem("Optimize")->Value = true;
Rs1->Sort = "au_lname ASC";
Rs1->Filter = "phone LIKE '415 5*'";

Rs1->MoveFirst();
while (VARIANT_FALSE == Rs1->EndOfFile)
{
printf("Name: %s\t %s\tPhone: %s\n",
(rs.lau_fnameStatus == adFldOK ? rs.m_szau_fname : ""),
(rs.lau_lnameStatus == adFldOK ? rs.m_szau_lname : ""),
(rs.lphoneStatus == adFldOK ? rs.m_szphone : ""));
if (rs.lphoneStatus == adFldOK)
strcpy(rs.m_szphone, "777");
TESTHR(picRs->Update(&rs)); // Add change to the batch
Rs1->MoveNext();
}
Rs1->Filter = (long) adFilterNone;
......
if (picRs) picRs->Release();
Rs1->Close();
pConn->Close();

  只要字段的状态是adFldOK,就可以访问。如果修改了字段,不要忘了先调用picRs的Update(注意不是Recordset的Update),然后才关闭,也不要忘了释放picRs(即picRs->Release();)。

  (4). 此时还可以用IADORecordBinding接口添加新纪录






if(FAILED(picRs->AddNew(&rs)))
......

  11. 访问长数据

  在Microsoft SQL中的长数据包括text、image等这样长类型的数据,作为二进制字节来对待。

  可以用Field对象的GetChunk和AppendChunk方法来访问。每次可以读出或写入全部数据的一部分,它会记住上次访问的位置。但是如果中间访问了别的字段后,就又得从头来了。

  请看下面的例子:





//写入一张照片到数据库:
VARIANT varChunk;
SAFEARRAY *psa;
SAFEARRAYBOUND rgsabound[1];

//VT_ARRAY │ VT_UI1
CFile f("h:\\aaa.jpg",CFile::modeRead);
BYTE bVal[ChunkSize+1];
UINT uIsRead=0;
//Create a safe array to store the array of BYTES
while(1)
{
uIsRead=f.Read(bVal,ChunkSize);
if(uIsRead==0)break;
rgsabound[0].cElements =uIsRead;
rgsabound[0].lLbound = 0;
psa = SafeArrayCreate(VT_UI1,1,rgsabound);
for(long index=0;index<uIsRead;index++)
{
if(FAILED(SafeArrayPutElement(psa,&index,&bVal[index])))
::MessageBox(NULL,"啊,又出毛病了。","提示",MB_OK │ MB_ICONWARNING);
}
varChunk.vt = VT_ARRAY│VT_UI1;
varChunk.parray = psa;
try{
m_pRecordset->Fields->GetItem("photo")->AppendChunk(varChunk);
}
catch (_com_error &e)
{
CString str=(char*)e.Description();
::MessageBox(NULL,str+"\n又出毛病了。","提示",MB_OK │ MB_ICONWARNING);
}
::VariantClear(&varChunk);
::SafeArrayDestroyData( psa);
if(uIsRead<ChunkSize)break;
}//while(1)
f.Close();

//从数据库读一张照片:
CFile f;
f.Open("h:\\bbb.jpg",CFile::modeWrite│CFile::modeCreate);
long lPhotoSize = m_pRecordset->Fields->Item["photo"]->ActualSize;
long lIsRead=0;

_variant_t varChunk;
BYTE buf[ChunkSize];
while(lPhotoSize>0)
{
lIsRead=lPhotoSize>=ChunkSize? ChunkSize:lPhotoSize;
varChunk = m_pRecordset->Fields->
Item["photo"]->GetChunk(lIsRead);
for(long index=0;index<lIsRead;index++)
{
::SafeArrayGetElement(varChunk.parray,&index,buf+index);
}
f.Write(buf,lIsRead);
lPhotoSize-=lIsRead;
}//while()
f.Close();

12. 使用SafeArray问题

  学会使用SafeArray也是很重要的,因为在ADO编程中经常要用。它的主要目的是用于automation中的数组型参数的传递。因为在网络环境中,数组是不能直接传递的,而必须将其包装成SafeArray。实质上SafeArray就是将通常的数组增加一个描述符,说明其维数、长度、边界、元素类型等信息。SafeArray也并不单独使用,而是将其再包装到VARIANT类型的变量中,然后才作为参数传送出去。在VARIANT的vt成员的值如果包含VT_ARRAY│...,那么它所封装的就是一个SafeArray,它的parray成员即是指向SafeArray的指针。SafeArray中元素的类型可以是VARIANT能封装的任何类型,包括VARIANT类型本身。

  使用SafeArray的具体步骤:

  方法一:

  包装一个SafeArray:

  (1). 定义变量,如:





VARIANT varChunk;
SAFEARRAY *psa;
SAFEARRAYBOUND rgsabound[1];

  (2). 创建SafeArray描述符:






uIsRead=f.Read(bVal,ChunkSize);//read array from a file.
if(uIsRead==0)break;
rgsabound[0].cElements =uIsRead;
rgsabound[0].lLbound = 0;
psa = SafeArrayCreate(VT_UI1,1,rgsabound);

  (3). 放置数据元素到SafeArray:





for(long index=0;index<uIsRead;index++)
{
if(FAILED(SafeArrayPutElement(psa,&index,&bVal[index])))
::MessageBox(NULL,"出毛病了。","提示",MB_OK │ MB_ICONWARNING);
}

  一个一个地放,挺麻烦的。

  (4). 封装到VARIANT内:






varChunk.vt = VT_ARRAY│VT_UI1;
varChunk.parray = psa;

  这样就可以将varChunk作为参数传送出去了。

  读取SafeArray中的数据的步骤:

  (1). 用SafeArrayGetElement一个一个地读





BYTE buf[lIsRead];
for(long index=0;index<lIsRead;index++)
{
::SafeArrayGetElement(varChunk.parray,&index,buf+index);
}

  就读到缓冲区buf里了。

  方法二:

  使用SafeArrayAccessData直接读写SafeArray的缓冲区:

  (1). 读缓冲区:






BYTE *buf;
SafeArrayAccessData(varChunk.parray, (void **)&buf);
f.Write(buf,lIsRead);
SafeArrayUnaccessData(varChunk.parray);

  (2). 写缓冲区:





BYTE *buf;
::SafeArrayAccessData(psa, (void **)&buf);
for(long index=0;index<uIsRead;index++)
{
buf[index]=bVal[index];
}
::SafeArrayUnaccessData(psa);

varChunk.vt = VT_ARRAY│VT_UI1;
varChunk.parray = psa;

  这种方法读写SafeArray都可以,它直接操纵SafeArray的数据缓冲区,比用SafeArrayGetElement和SafeArrayPutElement速度快。特别适合于读取数据。但用完之后不要忘了调用::SafeArrayUnaccessData(psa),否则会出错的。

  13. 使用书签( bookmark )

  书签可以唯一标识记录集中的一个记录,用于快速地将当前记录移回到已访问过的记录,以及进行过滤等等。Provider会自动为记录集中的每一条记录产生一个书签,我们只需要使用它就行了。我们不能试图显示、修改或比较书签。ADO用记录集的Bookmark属性表示当前记录的书签。

  用法步骤:

  (1). 建立一个VARIANT类型的变量

_variant_t VarBookmark;

  (2). 将当前记录的书签值存入该变量

  也就是记录集的Bookmark属性的当前值。

VarBookmark = rst->Bookmark;

  (3). 返回到先前的记录

  将保存的书签值设置到记录集的书签属性中:






// Check for whether bookmark set for a record
if (VarBookmark.vt == VT_EMPTY)
printf("No Bookmark set!\n");
else
rst->Bookmark = VarBookmark;

  设置完后,当前记录即会移动到该书签指向的记录。
14、设置过滤条件

  Recordset对象的Filter属性表示了当前的过滤条件。它的值可以是以AND或OR连接起来的条件表达式(不含WHERE关键字)、由书签组成的数组或ADO提供的FilterGroupEnum枚举值。为Filter属性设置新值后Recordset的当前记录指针会自动移动到满足过滤条件的第一个记录。例如:

rst->Filter = _bstr_t ("姓名='赵薇' AND 性别=’女’");

  在使用条件表达式时应注意下列问题:

  (1)、可以用圆括号组成复杂的表达式

  例如:

rst->Filter = _bstr_t ("(姓名='赵薇' AND 性别=’女’) OR AGE<25");>Filter = _bstr_t ("(姓名='赵薇' OR 性别=’女’) AND AGE<25");>Filter = _bstr_t ("(姓名='赵薇' AND AGE<25)>Filter = _bstr_t ("姓名 LIKE '*赵*' ");

  也可以只是尾部带星号:

rst->Filter = _bstr_t ("姓名 LIKE '赵*' ");

  Filter属性值的类型是Variant,如果过滤条件是由书签组成的数组,则需将该数组转换为SafeArray,然后再封装到一个VARIANT或_variant_t型的变量中,再赋给Filter属性。

  15、索引与排序

  (1)、建立索引

  当以某个字段为关键字用Find方法查找时,为了加快速度可以以该字段为关键字在记录集内部临时建立索引。只要将该字段的Optimize属性设置为true即可,例如:

pRst->Fields->GetItem("姓名")->Properties->
GetItem("Optimize")->PutValue("True");
pRst->Find("姓名 = '赵薇'",1,adSearchForward);
......
pRst->Fields->GetItem("姓名")->Properties->
GetItem("Optimize")->PutValue("False");
pRst->Close();

  说明:Optimize属性是由Provider提供的属性(在ADO中称为动态属性),ADO本身没有此属性。

  (2)、排序

  要排序也很简单,只要把要排序的关键字列表设置到Recordset对象的Sort属性里即可,例如:

pRstAuthors->CursorLocation = adUseClient;
pRstAuthors->Open("SELECT * FROM mytable",
_variant_t((IDispatch *) pConnection),
adOpenStatic, adLockReadOnly, adCmdText);
......
pRst->Sort = "姓名 DESC, 年龄 ASC";

  关键字(即字段名)之间用逗号隔开,如果要以某关键字降序排序,则应在该关键字后加一空格,再加DESC(如上例)。升序时ASC加不加无所谓。本操作是利用索引进行的,并未进行物理排序,所以效率较高。
但要注意,在打开记录集之前必须将记录集的CursorLocation属性设置为adUseClient,如上例所示。Sort属性值在需要时随时可以修改。

  16、事务处理

  ADO中的事务处理也很简单,只需分别在适当的位置调用Connection对象的三个方法即可,这三个方法是:

  (1)、在事务开始时调用

pCnn->BeginTrans();

  (2)、在事务结束并成功时调用

pCnn->CommitTrans ();

  (3)、在事务结束并失败时调用

pCnn->RollbackTrans ();

  在使用事务处理时,应尽量减小事务的范围,即减小从事务开始到结束(提交或回滚)之间的时间间隔,以便提高系统效率。需要时也可在调用BeginTrans()方法之前,先设置Connection对象的IsolationLevel属性值,详细内容参见MSDN中有关ADO的技术资料。
三、使用ADO编程常见问题解答

  以下均是针对MS SQL 7.0编程时所遇问题进行讨论。

  1、连接失败可能原因

  Enterprise Managemer内,打开将服务器的属性对话框,在Security选项卡中,有一个选项Authentication。

  如果该选项是Windows NT only,则你的程序所用的连接字符串就一定要包含Trusted_Connection参数,并且其值必须为yes,如:

"Provider=SQLOLEDB;Server=888;Trusted_Connection=yes"
";Database=master;uid=lad;";

  如果不按上述操作,程序运行时连接必然失败。

  如果Authentication选项是SQL Server and Windows NT,则你的程序所用的连接字符串可以不包含Trusted_Connection参数,如:

"Provider=SQLOLEDB;Server=888;Database=master;uid=lad;pwd=111;";

  因为ADO给该参数取的默认值就是no,所以可以省略。我认为还是取默认值比较安全一些。

  2、改变当前数据库的方法

  使用Tansct-SQL中的USE语句即可。

  3、如何判断一个数据库是否存在

  (1)、可打开master数据库中一个叫做SCHEMATA的视图,其内容列出了该服务器上所有的数据库名称。

  (2) 、更简便的方法是使用USE语句,成功了就存在;不成功,就不存在。例如:

try{
m_pConnect->Execute ( _bstr_t("USE INSURANCE_2002"),NULL,
adCmdText│adExecuteNoRecords );
}
catch (_com_error &e)
{
blSuccess=FALSE;
CString str="数据库INSURANCE_2002不存在!\n";
str+=e.Description();
::MessageBox(NULL,str,"警告",MB_OK │ MB_ICONWARNING);
}

  4、判断一个表是否存在

  (1)、同样判断一个表是否存在,也可以用是否成功地打开它来判断,十分方便,例如:

try{
m_pRecordset->Open(_variant_t("mytable"),
_variant_t((IDispatch *)m_pConnection,true), adOpenKeyset,
adLockOptimistic, adCmdTable);
}
catch (_com_error &e)
{
::MessageBox(NULL,"该表不存在。","提示",MB_OK │ MB_ICONWARNING);
}

  (2)、要不然可以采用麻烦一点的办法,就是在MS-SQL服务器上的每个数据库中都有一个名为sysobjects的表,查看此表的内容即知指定的表是否在该数据库中。

  (3)、同样,每个数据库中都有一个名为TABLES的视图(View),查看此视图的内容即知指定的表是否在该数据库中。

  5、类型转换问题

  (1)、类型VARIANT_BOOL

  类型VARIANT_BOOL等价于short类型。The VARIANT_BOOL is equivalent to short. see it's definition below:
typdef short VARIANT_BOOL

  (2)、_com_ptr_t类的类型转换

  _ConnectionPtr可以自动转换成IDspatch*类型,这是因为_ConnectionPtr实际上是_com_ptr_t类的一个实例,而这个类有此类型转换函数。

  同理,_RecordsetPtr和_CommandPtr也都可以这样转换。

  (3)、_bstr_t和_variant_t类

  在ADO编程时,_bstr_t和_variant_t这两个类很有用,省去了许多BSTR和VARIANT类型转换的麻烦。

  6、打开记录集时的问题

  在打开记录集时,在调用Recordset的Open方法时,其最后一个参数里一定不能包含adAsyncExecute,否则将因为是异步操作,在读取数据时无法读到数据。

  7、异常处理问题

  对所有调用ADO的语句一定要用try和catch语句捕捉异常,否则在发生异常时,程序会异常退出。

  8、使用SafeArray问题

  在初学使用中,我曾遇到一个伤脑筋的问题,一定要注意:

  在定义了SAFEARRAY的指针后,如果打算重复使用多次,则在中间可以调用::SafeArrayDestroyData释放数据,但决不能调用::SafeArrayDestroyDescriptor,否则必然出错,即使调用SafeArrayCreate也不行。例如:

SAFEARRAY *psa;
......
//When the data are no longer to be used:
::SafeArrayDestroyData( psa);

  我分析在定义psa指针时,一个SAFEARRAY的实例(也就是SAFEARRAY描述符)也同时被自动建立了。但是只要一调用::SafeArrayDestroyDescriptor,描述符就被销毁了。

  所以我认为::SafeArrayDestroyDescriptor可以根本就不调用,即使调用也必须在最后调用。

  9、重复使用命令对象问题

  一个命令对象如果要重复使用多次(尤其是带参数的命令),则在第一次执行之前,应将它的Prepared属性设置为TRUE。这样会使第一次执行减慢,但却可以使以后的执行全部加快。

  10、绑定字符串型字段问题

  如果要绑定的字段是字符串类型,则对应的字符数组的元素个数一定要比字段长度大2(比如m_szau_fname[22],其绑定的字段au_fname的长度实际是20),不这样绑定就会失败。

  11、使用AppendChunk的问题

  当用AddNew方法刚刚向记录集内添加一个新记录之后,不能首先向一个长数据字段(image类型)写入数据,必须先向其他字段写入过数据之后,才能调用AppendChunk写该字段,否则出错。也就是说,AppendChunk不能紧接在AddNew之后。另外,写入其他字段后还必须紧接着调用 AppendChunk,而不能调用记录集的Update方法后,才调用AppendChunk,否则调用AppendChunk时也会出错。换句话说,就是必须AppendChunk在前,Update在后。因而这个时候就不能使用带参数的AddNew了,因为带参数的AddNew会自动调用记录集的 Update,所以AppendChunk就跑到Update的后面了,就只有出错了!因此,这时应该用不带参数的AddNew。

  我推测这可能是MS SQL 7.0的问题,在MS SQL 2000中则不存在这些问题,但是AppendChunk仍然不能在Update之后。

  四、小结

  一般情况下,Connection和Command的Execute用于执行不产生记录集的命令,而Recordset的Open用于产生一个记录集,当然也不是绝对的。特别Command主要是用于执行参数化的命令,可以直接由Command对象执行,也可以将Command对象传递给 Recordset的Open。

  本文中的代码片断均在VC++ 6.0、Windows NT 4.0 SP6和MS SQL 7.0中调试通过。相信您读过之后,编写简单的数据库程序应该没有问题了。当然要编写比较实用的、复杂一点的程序,还需要对OLE DB、ADO以及数据库平台再多了解一点,希望您继续努力,一定会很快成功的!详细参考资料请参见微软MSDN July 2000光盘或MS SQL 7.0在线文档资料(Books online)。文中难免有错误和不妥之处,敬请各位批评指正!

Dao访问Access2000的数据库

Dao访问Access2000的数据库!

前两天在修改一个VC6.0编写的数据库程序。由于程序是很早以前写的,用的访问数据库的方式是DAO访问Access97。但是现在的数据库升级到了Access2000,总是出现问题,提示为:不能识别数据库的格式?为此大伤脑筋,差了很多资料,也问了很多网上的高手,都说DAO不能访问Access2000。这该如何是好呢?如果改为ADO或是ODBC访问,对程序的改动将会很大,很复杂。最后,在一同事的帮助下解决了!解决方法很简单,就是加了一句话。

AfxGetModuleState()->m_dwVersion = 0x0601;
只要加在InitInstance()就可以了!

为什么呢?原来DAO的采用jet数据库引擎的原因,访问Access97的引擎是3.5版本,而访问Access2000的是4.0版本。所以,要用DAO访问Access2000必须升级引擎到4.0上面这句话就是这个作用。
唉!原来就这么简单!

#ifndef _AFXDLL
#define _MFC_VER 0x0601
#else
AfxGetModuleState()->m_dwVersion = 0x0601;
#endif

如何让VC 里可以使用ACCSEE 2000

安装SP3然后,mfc exe工程中initinstance中加入
AfxGetModuleState()->m_dwVersion = 0x0601;

只不过制作DLL无法偷梁换柱。

参考:

Ing. Jozef Sakalos
Slovakia
A This question is also discussed in the Knowledge Base article Q236991 (http://support.microsoft.com/support/kb/articles/ Q236/9/91.asp), but since so many people have asked, I'll cover it here. The problem is that MFC is using the wrong DAO DLL. When you call AfxDaoInit to initialize DAO for MFC, MFC first determines which version of the DAO engine to load.

// in daocore.cpp
void AFXAPI AfxDaoInit()
{



BYTE bUseDao = _AfxDetermineDaoVersion();
switch (bUseDao) {
case 35:
// Use DAO350.DLL
break;
case 30:
// Use DAO300.DLL
break;
case 36:
// Use DAO360.DLL
break;
}
}


_AfxDetermineDaoVersion is a macro that expands to 30, 35, or 36, depending on which version of MFC you have.

#if _MFC_VER >= 0x0601
#define _AfxDetermineDaoVersion() (36)
#else
#define _AfxDetermineDaoVersion() (35)
#endif


The previous code only shows how _AfxDetermineDaoVersion expands when you link with a static MFC (_AFXDLL not #defined). If you're linking with MFC as a DLL (_AFXDLL #defined), _AfxDetermineDaoVersion expands to a real inline function:

static inline BYTE _AfxDetermineDaoVersion()
{
BYTE bReturn = 35;
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
if (pModuleState->m_dwVersion < 0x421)
bReturn = 30;
else if (pModuleState->m_dwVersion >= 0x0601)
bReturn = 36;
return bReturn;
}


The only difference between these cases is that in the first situation, the MFC version number is hardcoded as _MFC_ VER. For dynamically linked projects, MFC gets the version from your module's state. This is required because apps with different MFC versions can potentially call the same MFC42.DLL. Either way, MFC uses its own version number to determine which version of DAO to use. If the MFC version is 0x0601 (6.1) or higher, MFC uses DAO 3.6, the version used by Microsoft Access 2000.
So in order to gain access to Microsoft Access 2000 databases, you need MFC version 6.1. There's only one problem: there is no version 6.1 of MFC. The current version is 6.0!

// in afxver_.h
#define _MFC_VER 0x0600


The only way I know to get MFC 6.1 requires a time-travel device. So the question is: what's going on here? If there's no version 6.1, why does daocore.cpp have 0x0601 in it? The obvious way to support a different DAO version would have been to put the DAO version number as an argument to AfxDaoInit—after all, why should the DAO version be tied to the MFC version? But who thinks of version numbers the first time they write the code? But once a signature for AfxDaoInit was chosen, the MFC folks were forever constrained because they had to maintain strict binary backward compatibility with MFC42.DLL. Adding an argument to AfxDaoInit would change the function signature and hence the DLL entries. So the problem confronting the Redmondtonians was: how to tell MFC to use the new DAO, without introducing any new functions or function arguments? Using the MFC version number provided a convenient, if kludgy, way. (Of course, I'm only guessing that's what happened.)
So, to use the latest DAO engine, all you have to do is make MFC pretend it's the mysterious nonexistent version 6.1. If you're building with MFC as an MFC DLL, all you have to do is write:

AfxGetModuleState()->m_dwVersion = 0x0601;
AfxDaoInit();


That is, set the version number in your module state to 6.1 before you call AfxDaoInit. If you're the paranoid sort—and who isn't paranoid from time to time—you can save the original version and restore it after doing your business. A simple grep reveals, however, that _AfxDetermineDaoVersion is the only function in all of MFC that uses the mysterious 0x0601 version, so changing the version to 0x0601 should have no side effects.
If you're using the static MFC library, life is a little more difficult. The MSDN™ article mentioned earlier advises that you edit daocore.cpp and rebuild MFC. Yikes! That's a bit drastic for my taste. You should never rebuild MFC if you can avoid it. Better to confine whatever workarounds you need to your own project. Life is safer and more predictable if you make a hard and fast rule that the MFC libraries and code hierarchy are off limits. In fact, you should do what I do as soon as I install any new version of MFC or any SDK—set the read-only attribute on all the files.
But then how can you make MFC use DAO360.DLL in a static MFC build? Easy. Just copy the offending MFC source file (in this case, daocore.cpp) to your code directory, make the changes, and add it to your project. This is always a better way to modify MFC than munging MFC directly. When the linker needs a function or symbol from daocore.obj, it'll find the one in your modified version and, therefore, never need to load daocore.obj from MFC42.LIB. Of course, most MFC modules won't compile outside of their context; there are always some #include files from \mfc\src that you need. The easiest way to find them is just keep compiling and fixing until there are no more errors to fix. For daocore.cpp it turns out you need afximpl.h and daoimpl.h. afximpl.h is the MFC main internal include file, but in fact the only thing daocore.cpp needs from afximpl.h is _countof, so it's easier to copy the definition.

// Add these lines to the top of daocore.cpp
#include "daoimpl.h"
#define _countof(array) (sizeof(array)/sizeof(array[0]))
#undef _MFC_VER
#define _MFC_VER 0x0601


Once you add these lines to daocore.cpp and copy daoimpl.h to your project, daocore.cpp will compile with only a single warning, which you can safely ignore. And since you've redefined _MFC_VER to 0x0601, _AfxDetermineDaoVersion will now use DAO 3.6 (DAO360.DLL). Problem solved.
Just don't forget—this is only a stopgap measure! Once the Redmondtonians release the next MFC, you can return to life as usual. But you should remember this general technique: if you're absolutely, positively sure you need to modify MFC, don't modify the source directly. Instead, copy the module you want to change, plus any #include files and #defines it needs, and add it to your project. This method works whether you're linking with MFC as a static or dynamic library.

Have a question about programming in C or C++? Send it to Paul DiLascia at askpd@pobox.com

From the January 2000 issue of Microsoft Systems Journal.

VC中如何使用ADO?

#include "stdafx.h"
#import "c:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF", "EndOfFile")
int main(int argc, char* argv[])
{
printf("Use ADO to open C:\\tmp\\test.mdb database file!\n");
CoInitialize(NULL);
try
{
_ConnectionPtr pConn("ADODB.Connection");
_RecordsetPtr pRst("ADODB.Recordset");
pConn->Open("PROVIDER=MSDASQL;DRIVER={Microsoft Access Driver (*.mdb)};DBQ=C:\\tmp\\test.mdb;UID=;PWD=aaa;"

//pConn->Open("Provider= Microsoft.Jet.OLEDB.4.0;Data Source=C:\\tmp\\test.mdb;"//this is also OK
,"","",adConnectUnspecified);
//Open "users" table
pRst->Open("users", _variant_t((IDispatch *) pConn, true),
adOpenStatic, adLockReadOnly, adCmdTable);
FieldsPtr fds=pRst->GetFields();
printf("printf field name of the table\n");
for(int i=0;iGetCount();i++)
{
FieldPtr fd=fds->GetItem(_variant_t(short(i)));
printf("%s ",(LPCTSTR)fd->GetName());
}
printf("\n");
pRst->Close();
pConn->Close();
}
catch (_com_error &e)
{
printf("Description = '%s'\n", (char*) e.Description());
}
::CoUninitialize();
return 0;
}

浅析VC与matlab接口编程 (zz)

浅析VC与matlab接口编程

Matcom是mathTools公司推出的一个能将M文件转化成相同功能C++代码的工具。相比Matlab自带的编译器Compiler,用Matcom转化代码要简单和方便
得多。本文将结合作者编程经验,以VC6.0和Matcom为例,详细介绍如何利用Matcom进行VC与Matlab的联合编程。


  Matcom安装
安装Matcom前要已求安装VC6.0。在安装过程中出现选择编译器对话框,选择“是”(图一)。出现选择是否安装Matlab时,选“否”。其他选项采用默认设置。Matcom可以独立于Matlab运行,但需要外部的C++编译器,指定Matlab的位置是为了让编译文件中需要的一些系统函数找到路径用的。





              
图一



  
安装完成后,启动界面MIDEVA(图二)。MIDEVA集成开发环境包括命令行窗口、变量列表窗口、命令列表窗口和编译链接信息窗口等几部分,并有详细的帮助文档。




图二

Matcom命令输入方法与Matlab相同。如果安装在中文版操作系统时,输入命令前加一空格。(如图三)。



                  图三




下面详细介绍如何将m文件编译后放入VC中。

1、启动MIDEVA ,File->New,新建如下Test1.m文件:

x=1:0.1:10; y=sin(x); plot(x,y); 

将文件保存(如图四)。




    图四



m文件保存的默认位置为:matcom安装目录\matcom45,生成的C++文件保存的默认位置为:matcom安装目录\matcom45\Debug\,本例matcom安装目录为C盘 即 C:\matcom45\Debug\,在该目录下将会产生Test1.h、Test1.cpp、Test1.r 、Test1.obj文件。




2、在VC中用MFC Wizard(exe)创建一个基于对话框名为Example的工程。

在面板上添加一个ID为IDC_BUTTON1按扭。



3、将C:\matcom45\Debug目录下的Test1.cpp文件的内容拷贝到

void CExamleDlg::OnButton1()下,(如图五)。




             图五





4、将C:\matcom45\lib\下的matlib.h 和v4501v文件和C:\matcom45\debug下的Test1.h文件拷贝到工程Example目录下,然后在VC中将库文件和头文件加入到工程中:工程->添加工程->Files,选择刚刚拷贝到Example目录下的matlib.h 、v4501v和Test1.h文件(如图六)。




         
图六



5、在ExamleDlg.cpp中加入如下代码:


#include "matlib.h"
#include "Test1.h"

6、在void CExamleDlg::OnButton1()中分别添加一个初始化类库调用函数”initM(MATCOM_VERSION)”和一个结束类库调用函数”exitM()”(如图七)。






             图七



运行程序,点击画图按扭(如图八)。

 



                            
图八



  程序的release版本需要ago4501.dll、v4501v.dll、v4501.lib三个文件的支持。
以上只是介绍了VC和Matcom接口编程的一个简单例子,功能并不完善,也远远达不到一个软件所要求的水平,但只要熟悉了VC和Matcom接口编程的基本原理和方法,相信大家一定能够编出优秀的软件。



在“浅析VC与Matlab联合编程
(一)
”中简单介绍了VC与matcom的接口编程,实现了VC调用matcom的一个基本功能,要真正熟练使用接口编程,还要对函数调用有一定了解。本文通过一个实例,较详细的介绍如何在VC中调用matcom编译后的函数。



  首先新建一个函数huatu.m,该函数的功能是:接受用户输入的左端点值和右端点值两个输入参数,然后在该区间画正弦图形。函数还将生成两个随机数,作为一个点的坐标在图中打印出来(绿点),用来说明如何在VC中处理调用函数的返回值。








步骤如下:

1.建立一个名为Example2基于对话框的MFC(exe)工程。

2.在matcom中新建一个名为huatu.m文件,如图1;



图1



3.在matcom命令窗口中运行该函数,如图2;




图2




4.将生成的huatu.h、huatu.cpp(默认位置为 安装目录:\matcom45\samples\Debug)和matlib.h、v4501v.lib(默认位置为 安装目录:\matcom45\lib)四个文件拷贝到建立的工程Example2目录下。



5.将第四步拷贝的四个文件加入到VC工程中:工程->添加工程->文件,选择刚才拷贝到Example2目录下的四个文件。完成后如图3;




图3



6.在Example2dlg.cpp中添加头文件:(如图4所示)


#include "matlib.h"
#include "huatu.h"





图4



7.为工程建立界面:添加一个Button按扭控件;添加四个Edit box控件,用于显示数据;添加五个Static text控件,四个显示文字,一个显示图形,将显示图形的Static text的ID设置为ID_PIC。其他所有控件属性保持默认(为了看图方便,可以适当调整显示图形Static text控件的边缘)。如图5所示




图5



8.按Ctrl+W为控件添加变量。如图6所示




图6




9.为Button1按钮添加响应代码。如图7所示




图7



10.运行程序,输入左端点和右端点,点击”画图”按钮,结果如图8所示。(本例只是为了说明如何调用函数,没有进行容错处理)




图8



注:如果在编译中出现下列错误:


fatal error C1010: unexpected end of file while looking for precompiled header directive


  进行下列设置:工程->设置-> C/C++ 选择precompiled headers 选择第一或第二项:自动选择预补偿页眉,如图9所示





图9



  本例介绍了在VC中如何调用用matcom编译的m函数的方法,并且基本上实现了VC和Matlab的无缝结合,希望对大家编程会有所帮助,现在时间很紧,下次有机会详细介绍m文件翻译成C++语言后代码的含义。

在“浅析VC与Matlab联合编程<一>”和“浅析VC与Matlab联合编程<二>”中介绍了matcom,这个工具可以将用matlab写的m文件翻译成C++文件,或者是可执行文件(exe)或库文件(dll)。但是matcom在很多方面也有限制,比如,对struct等类的支持有缺陷,部分绘图语句无法实现或得不到准确图象,尤其是三维图象。
  实际上VC与matlab的接口实现方法有很多种,matcom只是其中一种,本文再介绍一种比较容易实现的方法:COM接口方法。COM (Compponent Object Model组件对象模型)是一项比较复杂的技术,详细讲的话几本书也讲不完,所以在这里不作介绍,本文通过一个例子详细介绍如何在matlab下做COM 组件,以及如何在VC中调用COM组件。
  首先在Matlab编辑器里编辑m函数文件:启动matlab->File->New->M-file 函数内容如图1:该函数无输入输出参数,文件保存为huatu.m。


图1 m函数huatu.m

在matlab下建立COM组件,步骤如下:
1、在matlab command window 输入如下命令:
>> comtool
出现com编辑界面,如图2:


图2 com组件编辑界面

2、新建工程:File->New Project…,如图3。

图2 com组件属性设置

3、设置组件属性,在"Component name"项中填写组件名称"component",这时候会自动生成类"component",在"Class name"项中填写类名称"huatu",如图4,

图4 com组件属性设置1

  为了便于区分,选中"Classes"中的"component",点击“remove”按钮,将类component移除,再点击"Add>>"添加新类huatu,结果如图5。点击"OK",接下来出现一个对话框,选择"Yes".

图5 com组件属性设置2

4. 添加文件:选中左边工作区的"huatu",点击Project->Add File…,选择已经编辑好的函数文件huatu.m,如图6。需要注意的是m文件必须是m函数,否则会报错,如果是m脚本文件的话,只需要改为无输入输出参数的m函数即可。

图6 添加m文件

5、生成 com组件:点击Build->COM Object…,结果如图7。

图7

com组件已经由matlab做好,默认的保存位置为:matlab安装位置\work\component。

VC中调用COM组件,步骤如下:
1、在VC中建立名为ComHuaTu的基于对话框的MFC(exe)。
2、面板上添加一个名为“画图”的button按钮,如图8。


图7 工程界面

3、将component_idl.h 和component_idl_i.c文件拷贝到VC建立的工程ComHuaTu目录下。两文件默认目录为\work\component\src
4、将上面两文件加入工程:工程->添加工程->Files,选择刚刚拷到目录下的component_idl.h 和component_idl_i.c文件。
5、将目录/extern/include/下的mwcomtypes.h拷贝到工程ComHuaTu目录下,并加入到工程中,方法同上。
6、为程序添加头文件component_idl.h 、component_idl_i.c和mwcomtypes.h,结果如图9

图9 添加头文件


7、按钮画图函数添加代码:

图10 添加按钮函数代码

函数代码的意义涉及到COM,正在写VC调用COM组件所涉及到的有关COM方面的知识。
8、设置预编译头文件:工程->设置(快捷键Alt+F7),选择C/C++项precomplied Headers,设置如图11

图11 预编译头文件设置

9、编译、连接、运行,结果如图12。

图11 运行结果

  可以看出利用VC调用com组件的方式来调用matlab比较简单,而且几乎支持matlab所有的函数,在m文件较大、用matcom、调用 math library或使用mcc方式无法实现的时候,推荐使用。需要说明的是,以上程序拷到另一台机器上是无法直接运行的,因为COM组件没有新机器上注册,如果要在另一台机器上使用的话,还需要打包安装COM组件,并且由于本程序代码很简单,所以也就不提供源程序了,自己可以动手做一下。
  本人正在写一本关于VC和matlab接口方面的书,估计年底完稿,详细介绍VC和matlab接口的几种方法,并提供详细的例子,例子都是我自己写过的,欢迎大家提供些好的意见和建议。

浅析VC与MATLAB联合编程<一>浅析VC与MATLAB联合编程<二>浅析VC与MATLAB联合编程<三>中简单介绍了VC和MATLAB接口的两种方法,初学者可能会问为什么要用VC和MATLAB接口,接口的实质又是什么,本文就通过一个例子来回答这两个问题。


  
首先来介绍一下MATLAB。
MATLAB名字由MATrix和 LABoratory 两词的前三个字母组合而成。那是20世纪七十年代后期的事:时任美国新墨西哥大学计算机科学系主任的Cleve Moler教授出于减轻学生编程负担的动机,为学生设计了一组调用LINPACK和EISPACK库程序的“通俗易用”的接口,此即用FORTRAN编写的萌芽状态的MATLAB。


  经几年的校际流传,在Little的推动下,由Little、Moler、Steve Bangert合作,于1984年成立了MathWorks公司,并把MATLAB正式推向市场。从这时起,MATLAB的内核采用C语言编写,而且除原有的数值计算能力外,还新增了数据图视功能。

  MATLAB以商品形式出现后,仅短短几年,就以其良好的开放性和运行的可靠性,使原先控制领域里的封闭式软件包(如英国的UMIST,瑞典的LUND和SIMNON,德国的KEDDC)纷纷淘汰,而改以MATLAB为平台加以重建。在时间进入20世纪九十年代的时候,MATLAB已经成为国际控制界公认的标准计算软件。

MATLAB已经成为目前国际上最流行、应用最广泛的科学工程计算软件,它广泛的应用于自动控制、数学运算、信号分析、图象处理、财务分析等各行各业。由于它具有强大的计算和绘图功能、大量稳定可靠的算法库和简洁高效的编程语言,已成为数学计算工具方面事实上的标准。

  
在欧美大学里,诸如应用代数、数理统计、自动控制、数字信号处理、模拟与数字通信、时间序列分析、动态系统仿真等课程的教科书都把MATLAB作为内容。这几乎成了九十年代教科书与旧版书籍的区别性标志。在那里,MATLAB是攻读学位的大学生、硕士生、博士生必须掌握的基本工具。

  在国际学术界,MATLAB已经被确认为准确、可靠的科学计算标准软件。在许多国际一流学术刊物上(尤其是信息科学刊物)都可以看到MATLAB的应用。
在设计研究单位和工业部门,MATLAB被认作进行高效研究、开发的首选软件工具。如美国National Instruments公司信号测量、分析软件LabVIEW,Cadence公司信号和通信分析设计软件SPW等,或者直接建筑在MATLAB之上,或者以MATLAB为主要支撑。又如HP公司的VXI硬件,TM公司的DSP,Gage公司的各种硬卡、仪器等都接受MATLAB的支持。




为了说明问题,我们举个例子:
解下列线形方程组:(如图1):


 



图1



用C语言(VC环境中)编写的代码为:(图2)










图2





编译、连接,运行结果为:(图3):





图3





而该方程在MATLAB中只需要一个命令“\”就可以解决:

在MATLAB中建立一个新的m文件,在MATLAB菜单中:File->New->M file(如图4):





图4





在M文件中输入代码,并保存为QJFCZ.m。(如图5)





图5







在MATLAB Command命令窗口中输入以下命令:

>> QJFCZ

然后回车,结果如下:


x =



-0.1429

2.7857

0

>>




  可以看出用MATLAB写出的代码十分简单,对于上面的方程,如果矩阵A和b的维数发生变化的话,
C代码还要重新编写,而用MATLAB书写的代码几乎不用改动,只需改变数据即可。


  
如果现在我们用VC编写的一个软件,其中要求解有5个未知数的线形方程组,如果用C\C++语言来写程序的话其编程量是很大的,
如果在C\C++语言中能用MATLAB那么简单的代码来实现该功能就可以大大减少工作量,VC和MATLAB接口就是做这个工作的。
MATLAB中提供了大量用C\C++重新编写的MATLAB库函数,包括初等数学函数、线形代数函数、矩阵操作函数、数值计算函数、
特殊数学函数、插值函数等等,还可以利用MATLAB的编译工具将m文件方便的转化为C\C++语言、可执行程序(exe)、动态连接库文件(dll)、COM组
件等等,并可直接供C\C++语言调用,利用VC和MATLAB接口技术可以在VC中充分发挥MATLAB的数值计算功能,并且可以不依赖MATLAB软件运行,我们在编写程序时,可以在VC下做出很漂亮的界面,而把复杂的数值处理交给MATLAB去做(实际上MATLAB也能做界面GUI(Graphic User Interface),而且做出的界面我认为
并不比VC逊色,但是需要MATLAB软件作为后台运行,如果不依赖MATLAB软件运行的话,可以在VC中调用GUI),然后通过接口技术将MATLAB集
成到VC中,这样可以大大减轻编程的负担,并减少程序编写时间。



  
事实上正是由于MATLAB数值计算功能的强大,MATLAB与许多程序都有接口,例如Fortran、VB、Java、SPSS(一个著名的统
计软件)、Excel、Word等,其实质也是通过接口在程序中调用MATLAB的功能。

在前面的介绍中,只是简介绍了VC与Matlab程序设计的几个简单例子,很多关键性的知识都没有介绍(例如函数的输入输出格式,VC中调用工具箱函数,编译器的使用,几种方法的混合使用等等)在以后的文章中,将会对这些知识进行介绍(因此标题也改了一下,呵呵,如果有错误的地方,还望大家多多指点)VC调用Matlab的方法有多种,为了对混合编程有一个大概认识,将常用的几种方法中介绍一下:



一、通过Matlab Engine方式

  Matlab Engine是指一组Matlab提供的接口函数,支持C语言, Matlab Engine采用C/S(客户机/服务器)模式,Matlab作为后台服务器,而C程序作为前台客户机,通过Windows的动态控件与服务器通信,向Matlab Engine传递命令和数据信息,从Matlab Engine接受数据信息。用户可以在前台应用程序中调用这些接口函数,实现对Matlab Engine的控制。采用这种方法几乎能利用Matlab全部功能,但是需要在机器上安装Matlab软件,而且执行效率低,因此在实际应用中不采用这种方法,在软件开发中也不可行,我认为适合个人使用或做演示用,因此不打算介绍。





二、直接调用Matlab的C/C++数学函数库


  Matlab中提供了可以供C/C++语言调用的C/C++数学函数库,其中包含了大量用C\C++语言重新编写的Matlab数学函数,这些函数涉及到线形代数、数值分析、傅立叶变换、多项式计算、解微分方程等,并且函数库中提供了大量各种矩阵操作函数,在VC中可以直接使用这些函数,通过这些函数可以在VC中方便的实现在Matlab中矩阵运算功能。可以看出用这种方法可以很灵活的调用Matlab来编写应用程序,但要求读者对C\C++语言比较熟悉,可以看出使用这种方法调用Matlab的工具箱函数有很大困难。适合对C\C++语言比较熟悉的用户使用,


三、用Matlab自带的Matlab Compiler

  Matlab Compiler的作用是将m文件转化成C/C++代码(也就是通常所用的mcc命令),这种源代码需要用C/C++编译器编译连接成独立应用程序,在将m文件转成独立应用程序的过程中生成的C/C++文件,原则上是可以被其它的C/C++代码调用的,编译器可以通过设置mcc命令的选项,将m文件编译成动态链接库文件、C/C++文件、可执行文件等一系列文件。到matlab R21.1为止,Matlab Compiler的m程序转换成C/C++代码功能有很多限制:


  1. 不能转换脚本m文件,只能转换m函数;

  2. 不能使用matlab对象;

  3. 不能用input或者eval操作matlab空间变量;


  4. 不能动态地命名变量,然后用load或者save命令来操作;

  5. 不能处理具有嵌套调用其他m文件的m文件;

  6. 不能使用MATLAB内联函数;


四、使用matlab的combuilde工具

  
COM是component object module的简称,它是一种通用的对象接口,任何语言只要按照这种接口标准,就可以实现调用它。matlab6.5新推出来的combuilder就是把用matlab编写的程序做成com组件,供其他语言调用。该方法实现简单,通用性强,而且几乎可以使用Matlab的任何函数(注意:不支持脚本文件,脚本文件使用时要改为函数文件),因此在程序较大、调用工具箱函数或调用函数较多时推荐使用,这也是Matlab公司(Matlab公司就是Mathworks公司)推荐的使用方法。




五、使用matcom工具。
  这是个第三方控件,很小(8M多),原本属于mathtool公司,后来被Mathworks公司合并了,使用该工具可以将m脚本文件和m函数转化成相同功能的C\C++文件,相比其它方法使用matcom具有如下优点:


  1. 转换过程简单(由matcom工具自动实现),容易实现;

  2. 可以方便的生成动态链接库文件(dll)和可执行文件(exe);

  3. 不仅可以转换独立的脚本文件,也可以转换嵌套脚本文件;

  4. 设置环境后,可以使用Matlab的工具箱函数;



但matcom也有以下不足:


  1. 对struct等类的支持有缺陷,对class数据类型;

  2. 部分绘图语句无法实现或得不到准确图象,尤其是三维图象;


因此在不涉及到三维做图以及m文件不大的情况下推荐使用。



  以上几种方法可以单独使用,也可以混合使用。这里简单的介绍了VC和matlab接口的几种方法,读者可以根据需要选择适合自己的方法。需要说明的是上以上几种方法并不是相互独立的,而是有相互联系的,比如使用C/C++函数库与使用编译器,对于实现相同功能的程序,直接调用函数库与使用编译器最终生成的代码可能相差不大,只不过一个是直接在VC中写C/C++代码,一个是在Matlab中写好m代码,然后通过编译器将m代码转化成相同功能的C/C++代码,而在转化的过程中也需要调用相应的C/C++函数库文件。在后面的文章中将从第二种方法开始进行介绍。