Visual C++数据库编程快速入门

   odbc(open database connectivity,开放数据库互连)是微软公司开放服务结构(wosa,windows open services architecture)中有关数据库的一个组成部分,它建立了一组规范,并提供了一组对数据库访问的标准api(应用程序编程接口)。这些api利用sql来完成其大部分任务。odbc本身也提供了对sql语言的支持,用户可以直接将sql语句送给odbc。
 
  一个基于odbc的应用程序对数据库的操作不依赖任何dbms,不直接与dbms打交道,所有的数据库操作由对应的dbms的odbc驱动程序完成。也就是说,不论是foxpro、access还是oracle数据库,均可用odbc api进行访问。由此可见,odbc的最大优点是能以统一的方式处理所有的数据库。
 
  一个完整的odbc由下列几个部件组成:
 
  应用程序(application)。
 
  odbc管理器(administrator)。该程序位于windows 95控制面板(control panel)的32位odbc内,其主要任务是管理安装的odbc驱动程序和管理数据源。
 
  驱动程序管理器(driver manager)。驱动程序管理器包含在odbc32.dll中,对用户是透明的。其任务是管理odbc驱动程序,是odbc中最重要的部件。
 
  odbc api。
 
  odbc 驱动程序。是一些dll,提供了odbc和数据库之间的接口。
 
  数据源。数据源包含了数据库位置和数据库类型等信息,实际上是一种数据连接的抽象。
 
  各部件之间的关系如图下图所示:
 
  应用程序要访问一个数据库,首先必须用odbc管理器注册一个数据源,管理器根据数据源提供的数据库位置、数据库类型及odbc驱动程序等信息,建立起odbc与具体数据库的联系。这样,只要应用程序将数据源名提供给odbc,odbc就能建立起与相应数据库的连接。
 
  在odbc中,odbc api不能直接访问数据库,必须通过驱动程序管理器与数据库交换信息。驱动程序管理器负责将应用程序对odbc api的调用传递给正确的驱动程序,而驱动程序在执行完相应的操作后,将结果通过驱动程序管理器返回给应用程序。
 
  在访问odbc数据源时需要odbc驱动程序的支持。用visual c++ 5.0安装程序可以安装sql server、 access、 paradox、 dbase、 foxpro、 excel、 oracle 和microsoft text等驱动程序.在缺省情况下,vc5.0只会安装sql server、 access、 foxpro和dbase的驱动程序.如果用户需要安装别的驱动程序,则需要重新运行vc 5.0的安装程序并选择所需的驱动程序。    
 
 
    1 ado是微软整个com战略体系中的一个组成部分
 
   活动数据对象(ado)是一组由微软提供的com组件。 ado建立在微软所提倡的com体系结构之上,它的所有接口都是自动化接口,因此在c++、visualbasic、delphi等支持com的开发语言中通过接口都可以访问到ado。ado通过使用ole db这一新技术实现了以相同方式可以访问关系数据库、文本文件、非关系数据库、索引服务器和活跃目录服务等的数据,扩大了应用程序中可使用的数据源范围,从而成为微软整个com战略体系中访问数据源组件的首选,是odbc的替代产品。 
 
   2 ado对象模型组成
 
  与微软的其它数据访问模型dao和rdo相比,ado对象模型非常精炼,仅由三个主要对象connection、command、recordset和几个辅助对象组成,其相互关系如图所示。connection对象提供ole db数据源和对话对象之间的关联,它通过用户名称和口令来处理用户身份的鉴别,并提供事务处理的支持;它还提供执行方法,从而简化数据源的连接和数据检索的进程。command对象封装了数据源可以解释的命令,该命令可以是sql命令、存储过程或底层数据源可以理解的任何内容。record set用于表示从数据源中返回的表格数据,它封装了记录集合的导航、记录更新、记录删除和新记录的添加等方法,还提供了批量更新记录的能力。其它辅助对象则分别提供封装ado错误、封装命令参数和封装记录集合的列。
 
   3 ado的特点分析
 
  (1)由于封装了许多底层工作,使用ado与使用odbc几乎是一样方便。
 
  (2) ado不仅具有odbc的主要功能,而且ado适用的数据源的范围要大的多。
 
  (3)在定义ado记录集变量和数据库表字段绑定类时,要求记录集的字段变量、状态变量与数据库表字段的个数、顺序必须相同。这一点比在fmc中使用odbc要复杂一些。但在数据库字段与ado记录集字段变量绑定的宏中,ado 提供的数据类型要远多于fmc中的rfx(如日期时间类型,在odbc中只能转换为cstring类型)。
 
  (4)ado允许同一connection实例下有多个record set实例。
 
  (5)ado允许进行批更新(使用的update batch方法),这样将大大减轻网络负担,提高数据库处理效率。
 4 ado在visual c++中的使用
 
 close();
 
  (12) 调用counitialize释放com资源 ::couninitialize();
 
   5 结论
 
  作为odbc的替代产品,ado确实有其过人之处。由于ado数据源几乎覆盖了目前常见的数据源类型,对于odbc所不支持的数据源,ado无疑是唯一的选择。而ado的批更新功能,更是网络环境下大数据量更新应用的重要因素。由于ado缺乏大量的第三方厂商的支持,使得ado目前远不如odbc普及,但其面向对象的特性将使ado具有比较广阔的发展前景。
 
   有很多种使用数据库的方法,对大多数数据库来说,选择c++这种产品也许并不适宜。我们知道,像dbase iv,foxpro,oracle和access这样的产品是完全以数据库管理为中心的。事实上,这些产品非常善于创建数据库管理器,以至于它们确实并不善于做太多其它的工作。即使要用更通用化而非更专用化的数据库产品来执行一些类型的工作,在使程序设计更容易这一方面,像visualbasic和delphi这样的rad环境也要比visual c++强很多。
 
   你是不是对我的说法感到很奇怪?下面我就要谈一谈,在谈到使用数据库管理系统(dbms)这个话题时,用visual c++实际上可以做些什么。虽然上述其它语言使得编写成熟的包括用户界面和高速搜索能力的dbms就像孩子做游戏一样容易,但是,它们缺少visual c++可以提供的某些重要东西。你不能为使用access的数据库轻松地编写出实用程序。正像实用程序的定义所说的,实用程序应该很小并且具备可移植性——access应用程序却不是这样。即使用access这样的产品创建的程序可以很小并且可以移植,你仍有其它方面的需求:底层的功能。
 
  注:编写数据库实用程序及驱动程序时,可以选择visual c++语言。
 
  想像一下,使用像visual basic这样的语言来与实时数据采集设备打交道的情况。在进行底层访问时,rad的保护环境常常使程序员不能进行有效的处理。当然,数据采集设备几乎不依赖于简明的连接。你打算如何把visual basic和外部的数据源连接起来呢?数据源甚至可能不了解windows,dos或类似的成熟的操作系统。
 
  只要使用得当,很容易看到visual c++是一种不可或缺的数据库管理工具。针对大规模的应用程序,即使你仍想依赖于visual basic这样的rad语言,也请考虑一下visual c++,它创建的程序规模小、提供底层访问并能提供实时访问。事实上,你可能还没有想到,visual c++数据库应用程序的市场是很有潜力的。随着人们在旅途中越来越多地使用膝上型和掌上型电脑,这两类电脑上的数据库应用程序也变得越来越普通。你也许能够适应今天的膝上型电脑上的access应用程序,但谈到硬盘大小或内存需求时,公司里较老的膝上型电脑可能就达不到要求。运行windows ce的掌上型电脑在运行这个access应用程序时,肯定会发生故障。在这一数据库市场的新领域,visual c++提供了无价无限的工具。
 
  web链接 谈到使用visual c++和数据库,其实你并不孤单。从一开始就有数据库专用新闻组提供有关数据库创建技巧的帮助,比如microsoft.public.access。不过,这些新闻组提供的是通用信息,对实际编写应用程序并非全都那么有用。专门针对visual c++问题的新闻组是microsoft.public.vc.database和microsoft.public.vc.mfcdatabase。如果你决定用odbc访问数据库,可能还要查看一下microsoft.public.odbc.sdk新闻组,它讨论的不仅仅是sdk。对最新技术感兴趣的程序员可以查阅microsoft.public.ado新闻组,或者microsoft.public.oledb(对象链接和嵌入数据库)新闻组,前者讨论 ado,后者讨论ado的基础技术。在microsoft.public.ado.rds有一个ado子组,它讨论远程数据访问。
 
  既然所有的疑惑都消除了,大多数人的信心也就增强了,下面我们就介绍两种使c++访问数据库中的数据的主要方法:odbc(开放数据库互连)和ado(activex数据对象)。在本章中,将介绍这两种类型的访问方法,但我想你会发现,ado方法是针对新的程序设计情形而采用的。它克服了早期技术的诸多限制,依赖于microsoft新的底层访问方法ole-db(对象链接和嵌入数据库)。在本书的后面我们会看到,用ado和visual c++提供的各种向导来汇集数据库工程,其速度有多快。
 
  注 odbc通常用来访问不具备ole-db特性的非microsoft数据库中的数据;16位的odbc驱动程序工作起来可能非常缓慢。
 
  odbc素以最慢的数据访问方法而著称,但是很可惜,当ado或dao都不支持某个数据库管理器而odbc支持这个数据库管理器时,在这种特定的情形下,你仍然需要使用odbc。在大多数情况下,这意味着要从数据库厂商那里获得所需的驱动程序,虽然visual c++确实附带了一些产品的驱动程序(如果你正在使用数据库管理器的某些神秘功能,那么就需要建立自己的接口棗这并不是一件十分困难的事)。本质上讲,你总是要使用odbc来访问microsoft产品之外的其它dbms产品所创建的数据库,这些数据库并不具备ole-db功能。odbc还要求做一些额外的工作棗为ado调整visual c++中的大部分向导。
 
   高级技巧 
 
  除了使用ado和odbc外,你还可以使用像dao(数据访问对象)这样的早期技术,该技术包含在像access这样的microsoft产品中。dao依赖于用microsoft access自动获得的microsoft jet数据库引擎。dao还是较早版的visual basic所使用的引擎(最新版的visual basic和visual c++依赖于相同的ado/ole-db组合),所以如果需要支持较早的visual basic应用程序,那么dao仍是一个不错的选择。
 
  尽管microsoft文件声明,可以用dao访问非microsoft产品建立的数据库,但你仍会发现,在这种情况下,使用ado和odbc要好得多。这样的话,不但兼容性问题会少一些,速度也将有所提高,因为数据请求经过的接口层减少了。有一条经验要记住,dao是设计用来处理mdb文件的。
 
  ado的一个问题是,它不支持远程通信。这是microsoft提出rdo(远程数据对象)的原因之一。这种特别技术在visual basic应用程序中的使用,要比在visual c++中的使用多得多,所以我猜想,你们中有很多人都在使用它。但是,记住rdo仍是一种生命力很强的技术,这一点很重要。ado确实具有替代rdo的远程数据服务(rds)特征。换言之,ado在一个软件包中提供了dao和rdo两种功能性。 
 
    一般情况下appwizard会在数据库应用程序中自动产生crecordset的派生类,并将派生类和某个数据源中的表联系起来也可以和视图上的子窗口联系起来。但是有时这样做会影响到程序的灵活性,这时候我们可以单独使用crecordset类。利用crecordset类我们可以执行sql语句,并可以读出结果集中数据。 
 
