【 Pro*C/C++ 】 Pro*C/C++ 编程

ProC/C++ 编程 1
一、Pro
C/C++ 简介 1
1.1、ProC/C++ 是什么 1
1.2、Pro
C/C++ 处理流程 2
二、ProC/C++ GCC 环境配置 3
2.1、Pro
C/C++ 预编译环境 3
2.2、GCC 编译器 5
三、开始编写第一个ProC++代码 5
3.1、第一个Pro
C++代码 5
3.2、ProC++代码预编译 6
3.3、GCC 编译 7
3.4、最后测试 8
四、复杂一些的Pro
C/C++代码 8
4.1、代码部分 8
4.2、代码编译 12
4.3、代码测试 14

一、ProC/C++ 简介
1.1、Pro
C/C++ 是什么
为了用户开发Oracle应用程序, Oracle提供了一套Oracle调用规范, 即Oracle Call Interface (OCI),但是比较难以掌握使用, 对于熟悉 C/C++ 的用户要编写Oracle应用程序, 还需要花很多时间去研究 OCI, 因此 Oracle 推出了一个在 C/C++ 代码中直接嵌入 SQL 的方法, 然后通过 Oracle 提供的预编译程序, 编译成SQLLIB的调用, 这就是 Pro*C/C++。
注意, SQLLIB 与 OCI 是不同的, SQLLIB 主要是保含一系列函数的Oracle 应用程序运行库,用于应用程序执行或预编译时调用,其本身并不是Oracle 标准接口API,随着Oracle 版本的变化SQLLIB 的函数调用格式、用法、参数等也有可能变化。如果一个项目中需要一个比较通用的、平台一致性好的、不随数据库版本变化而变化的一系列封装函数,最好的建议是使用Oracle Call Interface (OCI) 进行标准C 语言的编程处理。

1.2、ProC/C++ 处理流程
C/C++ 源代码 =⇒ 源代码中嵌入SQL =⇒ 通过 Pro
C 预编译命令产生新的源代码
=⇒ 通过正常的C/C++编译器编译连接应用程序 =⇒ 运行应用程序

二、ProC/C++ GCC 环境配置
2.1、Pro
C/C++ 预编译环境
当您安装 Oracle 数据库产品的时候,ProC/C++的预编译环境缺省就为我们安装好了,我们这里以安装 Oracle 10G 版本为例. 在 Oracle_Home的 Bin 目录下, 您可以找到预编译程序 proc.exe, 这个就是用来预编译您有在C/C++代码中嵌入SQL的文件的. 该编译程序用到了 Oracle_Home下的precomp\public 目录中的 .h 头文件, 在正真编译生成可执行文件时又会用到 precomp\lib 的一个 .lib 库文件.
我们进入 command 窗口,看看 proc 编译命名的参数:
Pro
C/C++编译命令参数(10G)
C:\oracle\product\10.2.0\db_2\BIN>proc

Pro*C/C++: Release 10.2.0.1.0 - Production on Mon Feb 23 16:22:29 2009

Copyright © 1982, 2005, Oracle. All rights reserved.

System default option values taken from: C:\oracle\product\10.2.0\db_2\precomp\admin\pcscfg.cfg

Option Name Current Value Description

