几种VC++数据库开发技术的比较

  从功能简单的数据库(如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在数据库开发方面有较强的优势,学会

在不同的场合选用不同的技术,对开发人员来说是必要的技术。

Read: 634

Visual C++中实现对图像数据的读取显示

在利用VC进行数据库编程时,经常需要处理数据库中的图像数据,将该图像从数据库中读取出来并显示,图像数据与 文本字段不同,它是作为OLE字段在数据库中存储,通过数据集对象的成员变量自动交换得到的图像数据,得到的数据并不能直接显示,如何处理图像数据,一直 是数据库编程中的一个难点,目前关于VC进行数据库编程的资料不少,但很少涉及图像数据的操作,笔者针对一现状,结合自己开发的一个项目,解决了如何显示 数据库中的图像这一问题,本文以操作ACESS数据库为例子,讲解一下自己的实现思路,希望对爱好VC编程的朋友们有所帮助,以起到抛砖引玉的作用。

为了简化问题,该数据库的表中只有一个名为Images的OLE字段,我使用DAO连接操作数据库,读取的图像数据显示在一个对话框上,至于使用 ODBC、DAO还是ADO,这要根据具体情况而定,但无论使用哪一种,对图像的显示来说,实现的过程是大同小异的。由于篇幅有限,文章中对如何实现数据 库的连接不再作具体的说明,有兴趣的读者朋友可以参考VC数据库编程的资料。实现过程中,首先定义一个CDaoRecordset的子类 CimageData如下:

class CimageData : public CDaoRecordset
{
public:
CimageData (CDaoDatabase* pDatabase = NULL);
DECLARE_DYNAMIC(CimageData)
file://{{AFX_FIELD(CimageData, CDaoRecordset)
CByteArray m_Images;//声明字节数组用来存放图像数据
file://}}AFX_FIELD
// Overrides
// ClassWizard generated virtual function overrides
file://{{AFX_VIRTUAL(CimageData)
public:
virtual CString GetDefaultDBName();
virtual CString GetDefaultSQL();
virtual void DoFieldExchange(CDaoFieldExchange* pFX);
file://}}AFX_VIRTUAL

该类的实现为:

CimageData:: CimageData (CDaoDatabase* pdb)
: CDaoRecordset(pdb)
{
file://{{AFX_FIELD_INIT(CimageData)
m_nFields = 1;//数据库的表中仅有一个字段
file://}}AFX_FIELD_INIT
m_nDefaultType = dbOpenDynaset;//以动态集方式打开数据库
}

CString CimageData::GetDefaultDBName()
{
return _T("E:\IMAGES.mdb");//默认的ACESS数据库在E盘,名为IMAGES
}

CString CimageData::GetDefaultSQL()
{
return _T("[Table]");//默认打开数据库中名为"Table"的表
}

void CimageData::DoFieldExchange(CDaoFieldExchange* pFX)
{
file://{{AFX_FIELD_MAP(CimageData)
pFX->SetFieldType(CDaoFieldExchange::outputColumn);
DFX_Binary(pFX, _T("[Images]"), m_Images);//以二进制方式在Images字段和m_Images变量间交换数据
file://}}AFX_FIELD_MAP
}

  有了该类,就可以定义相应的对象来与数据库中的图像字段交换数据,下面定义的函数GetImageData()说明了如何根据读取的OLE字段数据生成待显示的图像,需要注意的是该函数中使用的CBitmap类的变量Bitmap是预定义的一个全局变量:

BOOL CImageDlg:: GetImageData(CByteArray & DBArray)
{
CByteArray Array;
Array.Copy( DBArray);
int HeaderLen = 78 + sizeof(BITMAPFILEHEADER); file://确定图像头信息的起始位置
Array.RemoveAt( 0, HeaderLen ); // 移动到图像头信息的起始位置
BITMAPINFOHEADER &bmiHeader = *(LPBITMAPINFOHEADER)Array.GetData() ;
BITMAPINFO &bmInfo = *(LPBITMAPINFO)Array.GetData() ;
file://得到图像数据的头信息
int nColors=bmiHeader.biClrUsed ? bmiHeader.biClrUsed : 1 << bmiHeader.biBitCount;
file://确定图像的颜色数
LPVOID lpDIBBits;
if( bmInfo.bmiHeader.biBitCount > 8 )
lpDIBBits=(LPVOID)((LPDWORD)(bmInfo.bmiColors+bmInfo.bmiHeader.biClrUsed)+
((bmInfo.bmiHeader.biCompression == BI_BITFIELDS) ? 3 : 0));
else
lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);
file://得到图像各个像素的具体数据
CClientDC dc(NULL);
HBITMAP hBmp = CreateDIBitmap( dc.m_hDC,
&bmiHeader,
CBM_INIT,
lpDIBBits,
&bmInfo,
DIB_RGB_COLORS);
file://生成位图句柄
Bitmap.Attach( hBmp );//将该句柄与定义的Bitmap对象联系在一起
Array.RemoveAll(); file://释放内存

return TRUE;

}

  有了上面的准备工作,现在可以实现图像的显示函数了,其实现如下:

void CImageDlg::OnShowImage()
{
CimageData db;//定义记录集对象
db.Open();打开数据库
GetImageData(db.m_Images);//根据记录集对象的成员变量生成图像对象
file://以下是在对话框的固定区域显示图像
CPaintDC dc(this);
if (!(Bitmap.m_hObject == NULL))
{ CDC dcMem;
dcMem.CreateCompatibleDC( &dc ); file://create a Memory Image
CBitmap* pbmpOld ;
BITMAP BmpSize ;
Bitmap.GetBitmap(&BmpSize); file://get Image Size
pbmpOld = dcMem.SelectObject(&Bitmap);
dc.StretchBlt( 20, 20, 200, 200, &dcMem, 0, 0, BmpSize.bmWidth, BmpSize.bmHeight, SRCCOPY);
dcMem.SelectObject( pbmpOld );

}

  以上代码中使用的数据库为ACESS97,程序在windows98、Visual C++6.0环境下编译通过,运行正常。

Read: 760

Visual C++ 中的ODBC编程

ODBC(Open Database Connectivity,开放式数据库连接),是一种用来在相关或不相关的数据库管理系统(DBMS)中存取数据的标准应用程序接口(API)。本文给 出Windows 95 环境下用Visual C++ 进行ODBC 编程的具体方法及技巧。

—- 关键字:ODBC,Visual C++,Windows 编程。

—- 一.概述

—- ODBC 是一种使用SQL 的程序设计接口。使用ODBC 让应用程序的编写者避免了与数据源相联的复杂性。这项技术目前已经得到了大多数DBMS 厂商们的广泛支持。

—- Microsoft Developer Studio 为大多数标准的数据库格式提供了32 位ODBC 驱动器。这些标准数据格式包括有:SQL Server、Access、Paradox、dBase、FoxPro、Excel、Oracle 以及Microsoft Text。如果用户希望使用其他数据格式,用户需要相应的ODBC 驱动器及DBMS。

—- 用户使用自己的DBMS 数据库管理功能生成新的数据库模式后,就可以使用ODBC 来登录数据源。对用户的应用程序来说,只要安装有驱动程序,就能注册很多不同的数据库。登录数据库的具体操作参见有关ODBC 的联机帮助。

—- 二.MFC 提供的ODBC 数据库类

—- Visual C++ 的MFC 基类库定义了几个数据库类。在利用ODBC 编程时,经常要使用到CDatabase( 数据库类),CRecordSet( 记录集类) 和CRecordView( 可视记录集类)。其中:

—- CDatabase 类对象提供了对数据源的连接,通过它你可以对数据源进行操作。

—- CRecordSet 类对象提供了从数据源中提取出的记录集。CRecordSet 对象通常用于两种形式:动态行集(dynasets)和快照集(snapshots)。动态行集能保持与其他用户所做的更改保持同步。快照集则是数据的一 个静态视图。每一种形式在记录集被打开时都提供一组记录,所不同的是,当你在一个动态行集里滚动到一条记录时,由其他用户或是你应用程序中的其他记录集对 该记录所做的更改会相应地显示出来。

—- CRecordView 类对象能以控制的形式显示数据库记录。这个视图是直接连到一个CRecordSet 对象的表视图。

—- 三.应用ODBC 编程

—- 应用Visual C++ 的AppWizard 可以自动生成一个ODBC 应用程序框架。方法是:打开File 菜单的New 选项,选取Projects,填入工程名,选择MFC AppWizard (exe),然后按AppWizard 的提示进行操作。当AppWizard 询问是否包含数据库支持时,如果你想读写数据库,那么选定Database view with file support;而 阆敕梦适 菘獾男畔⒍ 幌牖匦此 龅母谋洌 敲囱《―atabase view without file support 选项就比较合适了。选择了数据库支持之后Database Source 按钮会激活,选中它去调用Data Options 对话框。在Database Options 对话框中会显示已向ODBC 注册的数据库资源,选定你所要操作的数据库,如:Super_ES,单击OK 后会出现Select Database Tables 对话框,其中列举了你所选中的数据库中包含的全部表,选择你希望操作的表后,单击OK。在选定了数据库和数据表之后,你可以按照惯例继续进行 AppWizard 操作。

—- 特别需要指出的是:在生成的应用程序框架View 类(如:CSuper_ESView)中包含一个指向CSuper_ESSet 对象的指针m_pSet,该指针由AppWizard 建立,目的是在视表单和记录集之间建立联系,使得记录集中的查询结果能够很容易地在视表单上显示出来。有关m_pSet 的详细用法可以参见Visual C++ Online Book。

—- 程序与数据语言建立联系,使用CDatebase::OpenEx() 或CDatabase::Open() 函数来进行初始化。数据库对象必须在你使用它构造一个记录集对象之前被初始化。

—- 下面举例说明在Visual C++ 环境中ODBC 的编程技巧:

—- 1 .查询记录

—- 查询记录使用CRecordSet::Open() 和CRecordSet::Requery() 成员函数。在使用CRecordSet 类对象之前,必须使用CRecordSet::Open() 函数来获得有效的记录集。一旦已经使用过CRecordSet::Open() 函数,再次查询时就可以应用CRecordSet::Requery() 函数。在调用CRecordSet::Open() 函数时,如果已经将一个已经打开的CDatabase 对象指针传给CRecordSet 类对象的m_pDatabase 成员变量,则使用该数据库对象建立ODBC 连接;否则如果m_pDatabase 为空指针,就新建一个CDatabase 类对象并使其与缺省的数据源相连,然后进行CRecordSet 类对象的初始化。缺省数据源由GetDefaultConnect() 函数获得。你也可以提供你所需要的SQL 语句,并以它来调用CRecordSet::Open() 函数,例如:

Super_ESSet.Open(AFX_DATABASE_USE_DEFAULT,strSQL);
—- 如果没有指定参数,程序则使用缺省的SQL 语句,即对在GetDefaultSQL() 函数中指定的SQL 语句进行操作:

CString CSuper_ESSet::GetDefaultSQL()
{return _T("[BasicData],[MainSize]");}
—- 对于GetDefaultSQL() 函数返回的表名,对应的缺省操作是SELECT 语句,即:

SELECT * FROM BasicData,MainSize
—- 查询过程中也可以利用CRecordSet 的成员变量m_strFilter 和m_strSort 来执行条件查询和结果排序。m_strFilter 为过滤字符串,存放着SQL 语句中WHERE 后的条件串;m_strSort 为排序字符串,存放着SQL 语句中ORDER BY 后的字符串。如:

Super_ESSet.m_strFilter="TYPE=电动机";
Super_ESSet.m_strSort="VOLTAGE";
Super_ESSet.Requery();
对应的SQL语句为:
SELECT * FROM BasicData,MainSize
WHERE TYPE=电动机
ORDER BY VOLTAGE
—- 除了直接赋值给m_strFilter 以外,还可以使用参数化。利用参数化可以更直观,更方便地完成条件查询任务。使用参数化的步骤如下:

—- (1) .声明参变量:

CString p1;
float p2;
—- (2) .在构造函数中初始化参变量

p1=_T("");
p2=0.0f;
m_nParams=2;
—- (3) .将参变量与对应列绑定

pFX- >SetFieldType(CFieldExchange::param)
RFX_Text(pFX,_T("P1"),p1);
RFX_Single(pFX,_T("P2"),p2);
—- 完成以上步骤之后就可以利用参变量进行条件查询了:

m_pSet- >m_strFilter="TYPE=? AND VOLTAGE=?";
m_pSet- >p1=" 电动机";
m_pSet- >p2=60.0;
m_pSet- >Requery();
—- 参变量的值按绑定的顺序替换查询字串中的"?" 适配符。

—- 如果查询的结果是多条记录的话,可以用CRecordSet 类的函数Move(),MoveNext(),MovePrev(),MoveFirst() 和MoveLast() 来移动光标。

—- 2 .增加记录

—- 增加记录使用AddNew() 函数,要求数据库必须是以允许增加的方式打开:

m_pSet- >AddNew(); //在表的末尾增加新记录
m_pSet- >SetFieldNull(&(m_pSet- >m_type), FALSE);
m_pSet- >m_type=" 电动机";
… //输入新的字段值
m_pSet- > Update(); //将新记录存入数据库
m_pSet- >Requery(); //重建记录集
—- 3 .删除记录

—- 直接使用Delete() 函数,并且在调用Delete() 函数之后不需调用Update() 函数:

m_pSet- >Delete();
if (!m_pSet- >IsEOF())
m_pSet- >MoveNext();
else
m_pSet- >MoveLast();
—- 4 .修改记录

—- 修改记录使用Edit() 函数:

m_pSet- >Edit(); //修改当前记录
m_pSet- >m_type="发电机";
//修改当前记录字段值

m_pSet- >Update(); //将修改结果存入数据库
m_pSet- >Requery();
—- 5 .撤消操作

—- 如果用户选择了增加或者修改记录后希望放弃当前操作,可以在调用Update() 函数之前调用:

CRecordSet::Move(AFX_MOVE_REFRESH);
—- 来撤消增加或修改模式,并恢复在增加或修改模式之前的当前记录。其中的参数AFX_MOVE_REFRESH 的值为零。

—- 6 .数据库连接的复用

—- 在CRecordSet 类中定义了一个成员变量m_pDatabase:

CDatabase* m_pDatabase;
—- 它是指向对象数据库类的指针。如果在CRecordSet 类对象调用Open() 函数之前,将一个已经打开的CDatabase 类对象指针传给m_pDatabase,就能共享相同的CDatabase 类对象。如:

CDatabase m_db;
CRecordSet m_set1,m_set2;
m_db.Open(_T("Super_ES"));//建立ODBC连接
m_set1.m_pDatabase=&m_db;
//m_set1复用m_db对象
m_set2.m_pDatabse=&m_db;
// m_set2复用m_db对象
—- 7 .SQL 语句的直接执行

—- 虽然通过CRecordSet 类,我们可以完成大多数的查询操作,而且在CRecordSet::Open() 函数中也可以提供SQL 语句,但是有的时候我们还想进行一些其他操作,例如建立新表,删除表,建立新的字段等等,这时就需要使用到CDatabase 类的直接执行SQL 语句的机制。通过调用CDatabase::ExecuteSQL() 函数来完成SQL 语句的直接执行:

BOOL CDB::ExecuteSQLAndReportFailure(const CString& strSQL)
{
TRY
{
m_pdb- >ExecuteSQL(strSQL);//直接执行SQL语句
}
CATCH (CDBException,e)
{
CString strMsg;
strMsg.LoadString(IDS_EXECUTE_SQL_FAILED);
strMsg+=strSQL;
return FALSE;
}
END_CATCH
return TRUE;
}
—- 应当指出的是,由于不同DBMS 提供的数据操作语句不尽相同,直接执行SQL 语句可能会破坏软件的DBMS 无关性,因此在应用中应当慎用此类操作。

—- 8 .动态连接表

—- 表的动态连接可以利用在调用CRecordSet::Open() 函数时指定SQL 语句来实现。同一个记录集对象只能访问具有相同结构的表,否则查询结果将无法与变量相对应。

void CDB::ChangeTable()
{
if (m_pSet- >IsOpen()) m_pSet- >Close();
switch (m_id)
{
case 0:
m_pSet- >Open(AFX_DB_USE_DEFAULT_TYPE,
"SELECT * FROM SLOT0"); //连接表SLOT0
m_id=1;
break;
case 1:
m_pSet- >Open(AFX_DB_USE_DEFAULT_TYPE,
"SELECT * FROM SLOT1"); //连接表SLOT1
m_id=0;
break;
}
}
—- 9 .动态连接数据库

—- 由于与数据库的连接是通过CDatabase 类对象来实现的,所以我们可以通过赋与CRecordSet 类对象参数m_pDatabase 以连接不同数据库的CDatabase 对象指针,就可以动态连接数据库。

void CDB::ChangeConnect()
{
CDatabase* pdb=m_pSet- >m_pDatabase;
pdb- >Close();

switch (m_id)
{
case 0:
if (!pdb- >Open(_T("Super_ES")))
//连接数据源Super_ES
{
AfxMessageBox("数据源Super_ES打开失败,"
"请检查相应的ODBC连接", MB_OK|MB_ICONWARNING);
exit(0);
}
m_id=1;
break;
case 1:
if (!pdb- >Open(_T("Motor")))
//连接数据源Motor
{
AfxMessageBox("数据源Motor打开失败,"
"请检查相应的ODBC连接", MB_OK|MB_ICONWARNING);
exit(0);
}
m_id=0;
break;
}
}
—- 四.总结

—- Visual C++ 中的ODBC 类库可以帮助程序员完成绝大多数的数据库操作。利用ODBC 技术使得程序员从具体的DBMS 中解脱出来,从而极大的减少了软件开发的工作量,缩短开发周期,提高了效率和软件的可靠性。本文总结的笔者从事软件开发的一些经验心得希望对从事ODBC 开发的工作者有所帮助。

Read: 973

利用Active X控件开发串口通信软件

  摘要:本文介绍了在Microsoft Visual C++ 6.0环境下通过对Active X控件的编程来实现串口的通信的一般方法。

 一、 引言

当我们在Windows操作系统下开发串行通信程序时通常不得不面对许多复杂的API函数,因为在Windows操作系统下不能直接对设备端口进行操 作,也不能在系统级(Ring 3级别)使用任何DOS或BIOS中断,如要对端口进行编程则只能以文件的形式来对端口进行操作,这就使开发人员不得不面对非常烦琐的API函数编程。本 文对此提出了另外一种封装性很好的使用Microsoft Visual C++ 6.0自带的"Microsoft Communications Control,version 6.0"Active X控件的编程方法,通过对该控件的正确使用,我们可以比较轻松地编写出所需的串行通信程序。

下面,我们将结合一个实际的程序示例来对此方法进行说明。本程序的编程环境是Windows 98和Microsoft Visual C++ 6.0。在本程序示例中对为避免阻塞而对线程的使用以及在使用中遇到的一些问题也做了详细的介绍。

二、 程序的设计实现

在开始进行代码编程前,首先以在工程中插入组件或控件的方式将Active X控件"Microsoft Communications Control,version 6.0"加入到工程中来,此时将会在工程中添加一个关于此控件的新类。使用该控件的一些方法和属性时不能象使用类一样简单的声明一个实例对象,而要通 ClassWizard为该控件和一个成员变量建立起绑定关系,在此我们将该控件同变量m_Comm相绑定后就可以通过该控件提供的方法来对串口的各种通 讯参数进行设置了。为了编程方便起见,也可以在资源视图中直接对该控件的属性进行设置,如无特别要求,对下表所列属性进行设置就基本可以满足编程要求了。 现将常用的属性列表如下:

属性 设定值 属性说明
CommPort 1 串口号,一般从1到4
InBufferSize 30720 接收缓冲区大小,为保持程序的稳定,建议设得值足够大
InputMode 0-Text 接收数据的类型,0表示文本类型,1表示二进制类型
InputLen 0 从接收缓冲区读取的字节数,0表示全部读取
OutBufferSize 512 发送缓冲区大小
Settings 4800,n,8,1 串口的参数设置,依次为波特率、奇偶校验(n-无校验,e-偶校验,o-奇校验)、数据位数、停止位数
RThreshold 1 设定当接收几个字符时触发OnComm事件,0表示不产生事件,
1表示每接收一个字符就产生一个事件
SThreshold 0 设定在触发OnComm事件前,发送缓冲区内所允许的最少的字符数,
0表示发送数据时不产生事件,1表示当发送缓冲区空时产生OnComm事件

我们要求能在程序启动的同时就打开串口以便即时对从串口到达的数据进行接收、处理。一般来说可以将下面的打开端口的代码写在OnCreate()、OnInitialUpdate()、InitInstance ()等程序入口函数中:

……
if(!m_Comm.GetPortOpen()) //检测是否已经打开过端口
m_Comm.SetPortOpen(TRUE); //如没有打开则将端口打开
……

  接下来的工作就是对数据的发送与接收了,这也是本文所要介绍的重点所在。发送数据的代码原则上是可以写到一个成 员函数中被直接调用的,但这并不是一个良好的编程习惯:我们应当把比较耗时的操作,如文件拷贝、打印、端口传输等工作放到一个单独的线程当中,以避免其在 工作时会引起整个进程的阻塞,以提高整个系统对CPU的利用率。例如我们可以在视类中菜单或按钮的响应函数中用AfxBeginThread (WriteProc,this)函数来开启一个名为"WriteProc"的线程,由于在线程中还需要使用视类的函数和变量,为了不产生新的视类的实例 对象,我们通过该函数的第二个参数将指向当前的视类的指针this作为参数传递给线程。在线程中可以用如下两种方法之中的一种调用视类的成员函数:

((COLECommView*) pParam)->DoSendProc();

或是:

COLECommView* view=(COLECommView*) pParam;
View->DoSendProc();

其中从pParam传来的变量就是指向视类的指针。在线程中通过调用视类中的DoSendProc函数来完成对数据的发送,正是由于该函数是被全局的线 程所调用的,我们就不可以使用取编辑框上的数据时通常所用的UpdateData()函数了,取而带之的是API 函数GetDlgItemText(),取到输入的数据后通过控件的SetOutput() 方法就把数据从串口发出去了,其中发送数据必须经ColeVariant类将其转换为通用的VARIANT型变量。实现
主要代码如下:

……
char a[255];
HWND hwnd=GetSafeHwnd();
::GetDlgItemText(hwnd,IDC_EDIT1,a,255);
int i=0;
CString str;
while(a[i]!=”)
{
str.Format("%c",a[i]);
m_SendData+=str;
i++;
}
str.Format("%c",10);
m_SendData+=str;
m_Comm.SetOutput(COleVariant(m_SendData));
……

至于数据的接收,我们可以通过让MS Comm控件响应其OnComm事件来完成,通过ClassWizard加入其对事件的响应后,通过下面的事件映射,当有字符到达时便会通知 OnComm()函数去处理,从而实现数据的异步接收:

……
BEGIN_EVENTSINK_MAP(COLECommView, CFormView)
//{{AFX_EVENTSINK_MAP(COLECommView)
ON_EVENT(COLECommView, IDC_MSCOMM1, 1 /* OnComm */, OnComm, VTS_NONE)
//}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()
……
void COLECommView::OnComm()
{
VARIANT Input;
if(m_Comm.GetCommEvent()==2)//接收缓冲区内有字符
{
Input=m_Comm.GetInput();//读取缓冲区内的数据
CString msg=Input.bstrVal;
CString str;
str.Format("%c",10);
if(msg.Right(1)==str)
{
m_RecvData+=msg;
m_History.AddString(m_RecvData);
m_RecvData="";
}
else
m_RecvData+=msg;
}
}

当数据被接收到接收缓冲区后,对于字符可以从VARIANT型结构变量的bstrVal成员变量中获取,VARIANT数据结构相当复杂,并牵扯到 COM(Component Object Model,组件对象模型)中的一些概念,具体详情请参阅Microsoft Corpration发布的MSDN中的有关论述。

三、 测试与实验

编译运行程序之前有必要对机器的端口做一番检查,以确保端口的完好,可以用常见的DOS程序Comdebug来检查。在确认串口工作正常后,可用串口线 将两台机器的串口相连,同时在两台机子上运行该程序,如果没有条件也可只用一台微机,将其串口的2脚和3脚短接,使其处于自发自收状态。经过数据的传输实 验证明该程序是可靠、正确的。

小结:利用通讯控件可以很容易的编写出串行通信程序。但相对来说通讯控件在VC中的使用要比在VB、Delphi中复杂的多,要想对串口通讯开发出更多更灵活的使用方法还需要不断的实践中摸索。本程序在
Windows 98下,由Microsoft Visual C++ 6.0编译通过。

上一页  1

Read: 637

通过COM技术实现Windows外壳编程

  摘要:本文介绍了一种有别于通常的Windows外壳编程方法。采用COM技术,通过Windows提供的外壳接口实现对其的编程。

一、 引言

在Windows环境下,不论是使用Visual C++还是Delphi或是其他一些软件开发工具,尽管存在着差别,但有一点是相同的:都是运行于Windows操作系统之下的。在程序开发过程中也经常 要在自己的应用程序中加入一些Windows系统本身就有的功能,比如文件的拷贝、删除、查找以及运行程序等等。而这些功能在Windows操作系统下都 是具备的,显然如果能直接从系统中调用这些功能将不仅仅减少程序的大小和开发人员的工作量,而且由于是直接通过操作系统来完成这些功能,将会大大减小这部 分程序出现异常错误的概率。Windows系统虽说也存在不少错误,但常用功能的错误还是比较少的,而且通过补丁程序可以更低限度减少系统错误,因此程序 员可以将调试检错的注意力放在应用程序的其他地方,对于调用系统功能这部分代码则可以不必投入太大的精力去调试,因为这部分调试的工作在操作系统发布的时 候就已经由微软做好了。

二、 Windows外壳编程

前面所说的直接使用Windows操作系统部分功能的编程方法就是针对Windows操作系统外壳的编程,可以通过对操作系统提供的几个编程接口对操作 系统的部分功能进行调用,甚至可以按照自己的意图在应用程序中对部分功能进行修改、扩展。但这方面的资料介绍不是特别多,讲的也大都语焉不详,而且用通常 的编程方法去进行外壳编程是非常麻烦的,动辄就要对相关的结构对象进行设置,而这样的结构里的数据成员少则十来个多则几十个,因此配置起来非常烦琐,下面 就以一个比较简单的外壳操作–拷贝文件进行举例说明:

……
SHFILEOPSTRUCT FileOp; //外壳的文件操作结构
FileOp.hwnd=m_hWnd; //设置句柄
//设置操作方式,拷贝用FO_COPY,删除用 FO_DELETE
FileOp.wFunc=FO_COPY;
FileOp.pFrom=m_source; //源文件路径
FileOp.pTo=m_detect; //目标文件路径
FileOp.fFlags=FOF_ALLOWUNDO; //允许恢复
FileOp.hNameMappings=NULL;
FileOp.lpszProgressTitle=strTitle; //设置标题
SHFileOperation(&FileOp); //执行外壳拷贝
if(FileOp.fAnyOperationsAborted) //监测有无中止
TRACE("An Operation was aborted!!!n");
……

上述代码实现起来虽然效果还是不错的,但然实现起来却是比较麻烦的,这仅仅是一个比较简单的外壳操作,对于一些比较复杂的外壳操作比如系统托盘、任务条 等等的编程,更是尤为严重,而且象此类编程,MFC里并没有提供封装好的程序类库,提供的只有系统的WinAPI 应用程序接口,因此在程序开发过程中往往会有一种在进行SDK编程的感觉。

  三、 COM技术在Windows外壳编程中的应用

COM (Component Object Model,组件对象模型)是Microsoft创建的一种二进制和网络标准,也是Microsoft大力推广并已取得广泛认可的一种组件标准。在COM 标准中,COM对象被很好的封装起来,客户无法访问对象的实现细节,提供给用户的唯一的访问途径是通过COM接口来访问。对于COM接口有两方面的含义: 首先它是一组可供调用的函数,由此客户可以让该对象做某些事情;其次,也是更为重要的,接口是组件及其客户程序之间的协议。也就是说接口不但定义了可用什 么函数,也定义了当调用这些函数时对象要做什么。Windows操作系统本身作为一个大的COM组件对象,也提供了一些必要的COM接口给客户程序,因此 我们可以通过这些COM接口来直接对Windows外壳进行编程。

在程序进行正式编写设计之前有一点是肯定的:程序里需要用到COM接口,要对COM对象进行操作。因此首先要加入初始化COM和终止COM的代码。一般是在应用程序类的InitInstance()函数的开始处和返回前添加初始化COM和终止COM代码的:

……
CoInitialize(NULL); //初始化COM
……
CoUninitialize(); //终止COM代码
……

以上两个函数在MFC程序和非MFC程序中都可以很好的使用。另外,如果程序框架是以MFC为基础的,那么只需简单的调用AfxOleInit()函数 就可以达到同样的目的。而且不必显式调用终止COM的代码。在COM标准中,访问COM对象的唯一途径是COM接口,因此在编写操纵Windows 系统外壳程序首先要得到其提供的COM接口。所用的COM接口是IShellDispatch,它是从IDispatch接口派生来的,在VC安装目录的 VC98IncludeExdisp.h头文件中有定义,下面节选了一些将要用到的接口定义:

……
EXTERN_C const IID IID_IShellDispatch;
#if defined(__cplusplus) && !defined(CINTERFACE)
interface DECLSPEC_UUID("D8F015C0-C278-11CE-A49E-444553540000")
IShellDispatch : public IDispatch
{
public:
……
virtual HRESULT STDMETHODCALLTYPE MinimizeAll( void) = 0;
virtual HRESULT STDMETHODCALLTYPE UndoMinimizeALL( void) = 0;
virtual HRESULT STDMETHODCALLTYPE FileRun( void) = 0;
virtual HRESULT STDMETHODCALLTYPE CascadeWindows( void) = 0;
virtual HRESULT STDMETHODCALLTYPE TileVertically( void) = 0;
virtual HRESULT STDMETHODCALLTYPE TileHorizontally( void) = 0;
virtual HRESULT STDMETHODCALLTYPE ShutdownWindows( void) = 0;
virtual HRESULT STDMETHODCALLTYPE Suspend( void) = 0;
virtual HRESULT STDMETHODCALLTYPE SetTime( void) = 0;
virtual HRESULT STDMETHODCALLTYPE TrayProperties( void) = 0;
virtual HRESULT STDMETHODCALLTYPE Help( void) = 0;
virtual HRESULT STDMETHODCALLTYPE FindFiles( void) = 0;
virtual HRESULT STDMETHODCALLTYPE FindComputer( void) = 0;
};
……

该接口在CoCreateInstance()函数创建COM对象时将会得到指向其的指针,通过这个函数客户程序可以避免显式同类厂打交道,其实该函数 内部也调用了CoGetClassObject()函数来获取COM对象的类厂,只不过它把通过类厂创建对象的过程封装起来了,只需用户指定对象类的 CLSID和待输出的接口指针及接口ID,显然这样直接创建COM对象是非常便捷的,在获取到COM对象指针之后就可以通过这个指针去访问调用COM对象 里的方法来实现Windows 外壳的种种功能调用了,下面是实现该功能的部分关键代码:

……
HRESULT sc;//返回结果
IShellDispatch *pShellDisp = NULL; //初始化接口指针
//直接创建COM对象
sc = CoCreateInstance( CLSID_Shell,//指定待创建的COM对象标识符
NULL, //指定被聚合时的外部对象的接口指针
CLSCTX_SERVER, //指定组件类别,可以指定进程内组件进程外组件或者进程内控制对象。
IID_IDispatch, //指定接口ID,需要注意的是这里指的是待
//创建的COM对象的接口ID,而非类厂对象的接口标识符
(LPVOID *) &pShellDisp );//存放函数返回的对象的接口指针

/* 在上述代码中,CoCreateInstance首先调用CoGetClassObject函数创建类厂对象,然后用得到的类厂对象的接口指针创建真正的COM对象,最后把类厂对象释放并返回,这样就很好的把类厂屏蔽起来,使用户用起来更为简单。*/

if( FAILED(sc) )//必须用FAILED 或SUCCECCED来判断COM对象是否创建成功
return;
pShellDisp->FindFiles(); //调用COM对象里的方法
pShellDisp->Release(); //释放申请到的接口指针
……

在这里通过pShellDisp接口指针调用了COM对象的FindFiles()方法去进行查找文件的系统外壳操作。同样,可以根据实际需要灵活调用响应的方法来执行相应的外壳操作,主要有以下几个方法:

MinimizeAll 所有窗口最小化

UndoMinimizeALL 恢复窗口最小化

FileRun 开始菜单的"运行…"

CascadeWindows 层叠窗口

TileVertically 垂直平铺

TileHorizontally 水平平铺

ShutdownWindows 关闭Windows

Suspend 挂起计算机

SetTime 设定时间

TrayProperties 任务栏属性

Help Windows帮助

FindFiles 查找文件

FindComputer 查找计算机

……

这些接口均在VC安装目录的VC98IncludeExdisp.h头文件中有定义,可以通过对该文件的查看来编写响应的外壳操作代码。

小结:本 文介绍了一种利用COM技术实现Windows系统外壳程序的简便实用的方法,对Windows系统外壳的程序设计和COM程序设计的方法和思想做了阐 述。在掌握了本文编程的中心思想前提下,不仅可以对Windows系统外壳进行程序设计,而且对于其他一些提供COM接口的应用程序进行编程,比如可以在 自己的应用程序中用类似的方法加入对Office办公套件的支持等等。因此重点不应放在具体的程序代码中,而是在于程序的设计思想与方法。本文所述程序在 Windows 98下,由Microsoft Visual C++ 6.0编译通过。

Read: 681