首先我们需要包含头文件afxdb.h,可以将#include 添加到stdafx.h文件中。此外在使用crecordset时必须有一个又一个cdatabase对象,该对象的作用是管理数据源连接。然后可以产生一个crecordset对象,利用bool crecordset::open( uint nopentype = afx_db_use_default_type, lpctstr lpszsql = null, dword dwoptions = none )可以执行sql语句。
 
但执行成功后,可以调用以下的函数滚动光标,读取数据。
 
   movefirst 移动光标到第一条记录处     movenext 移动光标到后一条记录处     moveprev 移动光标到前一条记录处     movelast 移动光标到最后一条记录处     isbof 检测光标是否在第一条记录上     iseof 检测光标是否在最后一条记录上     getfieldvalue 得到结果中数据   
下面是具体代码: /*
 
   假设cdatabase m_dbconn为成员变量
假设有一个表有如下sql语句产生:create table table1(loc_id not null)
*/
void cyourclass::connecttodb()
{//连接数据库
bool fok = m_dbconn.open("test");
trace("connect fok=%d/n",m_dbconn);
}
 
void cyourclass::select()
{//执行select语句
crecordset rec(&m_dbconn);
bool fok = rec.open(crecordset::forwardonly,"select loc_id from table1 order by loc_id");
trace("select fok = %d/n",fok);
trace("返回的列数为:%d/n",rec.getrowsetsize());
cstring szresult;
while(!rec.iseof())
{
rec.getfieldvalue((int)0,szresult);
rec.movenext();
trace("fetch : %s/n",szresult);
}
}
 