auto_connect no Allow automatic connection to ops$ account
char_map charz Mapping of character arrays and strings
close_on_commit no Close all cursors on COMMIT
cmax 100 CMAX Value for connection pool
cmin 2 CMIN Value for connection pool
cincr 1 CINCR Value for connection pool
ctimeout 0 CTIMEOUT Value for connection pool
cnowait 0 CNOWAIT Value for connection pool
code cpp The type of code to be generated
comp_charset multi_byte The character set type the C compiler supports
config default Override system configuration file with another
cpool no Support connection pooling
cpp_suffix cc Override the default C++ filename suffix
dbms native v6/v7/v8 compatibility mode
def_sqlcode no Generate ‘#define SQLCODE sqlca.sqlcode’ macro
define none Define a preprocessor symbol
duration transaction Set pin duration for objects in the cache
dynamic oracle Specify Oracle or ANSI Dynamic SQL Semantics
errors yes Whether error messages are sent to the terminal
errtype none Name of the list file for intype file errors
events no Support publish-subscribe event notifications
fips none FIPS flagging of ANSI noncompliant usage
header none Specify file extension for Precompiled Headers
hold_cursor no Control holding of cursors in the cursor cache
iname none The name of the input file
include none Directory paths for included files
intype none The name of the input file for type information
lines no Add #line directives to the generated code
lname none Override default list file name
ltype short The amount of data generated in the list file
maxliteral 1024 Maximum length of a generated string literal
maxopencursors 10 Maximum number of cached open cursors
mode oracle Code conformance to Oracle or ANSI rules
native_types no Support for native float/double
nls_char none Specify National Language character variables
nls_local no Control how NLS character semantics are done
objects yes Support object types
oname none The name of the output file
oraca no Control the use of the ORACA
pagelen 80 The page length of the list file
parse none Control which non-SQL code is parsed
prefetch 1 Number of rows pre-fetched at cursor OPEN time
release_cursor no Control release of cursors from cursor cache
select_error yes Control flagging of select errors
sqlcheck SEMANTICS Amount of compile-time SQL checking
sys_include none Directory where system header files are found
threads no Indicates a multi-threaded application
type_code oracle Use Oracle or ANSI type codes for Dynamic SQL
unsafe_null no Allow a NULL fetch without indicator variable
userid none A username/password [@dbname] connect string
utf16_charset nchar_charset The character set form used by UTF16 variables
varchar no Allow the use of implicit varchar structures
version recent Which version of an object is to be returned
win32_threads no Support windows default local threading
PCC-F-02135, CMD-LINE: User asked for help

这些命令参数选项,除了直接在命令行参数中指定外,您也可以通过 precomp\admin\psccfg.cfg 文件中进行指定, 下面是一个样例:
ltype=short
code=cpp
cpp_suffix=cc
parse=none
SQLCHECK=SEMANTICS
这里我们介绍其中的三个参数:
• code=cpp 这个参数表明我们通过 proc 预编译命令产生的代码文件是符合 C++ 规范的,如果您是在标准C环境中使用, 那么您可以指定 code=ansi_c
• cpp_suffix=cc 这个参数让 proc 命令产生的文件以 .cc 作为文件扩展名
• SQLCHECK=SEMANTICS 这个参数用于指示在预编译的时候是否要检查嵌入的SQL的语法,您可以设置 NONE 表示不检查(10G中中已经不再支持这个值了,至少设置为 SYNTAX), SEMANTICS 表示检查语义(也就是检查数据库表名,列名是否存在等)
2.2、GCC 编译器
在 Windows 环境中, 您可以选择使用微软的 VC 编译器, 或者 Bland公司的 BC 编译器. 我们这里使用 GCC 1) 编译器, GCC 编译器的安装可以直接从 http://gcc.gnu.org 这里下载, 最新提供一个在Windows下快速安装的工具程序.这里我们采用了 Dev-CPP 的开发环境, 这个开发环境中包含了 GCC, 下载地址见 http://www.bloodshed.net/dev/devcpp.html
三、开始编写第一个ProC++代码
3.1、第一个Pro
C++代码
本文中涉及到的 C++ 知识不做过多介绍,已经超出了本文范围.
编写一个 Oracle 登陆程序(我们这里使用 C++ 代码, 当然您也可以使用 标准C 代码):
1: using namespace std;
2: #include <iostream.h>
3: #include “sqlca.h” // SQL Comunication Area
4:
5: EXEC SQL BEGIN DECLARE SECTION;
6: char uid = “scott/tiger@orcl10g”;
7: EXEC SQL END DECLARE SECTION;
8:
9: int main()
10: {
11: EXEC SQL CONNECT :uid;
12: if (sqlca.sqlcode != 0)
13: cout<<sqlca.sqlerrm.sqlerrmc<<endl;
14: else
15: cout<<“Success.”<<endl;
16: EXEC SQL COMMIT WORK RELEASE;
17: }
第5,7行代码表示要定义SQL宿主变量, 这里边的变量的定义方式依照 C++ 中的方式定义.这些变量除了能够在正常的 C++ 代码中使用外, 也能使用在嵌入的SQL语句中, 因此被成为宿主变量
第 6 行就申明了一个 SQL 宿主变量
第11行为连接数据库的指令.
第12行判断SQL通讯区变量 sqlca 中的信息, 以检查sql语句执行是否成功, 这里检查是否连接成功, 成功执行 sqlca.sqlcode 值为 0, 当有错误发生时, sqlca.sqlerrm 这个子结构保存了相应的错误讯息.
第16行为提交工作并释放资源(也即断开连接).
我们把这个文件保存在 C:\proc 目录下, 命名为 myfirst.pc (通常我们编写的Pro
C 程序以 .pc 结尾)
3.2、Pro*C++代码预编译
首先设置预编译命令的路径,然后再行编译
C:>set ORACLE_HOME=C:\oracle\product\10.2.0\db_2

