数据库编程第2章

第2章 COM与数据库访问

2.1 COM的基本原理

COM即组件对象模型,是一种以组件为发布单元的对象模型。这种模型使各种软件组件可以通过一种通用的方式进行交互。COM既提供了组件之间进行交互的规范,也提供了实现交互的环境,因为组件对象之间交互的规范不依赖于任何特定的语言,所以COM也可以是不同语言协作开发的一种标准。

也许读者对OLE(对象链接嵌入,Object Linking and Embedding)不太陌生,OLE技术也是以COM技术为基础的。OLE充分发挥了COM标准的优势,使Windows操作系统上的应用程序具有非常强大的交互性。如果没有OLE的支持,Windows操作系统一定逊色许多。但是COM规范并不局限于OLE技术,实际上,OLE技术只是COM的一个应用而已。近年来网络技术迅猛发展,OLE技术在进行网络互联时表现出很大的局限性,而COM则表现出了很好的适应能力。因此,伴随着网络技术的发展,COM也得到了很好的展示机会。继OLE技术之后,微软又推出了一系列以COM为基础的技术,即ActiveX技术,再一次展示了COM的价值。

2.1.1 COM历史

微软起初并没有意料到COM的强劲发展势头,最初微软在桌面窗口系统里使用了OLE,随着桌面窗口应用程序之间交互的不断深入,微软将OLE发展成为COM,而后来COM技术的不断发展表明,COM所定义的组件标准其广泛性远远超过了OLE所具有的能力。

其实一开始COM就具有很好的应用前景。但是由于OLE技术的复杂性,一般人很少能窥探到OLE的底层,尤其是通过OLE学习COM本来就是本末倒置。所以可以这样说,是OLE阻碍了COM的发展,甚至是OLE的一些缺点掩盖了COM的优势。但是这种情况很快有了好转。人们认识到了COM是符合当前软件发展需要的很好的组件标准,使用COM进行软件构造是一种理想的应用方案。COM脱离了OLE之后得到了很大的发展,现在COM已经遍布于微软的各种软件中了。

组件化软件结构为我们带来了极大的好处。首先是软件升级的灵活性,每个组件可单独开发,单独升级,甚至单独调试和测试,只要组建的接口不变,组建的升级不会影响到软件的其它部分。其次,COM是一种面向对象的组件模型,COM对象以接口的方式提供服务,我们统称之为COM接口。

从COM在OLE2中首次作为底层技术使用以来,COM已经走过了六、七年时间,而且日益壮大,成为广为接受的软件组件模型。

2.1.2 COM结构

COM为组件和应用程序之间提供了通信的统一标准,为组件程序提供了一个面向对象的活动环境。COM标准包括规范和实现两大部分,规范部分定义了组件和组件之间通信的机制,这些规范不依赖于任何特定的语言和操作系统,只要遵循该规范,如何语言都可以作为组件开发的原始语言;COM标准的实现部分是COM库,COM库为COM规范的具体实现提供了一些核心服务。图2-1表示了COM组件、COM对象与COM接口三者之间的关系。

COM是面向对象的软件模型,对象是它的基本要素之一。COM对象有些类似于C++对象的概念,对象是类的一个实例。COM接口是一组逻辑上相关的函数集合,通常的COM接口都是以字母“I”作为前缀,例如IUnknown接口,对象通过接口成员函数为客户提供各种形式的服务。在COM模型里,对象对于使用组件的用户来说是透明的,客户请求服务时,只能通过接口进行。COM的每个接口都由一个128位的全局标识符(GUID,Globally Unique Identifier)来标识,客户通过GUID获得接口的指针,再通过接口指针调用其相应的成员函数。客户端对COM对象的标识也是通过一个128位的GUID来实现,成为CLSID(Class Identifier),是用CLSID可以保证对象在全球范围内的唯一性。只要用户的系统中还有该类COM对象的信息,并且包括COM对象所在的模块文件(DLL或者EXE文件)以及COM对象在代码中的入口点,客户程序就可以通过CLSID创建对象(实际上是得到对象的一个指针),然后调用对象的成员函数,实现特定的功能。

COM对象和客户之间的相互作用是建立在客户/服务器模型上的,客户/服务器模型的最大优势是它的稳定性,正是这种稳定性为COM奠定了基础。另外,在客户/服务器模型的基础上,COM技术灵活地扩展了这一模型。