此外crecordset::getfieldvalue有很多种原型,你可以通过指定列位置或是字段名来获取数据:
 
   void getfieldvalue( lpctstr lpszname , cdbvariant& varvalue , short nfieldtype = default_field_type );
 
void getfieldvalue( short nindex , cdbvariant& varvalue , short nfieldtype = default_field_type );
 
void getfieldvalue( lpctstr lpszname , cstring& strvalue );
 
void getfieldvalue( short nindex , cstring& strvalue );   
 
如果使用cdbvariant类型变量来获取结果,你可以得到任何类型的结果。在cdbvariant::m_dwtype成员变量中记录了该变量所包含的数据类型,根据该变量的值你可以确定数据类型并引用cdbvariant对象中的相应成员变量。
 
        
 要建立与数据源的连接,首先应构造一个cdatabase对象,然后再调用cdatabase的open成员函数.open函数负责建立连接,其声明为
 
virtual bool open( lpctstr lpszdsn, bool bexclusive = false, bool breadonly = false, lpctstr lpszconnect = “odbc;”, bool busecursorlib = true ); throw( cdbexception, cmemoryexception );
 
  参数lpszdsn指定了数据源名(构造数据源的方法将在后面介绍),在lpszconnect参数中也可包括数据源名,此时lpszdsn必需为null,若在函数中未提供数据源名且使lpszdsn为null,则会显示一个数据源对话框,用户可以在该对话框中选择一个数据源.参数bexclusive说明是否独占数据源,由于目前版本的类库还不支持独占方式,故该参数的值应该是false,这说明数据源是被共享的.参数breadonly若为true则对数据源的连接是只读的.参数lpszconnect指定了一个连接字符串,连接字符串中可以包括数据源名、用户帐号(id)和口令等信息,字符串中的"odbc"表示要连接到一个odbc数据源上.参数busecursorlib若为true,则会装载光标库,否则不装载,快照需要光标库,动态集不需要光标库. 若连接成功,函数返回true,若返回false,则说明用户在数据源对话框中按了cancel按钮。若函数内部出现错误,则框架会产生一个异常。
 
  下面是一些调用open函数的例子。
 
cdatabase m_db; //在文档类中嵌入一个cdatabase对象
 
//连接到一个名为"student registration"的数据源
 
m_db.open("student registration");
 
//在连接数据源的同时指定了用户帐号和口令
 
m_db.open(null,false,false,"odbc;dsn=student registration;uid=zyf wd=1234");
 
m_db.open(null); //将弹出一个数据源对话框
 
  要从一个数据源中脱离,可调用函数close。在脱离后,可以再次调用open函数来建立一个新的连接.调用isopen可判断当前是否有一个连接,调用getconnect可返回当前的连接字符串。函数的声明为
 
virtual void close( );
 
bool isopen( ) const; //返回true则表明当前有一个连接
 