C:>set path=%ORACLE_HOME%\bin

C:>cd proc

C:\proc>dir /b
myfirst.pc

C:\proc>proc myfirst.pc

Pro*C/C++: Release 10.2.0.1.0 - Production on Wed Feb 25 13:58:52 2009

Copyright © 1982, 2005, Oracle. All rights reserved.

System default option values taken from: C:\oracle\product\10.2.0\db_2\precomp\admin\pcscfg.cfg

C:\proc>dir /b
myfirst.cc
myfirst.lis
myfirst.pc

C:\proc>

以上已经与编译成功,我们可以看到新产生了 myfirst.cc 文件, 这个文件就是将嵌入的SQL指令预编译成为标准的ORACLE库函数调用后的文件.您可以查看该文件内容.
3.3、GCC 编译
在进行预编译产生正常的C++代码文件后, 接下来, 我们只需要用 C++ 编译器编译这个 .cc 文件即可得到我们的可执行文件. 我们首先需要设置一下 Path, 以便找到我们的编译器, 我的 gcc 编译器在 c:\Dev-Cpp\bin 目录下, 由于这里使用的是 C++ 的编程, 所以我使用 g++ 编译命令
C:\proc>set path=C:\Dev-Cpp\bin;%path%

C:\proc>g++ myfirst.cc
myfirst.cc:145:19: sqlca.h: No such file or directory
myfirst.cc: In function int main()': myfirst.cc:167: error: sqlca’ undeclared (first use this function)
myfirst.cc:167: error: (Each undeclared identifier is reported only once for each function it appears in.)