这部分内容看不清,暂时未添加。

COM除了规范以外也有实现部分,包括一些核心的系统级代码,是这些代码使得对象和库之间可以通过接口在二进制代码级别上进行交互。在Windows操作系统下,这些代码是以DLL文件的形式存在的,其中包括以下内容:

(1)提供了少量的API函数实现客户和服务端COM应用的创建过程。在客户端主要是一些创建函数;而在服务端,则提供了一些对象访问支持。

(2)COM通过注册表查找本地服务器,即EXE程序,以及程序名与CLSID的转换。

(3)提供了一种标准的内存控制方法,使应用程序控制进程中内存的分配。

COM库可以提供所有的组件按照统一的方式进行交互操作,使我们在编写COM应用程序时,不必为COM通信编写大量的基础代码,而是直接利用COM的API函数实现,从而大大加快了开发的速度。另外,COM库实现了更多的特性,例如DCOM,我们可以充分享用这些特性。

2.1.3 COM优势

COM除了客户/服务器模型特性以外,还具有语言无关性、进程透明性以及可重用机制。

COM规范的定义不依赖于任何特定的语言,因此编写组件对象所使用的语言和编写客户程序使用的应用可以不同,只要他们都能够生成符合COM规范的可执行代码即可。COM标准与面向对象的编程语言不同,它所采用的是一种二进制代码级别的标准,而不是源代码级别的标准。面向对象编程语言中定义的对象不能够跨语言进行调用,大大限制了对象的重用。COM对象把OOP(面向对象编程)语言中的对象封装到一个二进制代码里,并提供统一的接口,可以被其它编程语言所使用。

COM的进程透明性是指,运行在客户程序中的代码与运行在服务程序中的代码可以在一个进程中,也可以在不同的进程中,这取决于COM编程的方式,但是这种进程的运行对用户来说是透明的,用户可以不关心进程的位置。通常COM代码运行的方式有两种:一种是运行在客户进程里,另外一种是运行在客户进程之外。显然前者具有很高的效率,因为它可以共享客户进程里的数据,但是前者在稳定性方面就不及后者健壮,后者的COM组件的不稳定性不会影响到客户进程运行,前者则会在这种情况下崩溃。

可重用性是所有组件模型要实现的共同目标,尤其对大型的软件系统,软件的可重用性具有非常重要的意义。对COM对象的客户程序来说,它只是通过接口使用对象提供的服务,并不了解对象内部的实现过程,因此组建的重用性可以建立在组件对象的行为方式上,而不是在具体实现上,这是建立重用的关键。COM通过两种机制实现对象的重用,一种是包容方式,一个对象需要调用另外一个对象的功能时自行调用,而不需要用户干预;另外一种是聚合方式,一个对象在需要另外一个对象的功能时只是把该对象的接口暴露给用户,由用户通过接口进行功能调用。对象重用是COM规范很重要的一个方面,它保证了COM可以用于构建大规模的软件系统,使复杂的系统简化为一些相对简单的组件对象模块,体现了面向对象的思想。

2.1.4 COM接口

COM对象的客户与对象之间提供接口进行交互,COM规范的核心内容是关于接口的定义。

在软件重用上,COM同传统的API相比具有很大的优势。传统的API一般是通过DLL库实现的,在函数声明时也可以基本实现组件程序的重用性,可以将两个程序连接起来,但是这种API存在以下两个问题:

(1)当API函数非常多的时候,使用不方便,需要对函数进行有效地组织。例如,Windows系统的API函数有300多个,编程接口面太宽,不利于接口层的管理。

(2)API函数需要标准化,按照统一的调用方式进行处理,以使用不同的语言编程实现。参数的传递、参数类型、函数返回处理都需要标准化。

COM定义了一套完整的接口规范,不仅可以弥补API作为组件接口的不足,还发挥了组件对象的优势,并实现了组件对象的多态性。

在COM接口里最常用的是IUnknown接口,其它所以的接口都从该接口继承过来,IUnknown接口提供了两个非常重要的特性:生存期控制和接口查询。IUnknown接口引入了“引用计数”机制,可以有效地控制对象的生存期。另一方面,如果对象实现了多个接口,初始化时,对象不可能通过所有的接口指针,它只会拥有一个接口指针,这时,用户可以使用接口查询的方法来获得其它接口。IUnknown接口定义了三种方法:QueryInterface、AddRef、Release,所有的COM接口都可以使用这三个接口。