const cstring& getconnect( ) const;
 
  cdatabase的析构函数会调用close,所以只要删除了cdatabase对象就可以与数据源脱离。
        
 crecordview(记录视图)是cformview的派生类,它提供了一个表单视图(参见6.4.1)来显示当前记录.一个典型的记录视图如图10.3所示,用户可以通过表单视图显示当前记录.通过记录视图,可以修改、添加和删除数据.用户一般需要创建一个crecordview的派生类并在其对应的对话框模板中加入控件.
 
  记录视图使用ddx数据交换机制在表单中的控件和记录集之间交换数据。在前面介绍的ddx都是在控件和控件父窗口的数据成员之间交换数据,而记录视图则是在控件和一个外部对象(crecordset的派生类对象)之间交换数据.清单10.3显示了一个crecordview的派生类的dodataexchange函数,读者可以看出,该函数是与m_pset指针指向的记录集对象的域数据成员交换数据的,而且,交换数据的代码是classwizard自动加入的.在后面的例子中,将向读者介绍用classwizard连接记录视图与记录集对象的方法.
 
清单10.3 用来与记录集对象的域数据成员交换数据的dodataexchange函数
 
void csectionform: odataexchange(cdataexchange* pdx)
 
{
 
crecordview: odataexchange(pdx);
 
//{{afx_data_map(csectionform)
 
ddx_fieldtext(pdx, idc_course, m_pset->m_courseid, m_pset);
 
ddx_fieldtext(pdx, idc_section, m_pset->m_sectionno, m_pset);
 
ddx_fieldtext(pdx, idc_instructor, m_pset->m_instructorid, m_pset);
 
ddx_fieldtext(pdx, idc_room, m_pset->m_roomno, m_pset);
 
ddx_fieldtext(pdx, idc_schedule, m_pset->m_schedule, m_pset);
 
ddx_fieldtext(pdx, idc_capacity, m_pset->m_capacity, m_pset);
 
//}}afx_data_map
 
}
 
  作为总结,图10.4显示了mfc的odbc应用程序中的ddx和rfx数据交换.
 
 
img]http://www.czvc.com/tech/visual%20c++jc/t10_4.gif[/img]
图10.4 ddx和rfx数据交换机制
 
crecordview本身提供了对下面四个命令的支持:
 
id_record_first //滚动到记录集的第一个记录
 
id_record_last //滚动到记录集的最后一个记录
 
id_record_next //前进一个记录
 
id_record_prev //后退一个记录
 
 
 
crecordview提供了onmove成员函数处理这四个命令消息,onmove函数对用户是透明的,清单10.4列出了onmove的源代码.
 
 
 
清单10.4 onmove函数
 
bool crecordview::onmove(uint nidmovecommand)
 