这里出错了,因为 sqlca 这个结构变量没有找到, 实际上是没有找到 sqlca.h 这个头文件, 这个头文件在 %ORACLE_HOME%\precomp\public 目录下, 因此我们通过命令行指定这个位置:
C:\proc>g++ myfirst.cc -I %ORACLE_HOME%\precomp\public
C:\DOCUME1\nyfor\LOCALS1\Temp/ccgLbaaa.o(.text+0x2b9):myfirst.cc: undefined reference to `sqlcxt’
collect2: ld returned 1 exit status
C:\proc>

依然有错误,提示找不到 sqlcxt 的引用, 这个是在链接的时候发生的错误, 链接时需要一个Oracle库文件, 这个库文件位置在 %ORACLE_HOME%\precomp\lib目录中,文件名为: orasql10.lib, Oracle 9i 的文件名为 orasql9.lib
我们指定库文件路径及库文件后重新编译:
C:\proc>g++ myfirst.cc -I %ORACLE_HOME%\precomp\public -L %ORACLE_HOME%\precomp\LIB -l orasql10

C:\proc>dir /b
a.exe
myfirst.cc
myfirst.lis
myfirst.pc

C:\proc>

至此, 我们成功生成了一个可执行文件 a.exe , 这个文件可以用来测试登陆.
3.4、最后测试
当我们生成好了可执行文件后, 我们开始测试,
C:\proc>a
Success.
当我们把 Oracle Listener 停掉后再行测试
C:\proc>a
ORA-12170: TNS:Connect timeout occurred

四、复杂一些的ProC/C++代码
4.1、代码部分
这里我们直接在代码中编写注释, 以阐释Pro
C/C++中的一些基本指令的用法
using namespace std;
#include
#include “sqlca.h” //引入 SQL 通讯区

//以下申明一个宿主变量,这是一个全局的变量,这个变量在C代码中和正常的变量一样使用,
//在嵌入SQL语句中,在变量名前追加冒号:来引用该变量.
EXEC SQL BEGIN DECLARE SECTION;
//只有包含在这之间申明的变量才能用于嵌入SQL代码中
char *uid = “scott/tiger@//192.168.8.35:1522/orcl10”;
EXEC SQL END DECLARE SECTION;

//这里申明了一个函数,后面会做说明,代码实现在最后
void sql_error();
int main()
{
/*
这条指令指示嵌入SQL执行发生错误的时候(不包含NO_DATA_FOUND例外)转向执行的C函数
这个指令还有其它一些语法:
EXEC SQL WHENEVER SQLERROR CONTINUE; 继续执行下一条语句
EXEC SQL WHENEVER SQLERROR STOP; 停止应用程序的执行
EXEC SQL WHENEVER SQLERROR GOTO label; 跳转到指定的标号处运行
初始状态为 CONTINUE
*/
EXEC SQL WHENEVER SQLERROR DO sql_error();

/*
这条指令连接数据库, 常用的连接方法有两种:
一是将用户名和密码存放在两个宿主变量中, 采用 IDENTIFIED BY 子句:
EXEC SQL CONNECT :username IDENTIFIED BY :password ;
一是将用户名和密码按照“username/password”方式(在用户名和密码中用反斜线做分隔的字符串)存放在一个宿主变量中:
EXEC SQL CONNECT :usernameandpwd;
*/
EXEC SQL CONNECT :uid;
cout<<“Database Connected…”<<endl;
EXEC SQL WHENEVER SQLERROR CONTINUE;

//下面这条指令是执行一条本机动态SQL语句,语法和PL/SQL中类似
EXEC SQL EXECUTE IMMEDIATE 'drop table t_emp';

//前面代码当SQL执行发生错误时设置的CONTINUE,我们在下一条语句检查执行结果
if (sqlca.sqlcode == 0)    // sqlcode 为 0 表示执行成功.
{
    cout<<"Table T_EMP droped."<<endl;
}
EXEC SQL WHENEVER SQLERROR DO sql_error();

//以下是嵌入的普通的DDL语句,执行完后我们没有检测 sqlca.sqlcode ,
//是因为前面我们设置了出错时进行捕获并执行 sql_error() 函数
//我们在 sql_error 函数中进行错误处理.
EXEC SQL create table t_emp as select empno,ename,sal from emp where 1 = 0;
cout<<"Table T_EMP created."<<endl;

/*
下面在 main 函数内申明SQL宿主变量,这里的就是局部变量了,只能用在函数 main 内部以下申明了一些数组变量,供我们用来存储查询语句返回的多行数据,在 PL/SQL 中我们也曾使用bulk collect into 子句来批量 fetch 数据;当然我们也可以不定义数组,而定义单个变量,这样每次就只能获取一条数据.
*/
EXEC SQL BEGIN DECLARE SECTION;
int empno[5]; //存放 EMPNO 字段
char ename[5][11];
/*存放 ENAME 字段,因为在 C 中字符串是要以 ‘\0’ 结尾的,所以比我们在DB中定义的10个字节的长度要多定义一个字节, 这点要记住了. */
float sal[5]; //存放 SAL 字段

/下面这个数组定义的用途做个描述:
在 Oracle 的数据值中, 有 NULL 这样一种特殊的值, 而在 C 中并没有这种值, 为了表明从Oracle中获取的值是否为 NULL, 特别定义了一个指示变量(Indicator),在数据返回过程中, 通过设置这个指示器变量,以让应用程序知道这个值是否为 NULL 值,具体用法是将这个指示器变量直接跟在变量后面,数据返回后检查指示器,如果为 -1 表示值为 NULL; 为 0 表示值为非 NULL,这时对应的变量值就是有效的;值大于 0 则表示返回值有被截断.
/
short sal_ind[5];

    int last_cnt;
    int fetch_cnt;

EXEC SQL END DECLARE SECTION;

//以下两句简单测试了一下 NO_DATA_FOUND 这个例外的处理
EXEC SQL WHENEVER NOT FOUND DO puts("Hi, Here is 'NO_DATA_FOUND' exception.");
EXEC SQL select empno,ename,sal into :empno,:ename,:sal :sal_ind from t_emp;
EXEC SQL WHENEVER NOT FOUND continue;

//下面语句我们从表中获取4条数据
EXEC SQL select empno,ename,sal+comm
           into :empno,:ename,:sal :sal_ind
           from emp where rownum <= 4;

/*
这里我们可以检查 sqlca.sqlcode 的值, 我们会发现其值为 1403,
检查 sqlca.sqlerrd[2] 值为 4, 表示查询获取到了 4 条记录.
注意!!! 为何这里sqlcod值为1403 ? 那是因为我们定义的数组容量为5,那么程序就会
帮助我们一次性要获取到5条记录, 而在获取第 5 条记录时,已经找不到数据,
因此就会出现 NO_DATA_FOUND, 而如果你这里修改 rownum <= 4 为 rownum <= 5
那么你会发现 sqlcode 不再是 1403, 而是 0, 再进一步, 如果你修改成 rownum <= 6
那么这里将会发生 TOO_MANY_ROWS 例外,这个例外会被 WHENEVER SQLERROR 捕获到,
因此这里就会转去执行 sql_error() 函数.
*/

cout<<"Empno\tEname\tSal\tIndicator?"<<endl;
for(int i=0; i < sqlca.sqlerrd[2]; i++)
{
    cout<<empno[i]<<'\t'<<ename[i]<<'\t'<<sal[i]<<'\t'<<sal_ind[i]<<endl;
}
/* 上面的数组数据中你会发现指示器有的为 -1 表明对应的 Sal 栏位值为 NULL,
   因为有些 comm 值为 null, 将 sal 直接加上 comm 后,则结果会出现 NULL 值
   对于 NULL 值的资料栏位, 我们会发现对应栏位的值是乱七八糟的, 实际上当数据库资料
   为NULL 值时, 程式根本不会去设置相应变量的值, 而只是设置指示器变量的值. 栏位变量的值
   仍然保留SQL执行之前的值. */

/*下面申明一个静态游标(Cursor), 显式游标的使用依然遵循定义,打开,资料获取,关闭四个动作 */
EXEC SQL DECLARE cur_emp CURSOR FOR
    select empno,ename,rownum * 100+comm from emp where rownum <= 8;

EXEC SQL OPEN cur_emp;
last_cnt = 0;  // last_cnt 用来保存上一次Fetch数据之后总共Fetch出的数据行数
do
{
    //以下语句只针对 sal 字段进行了 NULL 值的判断处理.
    EXEC SQL FETCH cur_emp into :empno,:ename,:sal :sal_ind;

    /*sqlca.sqlerrd[2] 对于 insert,update,delete 表示影响到的行数,
      对于 select 则表示Fetch到的数据的总行数, 尚未 Fetch 的数据不计入 */
    fetch_cnt = sqlca.sqlerrd[2] - last_cnt; // 得到本次 Fetch 的行数
    if (fetch_cnt == 0)

break; //本次未获取到数据则跳出循环

/* 注意上面不能判断 sqlca.sqlcode == 1403 成立就退出, 因为数组可以容纳 5个元素, 而实际上可能获取到 3 个元素, 那么这时sqlca.sqlcode是等于1403的. */

    last_cnt = sqlca.sqlerrd[2];
    cout<<"Data fetched "<<fetch_cnt<<" rows from EMP."<<endl;
    if (sqlca.sqlcode == 1403) last_cnt = 0;

    /*批量插入数据行,注意批量插入时必须指定要插入的行数,用 for 表明.
      假定这里我们去掉 for :fetch_cnt, 那么我们的Pro*C 预编译程序会根据
      插入值变量中的数组元素个数作为批量插入的行数,也即会插入数组中全部的元素
      除非你确定数组中全部的元素都要插入, 请务必指定要插入的行数.
      因为我们这里演示的是获取一批数据插入一批数据,在最后一批时可能只有少量几笔,
      所以这里我们对插入的行数进行了指定*/
    EXEC SQL for :fetch_cnt insert into t_emp values(:empno,:ename,:sal :sal_ind);
    cout<<"Data insert "<<fetch_cnt<<" rows into T_EMP."<<endl;

} while (last_cnt != 0);

/*注意上面循环末尾不能用 while (sqlca.sqlcode != 1403) 因为到达循环末尾时
  sqlca 的内容已经不再表示前面 fetch 的结果了,而是最后的那个 insert 语句的结果,
  我们在代码中做一个记录, 在Fetch出现1403时,将 last_cnt 置为 0, 用于最后退出循环的判断 */
EXEC SQL CLOSE cur_emp;

EXEC SQL COMMIT WORK RELEASE;
cout<<"Database Disconnected....."<<endl;

}
/*这个是错误处理函数的实现部分,在用指令:
EXEC SQL WHENEVER SQLERROR DO sql_error();
后即会在SQL执行出错时转向这个函数(NO_DATA_FOUND除外) */
void sql_error()
{
/*这里必须重置一下WHENEVER SQLERROR的处理方式,否则当错误处理代码中再次
出现SQL错误时将会陷入死循环, 当然如果这个错误处理函数中没有进行任何的SQL
操作, 那么不重置这个也没太大关系 😃
因为这里出错后要回滚事务并断开连接,执行了SQL语句, 所以必须重置WHENEVER
SQLERROR 处理方式, 本函数直接使用 STOP 退出处理, 当然你也可以使用
CONTINUE 处理方式 */
EXEC SQL WHENEVER SQLERROR STOP;
cout<<sqlca.sqlerrm.sqlerrmc<<endl;
EXEC SQL ROLLBACK WORK RELEASE;
exit(1);
/退出程序. 这里不受前面设置的STOP的影响, 如果出错你想退出程序,
你这里就需要加上 exit, 前面设置的 STOP 只在这里执行
ROLLBACK WORK RELEASE 出错时才会退出程序
/
}
4.2、代码编译
这里我编写了一个 makefile 文件, 只为了方便编译测试, 你也可以不用写这个, 直接在输入相应命令
简单的 makefile
all : myfirst.exe

USERID=scott/tiger@//192.168.8.35:1522/orcl10
PROC_OPT=code=cpp cpp_suffix=cc parse=no sqlcheck=full
ORA_INC=-I C:\oracle\product\10.2.0\db_2\precomp\public
ORA_LIB=-L C:\oracle\product\10.2.0\db_2\precomp\LIB -l orasql10

test:all
@echo …RUN…
@myfirst.exe

myfirst.cc:myfirst.pc
@cls
@echo …Precompile…
@proc $? P R O C O P T u s e r i d = {PROC_OPT} userid= PROCOPTuserid={USERID}

myfirst.exe : myfirst.cc
@echo …Compile and link…
@g++ $? -o myfirst.exe ${ORA_INC} ${ORA_LIB}
接着你就可以输入 make test 进行编译并测试了.

4.3、代码测试
编译运行测试前,请先连接数据库建立好 T_EMP 表.
create table t_emp as select empno,ename,sal from emp where 1 = 0;
Note:为何要先建立好 T_EMP 表? 代码中不是有创建这个表的SQL吗?
这里做一个说明:在这里Oracle 10G版本中,Pro*C 预编译程序已经不允许将语法检查 (SQLCHECK) 设置为 NONE, 因此预编译时需要检查 SQL 语法,当然我们可以编写动态 SQL 来避免编译时进行语法检查, 但是这样, 这段测试代码就不得不全部采用动态 SQL , 否则预编译将无法通过, 我们这个演示演示了各种SQL, 为了完成演示,不得不预先建立用到的表, 以保证预编译程序通过. 当然预编译通过之后, 你可以从数据库中删除这个表后再来进行测试.
编译运行测试:
C:\proc>make test
…Precompile…

Pro*C/C++: Release 10.2.0.1.0 - Production on Fri Feb 27 15:20:41 2009

Copyright © 1982, 2005, Oracle. All rights reserved.

System default option values taken from: C:\oracle\product\10.2.0\db_2\precomp\admin\pcscfg.cfg

…Compile and link…
…RUN…
Database Connected…
Table T_EMP droped.
Table T_EMP created.
Hi, Here is ‘NO_DATA_FOUND’ exception.
Empno Ename Sal Indicator?
7369 SMITH 0 -1
7499 ALLEN 1550 0
7521 WARD 1750 0
7566 JONES 3.21363e-039 -1
Data fetched 5 rows from EMP.
Data insert 5 rows into T_EMP.
Data fetched 3 rows from EMP.
Data insert 3 rows into T_EMP.
Database Disconnected…

C:\proc>

  • 13
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沐风—云端行者

喜欢请打赏,感谢您的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值