对象不再使用的时候,COM并不会自动将其从内存中移走,COM开发人员必须自己将不同的对象移去,可以根据引用计数决定是否移去一个对象。COM通过IUnknown接口的AddRef、Release两种方法管理对象接口的引用计数,它们的通常调用规范如下:

  • 客户在任何时候收到一个接口指针,AddRef都必须被接口调用。
  • 客户使用完成对象以后必须调用Release函数。

AddRef和Release成员函数为生存期控制提供了支持,生存期控制就是通过引用计数来实现的。每个对象拥有一个可以跟踪对象的指针,或者引用的内部值。当对象创建时该计数为1.如果附件的接口或者接口的成员函数被创建,则该计数递增。相应地,如果接口的指针被销毁,则该计数递减。当引用计数为0时,该对象自行销毁。AddRef函数用来使对象的引用计数递增,而Release函数则使引用计数递减,当引用计数为0时,Release函数释放对象。

按照COM规范,一个COM对象可以支持多个接口,客户可以在运行时对COM对象的接口进行查询,QueryInterface函数允许COM对象实现这样的功能。查询是通过输入一个128位的GUID来实现的,成为IID。QueryInterface函数的查询结果是接口的一个指针,存放在它的另外一个参数里,查询失败时,该指针为空。

2.1.5 COM与数据库访问

COM对数据库访问的支持这样表现2在OLE DB和DAO上。OLE DB是完全基于COM的,可以认为它是ODBC的替代品,但不再局限于关系型数据库,而是几乎适用于所有的线性数据;DAO是建立在OLE DB上层的自动化对象库,它可以广泛运用于各种脚本语言中,为脚本语言访问数据库提供了极大的便利。

OLE DB/DAO 包含数据库访问的三个层次:数据提供者(data provider)、数据服务组件(data service component)、数据使用者(data consumer)。由于采用了开放的COM接口,增加数据源支持将变得更加容易,数据提供者只需要提供基本的服务,在应用层上的数据使用者就可以获得各种服务组件提供的服务。

OLE DB/DAO 以COM的方式为数据访问提供了一致的接口,这些接口已经被广泛运用于微软的各种产品中,并且微软推出的Visual Basic开发工具套也提供了OLE DB组件的开发支持,因此,OLE DB/DAO 将会得到进一步的发展。

2.1.6 COM与Internet

COM在Internet相关软件中的发展最能体现COM的优势,因为Internet软件要求有很好的开放性,开放性就意味着要遵循标准,在Windows平台上,COM就是这样的标准。

微软提出的ActiveX技术包含了所有基于COM的Internet相关的软件技术。从服务器方面,ASP(Active Server Page)把微软的IIS(Internet Information Server, Internet信息服务器)和其它的软件产品结合起来了。ASP页面文件中的脚本分成服务器方执行的代码和客户方执行的代码,而服务器方执行的代码可以通过DAO访问IIS服务器上的数据库,可以调用其它ASP(也是COM组件对象)所提供的各种功能服务。因此,在服务方提供动态信息时非常方便,只需要在服务器上开发一些ASP对象即可,这种Web服务器通过微软的开发工具可以很容易地实现。

从Internet的客户方来看,其内容更为丰富,Internet Client SDK 提供了很多可以直接使用的组件或者特性,包括ActiveX控制、WebBrowser控制、XML对象模型、Internet Explorer地址薄(通过LDAP协议实现)等,这些组件可以为Internet用户提供更多的便利,同时提供更为丰富多彩的信息。

由于COM已经渗透到Internet的各种软件中,包括一些基本的协议软件,所以,随着Internet的发展,COM必将得到更加广泛的应用。

2.2 ActiveX的数据库访问

2.2.1 ActiveX简介

ActiveX是COM技术的进一步扩展,它定义了组件的通用性,使组件能够同另外一个组件通信,而不对创建组件的语言进行限制;ActiveX定义了组件的分布式对象模型(DCOM),使分布在不同机器上的组件之间的通信可以像本地访问一样容易。ActiveX在Internet上的广泛应用使得Internet用户可以在它的支持下实现与网页内容的交互,为建立活动页面奠定了技术基础。