{
 
crecordset* pset = ongetrecordset();
 
if (pset->canupdate())
 
{
 
pset->edit();
 
if (!updatedata())
 
return true;
 
 
 
pset->update();
 
}
 
 
 
switch (nidmovecommand)
 
{
 
case id_record_prev:
 
pset->moveprev();
 
if (!pset->isbof())
 
break;
 
 
 
case id_record_first:
 
pset->movefirst();
 
break;
 
 
 
case id_record_next:
 
pset->movenext();
 
if (!pset->iseof())
 
break;
 
if (!pset->canscroll())
 
{
 
// clear out screen since we~re sitting on eof
 
pset->setfieldnull(null);
 
break;
 
}
 
 
 
case id_record_last:
 
pset->movelast();
 
break;
 
 
 
default:
 
// unexpected case value
 
assert(false);
 
}
 
 
 
// show results of move operation
 
updatedata(false);
 
return true;
 
}
 
  在函数的开头先调用crecordset::edit进入编辑模式,接着调用updatedata将控件中的数据更新到记录集对象的域数据成员中,然后调用crecordset::update将域数据成员的值写入数据源.这说明onmove在滚动记录的同时会完成对原来记录的修改.
 
  在函数的中间有一个分支语句用来处理四个不同的命令,在这个分支语句中调用了crecordset的各种用于滚动记录的成员函数,这些函数在滚动到一个新的记录时会把该记录的内容设置到域数据成员中.在函数的末尾调用updatedata(false)把新的当前记录的内容设置到表单的控件中。
 
  由此可见,onmove一来一回完成了两次表单控件和数据源的数据交换过程.通过分析该函数,读者可以学会在浏览记录时如何控制ddx和dfx数据交换. 
 
        
   对于在windows上编写数据库程序的程序员来说,activex data objects (ado) 是最常使用的技术了,通过ado可以简单的实现数据库的连接以及数据访问。但是在vc++中使用ado时,却因为是使用com的方式来调用,常常出现一些系统无法编译通过,或使用中程序非法出错的问题,在这里想大概介绍一下vc++中调用ado的常用方法。
 
 
 
   1、 用import导入ado 的 com 文件msado15.dll
 
  例如:
 
    #import "c:/program files/common files/system/ado/msado15.dll"/no_namespace   
  2、com 使用时初始化
 
   hresult cominit()
{
 hresult hr = s_ok; // 默认返回值
 if failed(coinitialize(null)) // com 初始化调用
 {
  couninitialize();
  hr = e_unexpected;
 }
 return hr;
}   
  3、建立数据库连接
 
   open(pconnstring,
   puserid,
   puserpassword,
   connectoption);
   return hr;
  }
  catch(_com_error &pcomerror)
  {
   …… // 错误处理
   return e_unexpected;
  }
 }     4.执行一个sql 查询,得到数据集(recordset)
 
    execute(m_ strsql,null, adcmdtext);
  return ptrrs;
 }
 catch(_com_error &a_pcomerror)
 {
  ….// 错误处理
  return null;
 }
}   
  5.通过数据集(recordset)得到列的名称
 
   count;
   // 循环取得列的属性和名称
   for(int iindex = 0 ; iindex type;
   }
  }
  return s_ok;
 }
 catch(_com_error &a_pcomerror)
 {
  …. // 错误处理
  return e_unexpected;
 }
 catch(...)
 {
  …. // 错误处理
  return e_unexpected;
 }
}   
  6.通过数据集(recordset)得到当前行记录
 
   hresult getonerecord(
 _recordsetptr ptrrs,
 const long lnoofcolumns,
 _variant_t varvalue[])
 {
  try
  {
   // 参数变量
   _variant_t l_vaindex;
   l_vaindex.vt = vt_i2;
   // 循环取得列的值
   for(long lindex = 0; lindex value;
   }
   return s_ok;
  }
  catch(_com_error &a_pcomerror)
  {
   …. // 错误处理
   return e_unexpected;
  }
  catch(...)
  {
   …. // 错误处理
   return e_unexpected;
  }
 }   
  7.出错情况下错误信息的取得
 
   count;
   for(long i = 0; i description); // 错误描述
   }
  }
  // ado 处理出错的情况下, 在connection对象里面都有记录,可以通过访问
  // connection 对象取得错误编号和错误信息。     
 
        
   odbc(open database conectivity)即开放式数据库互联,作为windows开放性结构的一个重要部分已经为很多的windows程序员所熟悉,odbc的工作依赖于数据库制造商提供的驱动程序,使用odbc api的时候,windows的odbc管理程序,把数据库访问的请求传递给正确的驱动程序,驱动程序再使用sql语句指示dbms完成数据库访问工作,因此,odbc的存在为我们开发应用数据库程序提供了非常强大的能力和灵活性。
 
   为了使odbc能与数据库一起工作,必须把数据库注册到odbc驱动程序管理器,这项工作可以通过定义一个dsn或数据源名字来完成。通常,我们只能手动打开系统控制面板,运行其中的odbc数据源管理器,手工配置数据源,但是这项工作对用户而言过于复杂,我们必须考虑用程序替用户完成这些配置工作。
 
  因此许多程序员在发布自己编写的数据库软件时候都希望能有一个优秀的安装程序能够自动设置好odbc数据源,虽然现在installshield等一些优秀的安装制作软件可以帮助我们实现此类功能,但毕竟缺少灵活,程序员不能完全控制它,事实上,我们完全可以自己编写一些程序实现此类功能,实现的方法有几种,一种办法是用程序修改windows注册表,程序员可以用windows api函数增改hkey_local_machinesoftwareodbc下的odbc.ini中的键值,这种方法比较烦琐。我现在推荐一种在程序中使用odbc api的方法,程序员可以在任何时候都可以用visual c++编写的程序调用这些api函数来设置odbc数据源。
 
  下面我用mfc写一个程序来演示如何实现这个功能:
 
  首先,打开visual c++,在file菜单上选new,然后选定mfc appwizard(exe)类的项目,project name我们定为try,按下ok键,下一step 1屏幕中选dialog based,由于不必用到后面的选项,此时即可按下finish键,结果系统将生成一个新的项目。完成上述工作后,在左侧workspace窗口中,选择resourceview,打开try resources中的dialog资源,选择并打开idd_try_dialog对话窗口,在controls菜单窗口中点选按键图标,回到idd_try_dialog对话窗口并点击此窗口,将生成一个名叫button1的按键,选中此按键再按鼠标右键,在弹出式菜单上选properties选项,在出现的对话框中把caption项的button1值改为setup odbc,关闭此对话框,再选中此按键按鼠标右键,选择classwizard,在出现的对话窗口中,object ids选idc_button1,messages中双击bn_clicked,此时弹出add member function对话窗,member function name是onbutton1,按ok键。在member functions选项中双击onbutton1 on_idc_buttion1:bn_clicked,在出现的void ctrydlg::onbutton1()函数中用以下odbc api函数语句替换 //todo: add your control notification handler code here这条注释语句:
 
      sqlconfigdatasource(null,odbc_add_sys_dsn,"microsoft access driver (*.mdb)0","
dsn=trydb0dbq=d:database try.mdb0defaultdir=d:database00");   
  您可以根据您不同的设置需要修改上面的语句,sqlconfigdatasource一般有以下几个许可的参数:odbc_add_dsn: 加入一个新的用户数据源,odbc_config_dsn: 修改一个存在的用户数据源,odbc_remove_dsn: 删除一个存在的用户数据源,odbc_add_sys_dsn: 增加一个新的系统数据源,odbc_config_sys_dsn: 修改一个存在的系统数据源,odbc_remove_sys_dsn: 删除一个存在的系统数据源,odbc_remove_default_dsn: 删除省缺的数据源说明部分。需要注意的是,当我们使用sqlconfigdatasource odbc api函数时必须声明包含系统的odbcinst.h头文件,所以我们再选择workspace窗口中fileview打开header files中try.h,在其中加入#include "odbcinst.h",如果不加入这个头文件,系统编译时就会显示undeclared identifier错误,在完成上述步骤后,假如我们立即编译并link这个项目,会发现出现下面的错误:
 
   trydlg.obj : error lnk2001: unresolved
external symbol _sqlconfigdatasource@16
debug/try.exe : fatal error lnk1120:
1 unresolved externals   
  有些人可能因为找不出错误而放弃了,其实这是因为当我们使用sqlconfigdatasource 这个api函数时候必须用到odbccp32.dll,它是microsoft提供的32位odbc安装和管理的dll,如果是16位必须用到odbcinst.dll,odbccp32.dll有一个import library,所以解决的办法就是把这个odbccp32.lib加到我们的项目中,我们可以打开project系统菜单项,选add to project子菜单,在其中选files项,打开vc安装目录下的vclib目录,文件类型选library files(.lib), 选择其中odbccp32.lib后按ok键,然后重新编译即可通过,运行这个程序,将弹出对话窗,按下setup odbc按键,之后,您就可以通过控制面板的odbc数据源管理器或注册表查看运行结果,您会发现,您的数据库已经成功的注册了。
 
  以上代码均在win98,vc6.0上编译通过,您可以灵活应用这些办法,让您编写的软件更易于安装维护和使用。 
 
        
 
 
 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;如果想访问数据库的信息而不想写回所做的改变,那么选定database 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建立,目的是在视表单和记录集之间建立联系,使得记录集中的查询结果能够很容易地在视表单上显示出来。
 
  要使程序与数据源建立联系,需用cdatebase::openex()或cdatabase::open()函数来进行初始化。数据库对象必须在使用它构造记录集对象之前初始化。
 
 
 
 三、实例
 
   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(″[bsicdata],[minsize]″);}   
  对于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以外,还可以使用参数化。利用参数化可以更直观、更方便地完成条件查询任务。使用参数化的步骤如下:
 
  s声明参变量:
 
     cstring p1;
  float p2;   
  s在构造函数中初始化参变量:
 
     p1=_t(″″);
  p2=0.0f;
  m_nparams=2;   
  s将参变量与对应列绑定:
 
   setfieldtype(cfieldexchange::param)
 
  rfx_text(pfx,_t(″p1″),p1);
  rfx_single(pfx,_t(″p2″),p2);   
  完成以上步骤后就可以利用参变量进行条件查询:
 
   requery();  
  参变量的值按绑定的顺序替换查询字串中的“?”通配符。
 
  如果查询的结果是多条记录,可以用crecordset类的函数move()、movenext()、moveprev()、movefirst()和movelast()来移动光标。
 
  2.增加记录
 
  增加记录使用addnew()函数,要求数据库必须是以允许增加的方式打开:
 
   requery();
  //重建记录集   
  3.删除记录
 
  可以直接使用delete()函数来删除记录,并且在调用delete()函数之后不需调用update()函数:
 
   movelast();  
  4.修改记录
 
  修改记录使用edit()函数:
 
   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语句的直接执行:
 
   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语句来实现。同一个记录集对象只能访问具有相同结构的表,否则查询结果将无法与变量相对应。
 
   open(afx_db_use_default_type,″select * from slot1″); //连接表slot1
   m_id=0;
   break;
 }
}   
  9.动态连接数据库
 
  可以通过赋与crecordset类对象参数m_pdatabase来连接不同数据库的cdatabase对象指针,从而实现动态连接数据库。
 
   open(_t(″motor″)))
   //连接数据源motor
   {
    afxmessagebox(″数据源motor打开失败″,″请检查相应的odbc连接″, mb_ok|mb_iconwarning);
    exit(0);
   }
   m_id=0;
   break;
 }
}   
   总结:
 
  visual c++中的odbc类库可以帮助程序员完成绝大多数的数据库操作。利用odbc技术使得程序员从具体的dbms中解脱出来,从而可以减少软件开发的工作量,缩短开发周期,并提高效率和软件的可靠性。
 
 
 
 
        
   ado提供了一组非常简单,将一般通用的数据访问细节进行封装的对象。由于odbc数据源也提供了一般的ole db privider,所以ado不仅可以应用自身的ole db privider,而且还可以应用所有的odbc驱动程序。关于ole db和ado的其它详细情况,读者可以自行查阅相关书籍或msdn,这里就不一一说明了。让我们直接步入主题,如何掌握ado这种数据库访问技术ado的操作方法和前面讲过的dao的操作在很多方面存在相似之处,在这里,笔者为了更有效的说明它的使用方法,用vc6.0做了一个示例程序(adorwaccess),这个示例程序可以直接通过ado来操作access数据库,示例程序的运行效果如下图所示:
 
 
 
  在示例程序中我们仍采用原库结构,数据库名demo.mdb,库内表名demotable,表内字段名为name(姓名)和age(年龄)的两个字段,来构造示例程序操作所需的access数据库,这也和上两篇文章的示例源码中的库结构相兼容。
   下面让我们看看ado数据库访问技术使用的基本步骤及方法:
 
  首先,要用#import语句来引用支持ado的组件类型库(*.tlb),其中类型库可以作为可执行程序(dll、exe等)的一部分被定位在其自身程序中的附属资源里,如:被定位在msado15.dll的附属资源中,只需要直接用 #import引用它既可。可以直接在stdafx.h文件中加入下面语句来实现:
 
    #import "c:/program files/common files/system/ado/msado15.dll" /