ActiveX的优势是:

  • 开放的、跨平台的支持,支持Macintosh、Windows和UNIX等开放系统。
  • 通过网页内容交互可以吸引和保持用户。
  • 具有有力的开发工具支持,Microsoft的Visual Basic和Visual C++、Borland的Delphi和C++、Java以及支持Java的工具都可以使用ActiveX技术。
  • 大量的ActiveX控制可以直接被用户使用。
  • 工业标准支持HTML、TCP/IP、Java和COM。

ActiveX的内容包括:

  • ActiveX 控制:在交互的、用户可控制Web页面里提供活动对象。
  • ActiveX 文档:使用户可以浏览非HTML文档,例如Microsoft Excel或者Word文件。
  • ActiveX 脚本:可以在浏览器里或服务端控制几个ActiveX控件以及与Java Applets的集成功能。
  • Java 虚拟机:使支持ActiveX的浏览器可以运行Java Applets,或者集成Java Applets和ActiveX控制。
  • ActiveX 服务器框架:提供一些基于Web服务器的功能,例如,安全、数据操作和其他功能。

2.2.2 ActiveX对数据库访问的支持

ActiveX 对数据库访问的支持指ADO,因此有必要对ADO进行深入探讨。

ADO是OLE DB层次上的高级数据库访问接口API,与OLE DB相比,ADO的编程更容易。ADO适用于更多的语言,尤其适合脚本语言,例如VBScript和JavaScript等。ADO也适用于多种编程语言,包括Visual Basic、Java和Visual C++。

ADO提供了一个“双重界面”,正是这个“双重界面”使得ADO同时适用于脚本语言和编程语言。ADO实际上提供了两套API,一套通过OLE自动化,该套API面向不适用指针的语言,比如脚本语言。另一套API通过vtable界面向C++程序提供。

ADO的编程模型一般由一个动作系列组成。ADO提供了一组类,可以简化在C++代码中建立这种序列的处理。通常的动作系列是:

  • 连接到一个数据源。
  • 指定对该数据源的一个查询。
  • 执行该查询。
  • 把查询数据检索到一个在C++代码中容易访问的对象里。
  • 需要时更新数据源,来反映对该数据的编辑。
  • 提供检测错误的方法。

2.3 ATL的数据库访问

2.3.1 ATL 目标

自从1993年的Microsoft首次公布COM技术以后,Windows平台上的开发模式发生了巨大变化,以COM技术为基础的一系列软件组件化技术将Windows编程带入了组件化时代。广大的软件开发人员在为COM带来的软件组件化趋势欢欣鼓舞的同时,对COM开发技术的难度和繁琐的细节也深感不便。COM编程一度被视为一种高不可攀的技术,令人望而却步。软件开发人员希望能够有一种方便快捷的COM开发工具,提高开发效率,更好地利用这项技术。

针对这种情况,Microsoft公司在推出COM SDK以后,为简化COM编程,提高开发效率,采取了许多方案,特别是在MFC中加入了对COM和OLE的支持。但是随着Internet的发展,分布式的组件技术要求COM组件能够在网络上传输,而又尽量节约宝贵的网络宽带资源。采用MFC开发的COM组件由于种种限制不能很好地满足这种需求,因此Microsoft在1995年又推出了一种全新的COM开发工具——ATL。

在ATL产生之前,开发COM组件的方法主要有两种:一是使用COM SDK直接开发COM组件,另一种方式是通过MFC提供的COM支持来实现。

直接使用COM SDK开发COM组件是最基本也是最灵活的方式。通过是用脑Microsoft提供的开发包,我们可以直接编写COM程序。但是,这种开发方式的难度和工作量都很大,一方面,要求开发者对于COM的技术原理具有比较深入的了解(虽然对技术本身的深刻理解对使用任何一种工具都是非常有益的,但对于COM这样一整套复杂的技术而言,在短时间内完全掌握是很难的)。另一方面,直接使用COM SDK要求开发人员自己去实现COM应用的每一个细节,完成大量的重复性工作。这样做的结果是,不仅降低了工作效率,同时夜视开发人员不得不把许多精力投入到应用需求本身无关的技术细节中。虽然这种开发方式对于某些特殊的应用很有必要,但这种编程方式并不符合组件化程序设计方法所倡导的可重用性。因此,直接采用COM SDK不是一种理想的开发方式。