no_namespace /
rename ("eof", "adoeof")    
  其中路径名可以根据自己系统安装的ado支持文件的路径来自行设定。当编译器遇到#import语句时,它会为引用组件类型库中的接口生成包装类,#import语句实际上相当于执行了api涵数loadtypelib()。#import语句会在工程可执行程序输出目录中产生两个文件,分别为*.tlh(类型库头文件)及*.tli(类型库实现文件),它们分别为每一个接口产生智能指针,并为各种接口方法、枚举类型,clsid等进行声明,创建一系列包装方法。语句no_namespace说明ado对象不使用命名空间,rename ("eof", "adoeof")说明将ado中结束标志eof改为adoeof,以避免和其它库中命名相冲突。
 
  其次,在程序初始过程中需要初始化组件,一般可以用coinitialize(null);来实现,这种方法在结束时要关闭初始化的com,可以用下面语句couninitialize();来实现。在mfc中还可以采用另一种方法来实现初始化com,这种方法只需要一条语句便可以自动为我们实现初始化com和结束时关闭com的操作,语句如下所示: afxoleinit();
 
  接着,就可以直接使用ado的操作了。我们经常使用的只是前面用#import语句引用类型库时,生成的包装类.tlh中声明的智能指针中的三个,它们分别是_connectionptr、_recordsetptr和_commandptr。下面分别对它们的使用方法进行介绍:
 
  1. _connectionptr智能指针,通常用于打开、关闭一个库连接或用它的execute方法来执行一个不返回结果的命令语句(用法和_commandptr中的execute方法类似)。
 
   打开一个库连接。先创建一个实例指针,再用open打开一个库连接,它将返回一个iunknown的自动化接口指针。代码如下所示:
 
   open("provider=microsoft.jet.oledb.4.0;data source=demo.mdb","","",admodeunknown);
 
}
catch(_com_error e)
{
afxmessagebox("数据库连接失败,确认数据库demo.mdb是否在当前路径下!");
return false;
}   
   关闭一个库连接 如果连接状态有效,则用close方法关闭它并赋于它空值。代码如下所示:
 
   close();