使用MFC提供的COM支持开发COM支持开发COM应用可以说在使用COM SDK基础上提高了自动化程度,缩短了开发时间。MFC采用面向对象的方式将COM的基本功能封装在若干MFC的C++类中,开发者通过继承这些类得到COM支持功能。为了使派生类方便地获得COM对象的各种特性,MFC中有许多预定义宏,这些宏的功能主要是实现COM接口的定义和对象的注册等通常在COM对象中要用到的功能。开发者可以使用这些宏来定制COM对象的特性。

另外,在MFC中还提供对Automation和ActiveX control的支持,对于这两个方面,Visual C++也提供了相应的APPWizard和ClassWizard支持,这种可视化的工具更加方便了COM应用的开发。

MFC对COM和OLE的支持确实比手工编写COM程序有了很大的进步。但是MFC对COM的支持是不够完善和彻底的,例如对COM接口定义的IDL语言,MFC并没有任何支持。此外,对于近些年来COM和ActiveX技术的新发展,MFC也没有提供灵活的支持。这是由MFC设计的基本出发的决定的。MFC被设计成对Windows平台编程开发的面向对象的封装,自然要设计Windows编程的方方面面,COM作为Windows平台编程开发的一个部分也得到MFC的支持。但是MFC对COM的支持是以其全局目标为出发点的,因此对COM的支持必然要服从其全局目标。从这个方面而言,MFC对COM的支持不能很好的满足开发者的要求。

随着Internet技术的发展,Microsoft将ActiveX技术作为其网络战略的一个重要组成部分大力推广。然而使用MFC开发的ActiveX Control,代码冗余量大,而且必须要依赖于MFC的运行时刻库才能正确地运行。虽然MFC的运行时刻库只有部分功能与COM有关,但是由于MFC的继承实现的本质,ActiveX Control必须背负运行时刻库这个沉重的包袱。如果采用静态连接MFC运行时刻库的方式,这将使ActiveX Control代码过于庞大,在网络上传输时将占据宝贵的网络带宽资源;如果采用动态连接MFC运行时刻库的方式,这将要求浏览器一方必须具备MFC的运行时刻库支持。总之,MFC对COM技术的支持在网络应用的环境下也显得很不灵活。

解决上述COM开发方法中的问题正是ATL的基本目标。

首先,ATL的基本目标就是使COM应用开发尽可能地自动化,这个基本目标就决定了ATL只面向COM开发提供支持。目标的明确使ATL对COM技术的支持达到淋漓尽致的地步。对COM开发的任何一个环节和过程,ATL都提供支持,并将与COM开发相关的众多工具集成到一个统一的编程环境中。对于COM/ActiveX的各种应用,ATL也都提供了完善的Wizard支持。所有这些都极大地方便了开发者的使用,使开发者能够把注意力集中在应用本身相关的逻辑上。

其次,ATL因其采用了特定的基本实现技术,摆脱了大量冗余代码,使用ATL开发出来的COM应用的代码简练高效,即所谓的“Slim Code”。ATL在实现上尽可能采用优化技术,甚至在其内部提供了所有C/C++开发的程序所必须具有的C启动代码的替代部分。同时ATL产生的代码在运行时不需要依赖于类似MFC程序所需要的庞大的代码模块,包含在最终模块中的功能是用户认为最基本和最必须的。这些措施使采用ATL开发的COM组件(包括ActiveX Control)可以在网络环境下实现应用的分布式组件结构。

第三,ATL的各个版本对Microsoft的基于COM的各种新的组件技术,如MTS、ASP等都有很好的支持。ATL对新技术的反应速度大大快于MFC。ATL已经成为Microsoft支持COM应用开发的主要开发工具,因此,COM技术方面的新进展在很短的时间内都会在ATL中得到反映。这使开发者使用ATL进行COM编程可以得到直接使用COM SDK编程同样的灵活性和强大的功能。

2.3.2 ATL 内容简介