m_pconnection= null;    
  2. _recordsetptr智能指针,可以用来打开库内数据表,并可以对表内的记录、字段等进行各种操作。
 
 
        
    打开数据表 打开库内表名为demotable的数据表,代码如下:
 
    errormessage());
}   
   读取表内数据 value方法,来获取当前记录指针所指的字段值,然后再用movenext()方法移动到下一条记录位置。代码如下所示:
 
   errormessage());
}      **记录 可以先用addnew()方法新增一个空记录,再用putcollect(字段名,值)输入每个字段的值,最后再update()更新到库中数据既可。其中变量m_name和m_age分别为姓名及年龄编辑框的成员变量名。代码所下所示:
 
    errormessage());
}   
   移动记录指针 移动记录指针可以通过movefirst()方法移动到第一条记录、movelast()方法移动到最后一条记录、moveprevious()方法移动到当前记录的前一条记录、movenext()方法移动到当前记录的下一条记录。但我们有时经常需要随意移动记录指针到任意记录位置时,可以使用move(记录号)方法来实现,注意: move()方法是相对于当前记录来移动指针位置的,正值向后移动、负值向前移动,如:move(3),当前记录是3时,它将从记录3开始往后再移动3条记录位置。代码如下所示:
 
   errormessage());
}   
  改记录中字段值 可以将记录指针移动到要修改记录的位置处,直接用putcollect(字段名,值)将新值写入并update()更新数据库既可。可以用上面方法移动记录指针,修改字段值代码如下所示:
 
   errormessage());
}   
   删除记录 删除记录和上面修改记录的操作类似,先将记录指针移动到要修改记录的位置,直接用delete()方法删除它并用update()来更新数据库既可。代码如下所示:
 
   errormessage());
}   
   关闭记录集 直接用close方法关闭记录集并赋于其空值。代码如下所示:
 
   close();
m_precordset = null;   
  3. commandptr智能指针,可以使用_connectionptr或_recordsetptr来执行任务,定义输出参数,执行存储过程或sql语句。
 
   执行sql语句 先创建一个_commandptr实例指针,再将库连接和sql语句做为参数,执行execute()方法既可。代码如下所示:
 
   execute(null, null,adcmdtext); // 执行sql语句,返回记录集    
   执行存储过程 执行存储过程的操作和上面执行sql语句类似,不同点仅是commandtext参数中不再是sql语句,而是存储过程的名字,如demo。另一个不同点就是在execute()中参数由adcmdtext(执行sql语句),改为adcmdstoredproc来执行存储过程。如果存储过程中存在输入、输出参数的话,需要使用到另一个智能指针_parameterptr来逐次设置要输入、输出的参数信息,并将其赋于_commandptr中parameters参数来传递信息,有兴趣的读者可以自行查找相关书籍或msdn。执行存储过程的代码如下所示:
 
   execute(null,null, adcmdstoredproc);  
  最后,如果想知道详细实现细节的话,可以在下载示例源码后,仔细查看源码既可。
 
 
        
   1. 生成应用程序框架并初始化ole/com库环境
 
  创建一个标准的mfc appwizard(exe)应用程序,然后在使用ado数据库的initinstance函数中初始化ole/com库(因为ado库是一个com dll库)。
本例为:
  bool cadotestdlg::oninitdialog() {         ::coinitialize(null); //初始化ole/com库环境    }   程序最后要调用 ::couninitialize();//释放程序占用的com 资源。
 
  另外:
 close();m_precordset = null;m_pconnection = null;   2. 引入ado库文件
 
  使用ado前必须在工程的stdafx.h文件最后用直接引入符号#import引入ado库文件,以使编译器能正确编译。代码如下:
#import "c:/program files/common files/system/ado/msado15.dll" no_namespace rename("eof","adoeof")
  ado类的定义是作为一种资源存储在ado dll(msado15.dll)中,在其内部称为类型库。类型库描述了自治接口,以及c++使用的com vtable接口。当使用#import指令时,在运行时visual c++需要从ado dll中读取这个类型库,并以此创建一组c++头文件。这些头文件具有.tli 和.tlh扩展名,读者可以在项目的目录下找到这两个文件。在c++程序代码中调用的ado类要在这些文件中定义。
   程序的第三行指示ado对象不使用名称空间。在有些应用程序中,由于应用程序中的对象与ado中的对象之间可能会出现命名冲突,所以有必要使用名称空间。如果要使用名称空间,则可把第三行程序修改为: rename_namespace("adons")。第四行代码将ado中的eof(文件结束)更名为adoeof,以避免与定义了自己的eof的其他库冲突。
 
  3.利用智能指针进行数据库操作
 
  在caboutdlg头文件中定义两个ado智能指针类实例,并在对话框中加入一个listctrl。
 class cadotestdlg : public cdialog{      _connectionptr m_pconnection;     _recordsetptr m_precordset;   clistctrl m_list;       ......}        ado库包含三个智能指针:_connectionptr、_commandptr和_recordsetptr。
 
  _connectionptr通常被用来创建一个数据连接或执行一条不返回任何结果的sql语句,如一个存储过程。
  _commandptr返回一个记录集。它提供了一种简单的方法来执行返回记录集的存储过程和sql语句。在使用_commandptr接口时,可以利用全局_connectionptr接口,也可以在_commandptr接口里直接使用连接串。  _recordsetptr是一个记录集对象。与以上两种对象相比,它对记录集提供了更多的控制功能,如记录锁定、游标控制等。
 
  在使用ado程序的事件响应中onbutton1加入以下代码:
 close(); m_precordset = null; m_pconnection = null; } 
  程序中通过_variant_t和_bstr_t转换com对象和c++类型的数据, _variant_t类封装了ole自治variant数据类型。在c++中使用_variant_t类要比直接使用variant数据类型容易得多。
 
  好,编译后该程序就能运行了,但记住运行前要创建一个叫adotest的odbc数据源。该程序将把表middle中的big_name字段值显示在列表控件中。 
 
        
   4.执行sql命令并取得结果记录集
 
      为了取得结果记录集,我们定义一个指向recordset对象的指针:_recordsetptr m_precordset;
并为其创建recordset对象的实例: m_precordset.createinstance("adodb.recordset");
sql命令的执行可以采用多种形式,下面我们一进行阐述。
 
  (1)利用connection对象的execute方法执行sql命令
 
  execute方法的原型如下所示:
 close();///关闭记录集     cstring message;    message.format("共有%d条记录",vcount.lval);     afxmessagebox(message);///显示当前记录条数 (2)利用command对象来执行sql命令 
 execute(&vnull,&vnull,adcmdtext);///执行命令,取得记录集   在这段代码中我们只是用command对象来执行了select查询语句,command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
 
  (3)直接用recordset对象进行查询取得记录集
 
  实例——
 open("select * from users", _variant_t((idispatch *)m_pconnection,true), adopenstatic, adlockoptimistic, adcmdtext);open方法的原型是这样的:hresult recordset15::open ( const _variant_t & source,     const _variant_t & activeconnection,    enum cursortypeenum cursortype,    enum locktypeenum locktype,    long options ) 其中:①source是数据查询字符串②activeconnection是已经建立好的连接(我们需要用connection对象指针来构造一个_variant_t对象) ③cursortype光标类型,它可以是以下值之一,请看这个枚举结构:enum cursortypeenum{ adopenunspecified = -1,///不作特别指定 adopenforwardonly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用movenext向前滚动,这种方
式可以提高浏览速度。但诸如bookmark,recordcount,absoluteposition,absolutepage都不能使用 adopenkeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操
作对你是可见的。 adopendynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。 adopenstatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你
的记录集来说是不可见的。};④locktype锁定类型,它可以是以下值之一,请看如下枚举结构:enum locktypeenum{ adlockunspecified = -1,///未指定 adlockreadonly = 1,///只读记录集 adlockpessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 adlockoptimistic = 3,乐观锁定方式。只有在你调用update方法时才锁定记录。在此之前仍然可以做
数据的更新、**、删除等动作 adlockbatchoptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、**及删除是在批处理模
式下完成。}; ⑤options可以取如下值之一: adcmdtext:表明commandtext是文本命令 adcmdtable:表明commandtext是一个表名 adcmdproc:表明commandtext是一个存储过程 adcmdunknown:未知   
 
        
   5. 记录集的遍历、更新
 
      根据我们刚才通过执行sql命令建立好的users表,它包含四个字段:id,username,old,birthday
以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,
更改其年龄,保存到数据库。    delete(adaffectcurrent);///删除当前记录///添加三条新记录并赋值for(int i=0;i update();///保存到库中      备注:多次查询可把查询过程做成一个函数executesql让m_precordset获得连接指针m_pconnection查询结果 open(bstrsql,(idispatch*)m_pconnection,adopendynamic,adlockoptimistic,
adcmdtext);              //adopendynamic:动态 adlockoptimistic乐观封锁法 adcmdtext:文本查询语句     }     catch(_com_error error)     {        cstring errormessage;        errormessage.format("%s",(lptstr)error.description());        afxmessagebox(errormessage);     }}        //出错处理:3127——没有找到目标表3092——目标表已经存在例如:catch(const _com_error e){      afxmessagebox(e.description());     long errorcode=e.wcode();     if(3127==errorcode) afxmessagebox("表不存在");      if(3092==errorcode) afxmessagebox("表已经存在");      return false;}   
 
        
无忧书籍网 www.51shuji.com
 
 
 
  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 2

打赏作者

wangxfvc

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值