ATL 是 ActiveX Template Library 的缩写,它是一套C++模板库。使用ATL能够快速地发出高效、简介的代码,同时对COM组件的开发提供最大限度的代码自动生成以及可视化支持。为了方便使用,从Microsoft Visual C++ 5.0 版本开始,Microsoft 把 ATL 集成到Visual C++ 开发环境中。1998年9月推出的 Visual Studio 6.0 集成了 ATL 3.0 版本。目前,ATL 已经成为Microsoft 标准开发工具中的一个重要成员,日益受到C++开发人员的重视。

同MFC类似,ATL提供了一套基于模板的C++类。通过这些类,可以很容易地创建一个小型的、快速的COM对象。因此,可以说,ATL是为COM应用开发的。ATL是一个产生C++/COM代码的框架,就如同C语言是一个产生汇编代码的框架一样。ATL的内部模板类实现了COM的一些基本特性,比如一些基本的COM接口IUnknown、IClassFactory、IClassFactory2和IDispatch的实现。ATL也支持COM的一些高级特性,如双重接口、标准COM计数器接口、连接点,以及ActiveX等。通过ATL建立的COM对象支持套间线程和自由线程两种模型。

模板是C++语言的高级语法特性,它是更高层次上的抽象。在使用模板库时,我们不再通过继承的方式使用模板,而是对模板进行实例化以便得到类。可以说,模板是对类的抽象。因此,使用和编写模板对程序员的要求很高,尤其要求模板本身的代码必须是类型安全的。

ATL实现COM接口的方式与MFC有所不同,MFC使用嵌套类的方式实现COM接口,用接口映射表提供多接口支持,ATL则使用多重继承的方式实现COM接口。虽然MFC和COM在实现接口上不同,但是二者支持形式非常相似。MFC采用接口映射表,定义了一组宏;ATL采用了COM映射表,也定义了一组形式上很相似的宏。为了支持COM对象的创建,ATL还定义了对象映射表。对象映射表是一张全局表,放在ATL应用的主源文件里。

简单地说来,ATL中所使用的基本技术包括以下三个方面:

  • COM 技术
  • C++模板类技术
  • C++多重继承技术

作为ATL最核心实现技术的模板是对标准C++语言的扩展。但是在大多数C++编程环境中,人们很少使用它。这是因为,虽然模板的功能很强,但是它内部机制比较复杂,需要较多的C++知识和经验才能灵活地使用它。例如,在MFC中的CObjectArray等功能类就是由模板来定义的。完全通过模板来定义程序的整体类结构,ATL是迄今为止做得最为成功的。

所谓模板类,简单地说,就是对类的抽象。我们知道C++语言用类定义了构造对象(这里指C++对象而不是COM对象)的方式,对象是类的实例,而模板类定义的是类的构造方式,使用模板类定义实例化的结果产生的是不同的类。因此可以说模板类是“类的类”。

2.3.3 ATL对数据库访问的支持

ATL提供的数据库访问COM接口是对OLE DB API的包装,因此属于高级的数据库编程接口。ATL提供的数据库访问COM接口主要是如下的几个类:

  • CDataSource 类:该类用于创建一个数据源对象,这个对象把OLE DB客户程序和OLE DB供应程序连接起来。该类通常用于向供应程序发送连接数据源名称、用户ID以及口令等属性。在属性建立以后,就可以调用该类的Open方法,打开一个通往OLE DB供应程序的数据连接。
  • CSession 类: CSession 对象表示了一个客户程序同供应程序的一次对话。类似于 ODBC 中许多同步对话的动态的HSTMT。CSession对象是主链接,从而得到OLE DB的功能。通过该对象,可以得到一个命令、事务处理或者行集对象。
  • CCommand 类:该类的对象处理数据的操作,例如查询。它可以处理参数化和非参数化的语句。CCommand对象也能响应待定参数或者输出一列。绑定是一个结构,包含了指定在哪个行集检测,是哪个列的信息,它也包含了如数据类型、状态等信息。
  • CRowset 类:CRowset 对象代表了来着数据源的数据。这个对象响应绑定的数据及任何的基本操作,例如更新、查询、移动等。总是需要一个CR我色堂对象来包含和维护数据。
  • CAccessor 类:CAccessor 对象描述了存储在OLE DB用户程序的存取器,通常被定义为行集存储和传输数据。CAccessor对象也能由供应程序控制把用户变量绑定到返回数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值