将libzdb移植到msvc(Visual Studio 2013)

开篇提示:本文欢迎转载,但必须注明本文出处,例如:
“该文引用自 CruiseYoung的: 将libzdb移植到msvc(Visual Studio 2013)   
http://blog.csdn.net/fksec/article/details/41478947”   
否则说明阁下愿意支付以100元人民币每字计的稿费,敬请留意。

1 资料准备

1.1 官方网站:
http://www.tildeslash.com/libzdb/
代码托管地址:
https://bitbucket.org/tildeslash/libzdb
1.2 开发中代码下载:
https://bitbucket.org/tildeslash/libzdb/downloads
1.3 发布版代码下载(当前发布的版本为3.0):
http://www.tildeslash.com/libzdb/dist/libzdb-3.0.tar.gz
1.4 支持sql server的代码下载(SVN工具下载):
http://code.taobao.org/p/libzdb_dev/src/
http://code.taobao.org/svn/libzdb_dev/trunk
1.5 整理后的代码:
https://github.com/CruiseYoung/libzdb_msvc
https://bitbucket.org/cruiseyoung/libzdb_msvc
注:
branch: static 此分支只保证可以生成静态库(在msvc中使用,尚有"typedef struct T *T;"不合语法问题)。
branch: master此分支生成的静态库可以放心使用;此分支将库源代码一个一个的加入自己的工程,同时可支持5种数据库池(具体看宏打开情况)。
推荐用此分支。该分支与作者代码最为接近。
branch: unity 此分支生成的静态库可以放心使用;此分支使用zdb.c文件统一将库源代码加入自己的工程时,同时只能支持一种数据库池(宏同时只能打开一种)。但若使用分支“master”方式加入自己工程时,同时可支持5种数据库池(具体看宏打开情况)。

2 编译代码准备

2.1 获取Time.re、URL.re
从1.2中下载的代码中提取;
2.2 获取Time.c、URL.c
从1.3中下载的代码中提取;
2.3 代码整合准备
2.3.1 将1.3中的代码解压到libzdb-3.0目录(有的软件可能要解压两次,例如7-zip);
2.3.2 将1.2中的代码解压到tildeslash-libzdb-581e778d1346目录;
2.3.3 将1.4中的代码下载到libzdb_dev目录;
2.3.4 整合:
新建libzdb_msvc目录;
将libzdb-3.0目录中的所有文件复制到libzdb_msvc目录;
将tildeslash-libzdb-581e778d1346目录中所有文件复制并覆盖到libzdb_msvc目录;
将libzdb_dev\src\db\sqlserver整个目录(当然包含了目录中的文件)libzdb_msvc\src\db目录。

3 该库依赖其它库

3.1 ws2_32.lib;
3.2 pthreads-win32 http://www.sourceware.org/pthreads-win32/
3.3 MySql Connector/C http://dev.mysql.com/downloads/connector/c/
3.4 Oracle、PostgreSql、sqlite我个人没有研究,网址就不给了哈;
3.5 MS Sql Server(odbc): odbc32.lib;odbccp32.lib;
注1:建议pthreads-win32用动态库, 静态库需要对pthreads-win32进行初始化,见我的另一篇文章
pthread-win32库编译及使用方法注意事项 - CruiseYoung的专栏
http://blog.csdn.net/fksec/article/details/41517953
注2pthreads-win32和MySql Connector/C不能同时用静态库,线程函数有冲突,我当时是这样。但将libzdb编译成静态库是,好像都可以用静态库,所以关于这点,请大家一起来验证吧。
注3:支持的5种数据库(MySql、Oracle、PostgreSql、sqlite、MS Sql Server), 如用zdb.c嵌入工程时同时只能支持一种,其他方式可同时支持这五种。

4 建msvc工程(这个不需要我详细写了吧)

4.1 libzdb工程,该工程是将libzdb编译成静态库的工程
4.2 libzdb_unity工程,该工程是将libzdb源代码嵌入到自己的工程中的示例,和资源(zdb.c 以及工程配置)
4.3 TestException、TestPool、TestSelect、TestUnit四个工程对应官方提供的4个测试文件。作用看名字就猜得到哈。
详细见代码。
4.4 添加头文件的访问路径:
4.4.1 “libzdb”头文件访问路径,
例如:..\..\src;..\..\src\net;..\..\src\util;..\..\src\db;..\..\src\exceptions;
..\..; // 保证"src/net/URL.re"、"src/system/Time.re"能够访问到;
..\..\src;
..\..\src\net;
..\..\src\util;
..\..\src\db;
..\..\src\exceptions;
// 以上路径是保证各自目录下的头文件能够访问到,有人会问为什么不配置src/system的路径,那是因为程序中关于system目录下的头文件的引用都有system打头,所以不需要了
:以上路径是相对于各自工程的相对路径,请根据实际情况配置。
4.4.2 其它库头文件访问路径,这个我就不讲了哈,请根据实际情况配置。我的工程以MySql为例来讲解;
4.5 工程公共预定义(各预定宏作用请自己搜索,此处我就不详讲了哈):
XKEYCHECK_H;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;

5 静态库成功编译的修改

:修改之前请先阅读,以下博文:
C代码从GCC到MSVC的移植
http://blog.csdn.net/jinzhuojun/article/details/7881049
5.1 将所有libzdb库中引用自定义头文件时采用
#include <...>
统一改为
#include "..."
涉及文件:
\src\exceptions\assert.h
\src\exceptions\AssertException.h
\src\exceptions\MemoryException.h
\src\exceptions\SQLException.h
5.2 “__attribute__”不能识别
处理在msvc上 标识符“__attribute__”不能识别的问题,该关键字是GCC下扩展的特有关键字,不过clang下好像也能识别。
由于在以下文件:
\src\system\System.h
\src\util\Str.h
\src\exceptions\Exception.h
\src\db\Connection.h
\src\net\URL.h
\src\util\StringBuffer.h
前3个文件在文件\src\Config.h引入“标识符‘__attribute__’”,其中前2个文件通过
#include "Str.h"
#include "system/System.h"
引入 
第3个文件通过
#include "SQLException.h"
间接引入“标识符‘__attribute__’”
所以有关“__attribute__”的宏应该出现这3个文件之前。
因此,\src\Config.h中的以下语句
/* ---------------------------------------------------------- Build macros */



/* Mask out GCC __attribute__ extension for non- GCC/llvm-clang compilers. */
#if (! (defined(__GNUC__) || defined(__clang__)))
#define __attribute__(x)
#endif
应提前到引入所有libzdb库自定义头文件的前面。
此处为了统一处理,我在\src\Config.h文件同级目录下创建\src\Macros.h文件,
将上述宏移到\src\Macros.h文件中。
并在\src\Config.h文件
#include "xconfig.h"
语句后添加
#include "Macros.h"
由于\src\exceptions\assert.h中用到了__attribute__,因此我们在其实现文件\src\exceptions\assert.c中的语句
#include "assert.h"
之前添加
#include "Macros.h"
如此添加方式,是与库引用头文件的风格保持一致。
5.3 “inline”不能识别
处理在msvc上 标识符“inline”不能识别的问题
原因(Inline assembly is not supported on the ARM and x64 processors.)见:
Inline Assembler
http://msdn.microsoft.com/en-us/library/4ks26t93.aspx
预定义的宏
http://msdn.microsoft.com/zh-cn/library/b0084kay(v=vs.120).aspx
在\src\Macros.h文件中定义以下宏
#if defined(_MSC_VER) && !defined(inline)
#define inline __inline
#endif
5.4 __func__”不能识别
处理在msvc上 标识符“__func__”不能识别的问题
在\src\Macros.h文件中定义以下宏
#if defined(_MSC_VER) && !defined(__func__)
#define __func__ __FUNCTION__
#endif
5.5 未声明的标识符“int64_t”、“INT32_MIN”、“INT32_MAX”
解决 未声明的标识符“int64_t”、“INT32_MIN”、“INT32_MAX”: 按如下方法添加头文件
#ifdef _WIN32
#include <stdint.h>
#include <inttypes.h>
#endif
5.6 解决函数“snprintf”未定义: 
在\src\Macros.h文件中定义以下宏
#if defined(_MSC_VER) && !defined(snprintf)
#define snprintf _snprintf
#endif
5.7 解决函数“gmtime_r”未定义
该函数一定要注意,找msvc中,与其功能相同的函数为gmtime_s, 但其参数需要交换
在\src\Macros.h文件中所以定义以下宏
#if defined(_MSC_VER) && !defined(gmtime_r)
#include <time.h>
#define gmtime_r(a, b) gmtime_s((b), (a))
#endif
5.8 解决类型“suseconds_t”未声明的标识符: 
在\src\Macros.h文件中定义以下宏
#if defined(_MSC_VER) && !defined(suseconds_t)
#define suseconds_t long
#endif
5.9 解决“select”、struct“timeval”未定义
在\src\Macros.h文件中定义以下宏
#if defined(_MSC_VER)
#include <WinSock2.h>               // struct timeval
//#pragma comment(lib, "ws2_32.lib")  // select(0, 0, 0, 0, &t);
#endif

#ifndef _WIN32
#include <sys/time.h>
#include <sys/select.h>
#endif
5.10 “gettimeofday”未定义
解决函数“gettimeofday”未定义: 该函数在windows上没有与其对应的函数,需要自己实现。
int gettimeofday(struct timeval *tv, struct timezone *tz);
因此,我们要定义struct timezone.
将5.9和5.10合并处理,在\src\Macros.h文件中添加:
#if defined(_MSC_VER)
#include <WinSock2.h>               /* struct timeval */
//#pragma comment(lib, "ws2_32.lib")  /* release need select(0, 0, 0, 0, &t);  */

//#include <time.h>                   /* struct timezone */
struct timezone
{
    int  tz_minuteswest; /* minutes west of Greenwich */
    int  tz_dsttime;     /* type of DST correction */
};

int gettimeofday(struct timeval *tv, struct timezone *tz);
#endif
我们在src\system目录下创建其实现文件gettimeofday.c,并添加以下代码
#include "Macros.h"

#if defined(_MSC_VER) && !defined(gettimeofday)

#include <time.h>       /* _timezone, _daylight */
#include <WinSock2.h>   /* timeval */
#include <windows.h>    /* I've ommited this line. */

#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
#define DELTA_EPOCH_IN_MICROSECS  11644473600000000Ui64
#else
#define DELTA_EPOCH_IN_MICROSECS  11644473600000000ULL
#endif

//struct timezone
//{
//    int  tz_minuteswest; /* minutes west of Greenwich */
//    int  tz_dsttime;     /* type of DST correction */
//};

int gettimeofday(struct timeval *tv, struct timezone *tz)
{
    // Define a structure to receive the current Windows filetime
    FILETIME ft;

    // Initialize the present time to 0 and the timezone to UTC
    unsigned __int64 tmpres = 0;
    static int tzflag = 0;

    if (NULL != tv)
    {
        GetSystemTimeAsFileTime(&ft);

        // The GetSystemTimeAsFileTime returns the number of 100 nanosecond
        // intervals since Jan 1, 1601 in a structure. Copy the high bits t
        // the 64 bit tmpres, shift it left by 32 then or in the low 32 bit
        tmpres |= ft.dwHighDateTime;
        tmpres <<= 32;
        tmpres |= ft.dwLowDateTime;

        // Convert to microseconds by dividing by 10
        tmpres /= 10;
        // The Unix epoch starts on Jan 1 1970.  Need to subtract the diffe
        // in seconds from Jan 1 1601.
        tmpres -= DELTA_EPOCH_IN_MICROSECS;

        // Finally change microseconds to seconds and place in the seconds 
        // The modulus picks up the microseconds.
        tv->tv_sec = (long)(tmpres / 1000000UL);
        tv->tv_usec = (long)(tmpres % 1000000UL);
    }

    if (NULL != tz)
    {
        if (!tzflag)
        {
            _tzset();
            tzflag++;
        }
        tz->tz_minuteswest = _timezone / 60;
        tz->tz_dsttime = _daylight;
    }

    return 0;
}

#endif
5.11 头文件找不到
关于在win32平台上,非win32平台头文件找不到的问题,我们采用分平台引用,即但当前系统为win32平台时引用其在win32平台对应的头文件。
涉及:
5.11.1 <sys/time.h>、<sys/select.h>
做如下修改
\src\system\Time.c中,修改为
#ifndef _WIN32
#include <sys/time.h>
#include <sys/select.h>
#endif
\src\system\Time.re中,修改为
#ifndef _WIN32
#include <sys/time.h>
#include <sys/select.h>
#endif
以上两文件中对应的win32头文件,我们统一放到\src\Macros.h文件中,即
#if defined(_MSC_VER)
#include <time.h>
#include <WinSock2.h>
//#pragma comment(lib, "ws2_32.lib") // select(0, 0, 0, 0, &t);
#include <windows.h> //I've ommited this line.
#endif
以上语句在5.10中已经合并处理,不单独添加。
5.11.2 <strings.h>
\src\util\Str.c中,修改为
#ifndef _WIN32
#include <strings.h>
#endif // !_WIN32
5.12 不同平台pthread_t的定义不同引入问题的修改
由于pthreads-win32下的 pthread_t与posix的pthread_t的定义不同:
posix下pthread_t的定义是:
typedef unsigned long int pthread_t;
而pthread_win32定义是:
/*
 * Generic handle type - intended to extend uniqueness beyond
 * that available with a simple pointer. It should scale for either
 * IA-32 or IA-64.
 */
typedef struct {
    void * p;                   /* Pointer to actual object */
    unsigned int x;             /* Extra information - reuse count etc */
} ptw32_handle_t;

typedef ptw32_handle_t pthread_t;
见:d:\comm\pthreads\include\pthread.h(根据实际情况查看)
所以:src\db\ConnectionPool.c文件中
语句
stopSweep = (P->doSweep && P->reaper);
应分平台处理,即将其改为:
#ifndef _WIN32
    stopSweep = (P->doSweep && P->reaper);
#else
    stopSweep = (P->doSweep && P->reaper.p);
#endif // !_WIN32
请参考博文:pthread_win32下的 pthread_t与posix的pthread_t的不同
http://www.cnblogs.com/ayanmw/archive/2012/08/07/2626661.html
5.13 Time.c、URL.c文件的修改
由于\src\system\Time.c文件和\src\net\URL.c与对应的文件\src\system\Time.re \src\net\URL.re的代码行号有关,所以要做对应修改,每次编译前都要检查\src\system\Time.re \src\net\URL.re的代码是否有有关行号变化的情况。有变化则需作对应修改。
5.14 release版本需引入的头文件
在实际编译过程中发现,在release时在某些文件中还需要引入其它头文件,
涉及:
\src\system\Time.c
\src\util\StringBuffer.c
中,添加
#include "AssertException.h" // release need
5.14 解决VERSION未定义问题
在\src\Config.h文件中
/**
 * Version, copyright and contact information
 */
#define ABOUT   "libzdb/" VERSION " Copyright (C) Tildeslash Ltd. " LIBZDB_URL
语句前添加:
/* Version number of package */
#define VERSION "3.1.0"
5.15 添加控制数据库开关
在\src\Config.h文件中
在引用头文件语句结束后,添加,每次只能打开一个宏(由于#include "xconfig.h"文件中把以下宏给undef了,所以在工程属性中配置无效)。
#define HAVE_LIBMYSQLCLIENT
//#define HAVE_LIBPQ
//#define HAVE_LIBSQLITE3
//#define HAVE_ORACLE
//#define HAVE_SQLSERVER
5.16 消除宏"ERROR"重定义警告 (不强制)
在所有宏定义“ERROR”之前取消“ERROR”定义,即语句#define ERROR(e) do {*error = Str_dup(e); goto error;} while (0)之前添加语句#undef ERROR
出现在:
src\db\mysql\MysqlConnection.c
src\db\oracle\OracleConnection.c
src\db\postgresql\PostgresqlConnection.c
5.17 测试文件中作如下修改
5.17.1 \test\pool.c中,修改为
#ifndef _WIN32
#include <unistd.h>
#endif
并添加
#if defined(_MSC_VER) && !defined(sleep)
#define sleep Sleep
#endif
5.17.2 \test\unit.c中
添加
#ifdef _WIN32
#include <io.h> //open
#endif

6 unity编译方式

为了能直接将libzdb代码引入自己的工程请作以下修改:
6.1 解决结构体对象定义“typedef struct T *T;”类问题(“-->”符号意为“更改为”)
6.1.1 Vector
#define T Vector_T后的struct T {};-->struct Vector_S {};
#define T Vector_T 后的typedef struct T *T;-->typedef struct Vector_S *T;
6.1.2 StringBuffer
#define T StringBuffer_T后的struct T {};-->struct StringBuffer_S {};
#define T StringBuffer_T后的typedef struct T *T;-->typedef struct StringBuffer_S *T;
6.1.3 Exception
Exception.h  中
#define T Exception_T后的
typedef struct T {
        const char *name;
} T;
-->
struct Exception_S {
        const char *name;
} ;
然后将所有Exception_T改为Exception_S,
6.1.4 ConnectionDelegate
// #define T ConnectionDelegate_T后的struct T {};-->struct ConnectionDelegate_S {};
#define T ConnectionDelegate_T后的typedef struct T *T;-->typedef struct ConnectionDelegate_S *T;
6.1.5 PreparedStatementDelegate
#define T PreparedStatementDelegate_T后的struct T {};-->struct PreparedStatementDelegate_S {};
#define T PreparedStatementDelegate_T后的typedef struct T *T;-->typedef struct PreparedStatementDelegate_S *T;
6.1.6 ResultSetDelegate
#define T ResultSetDelegate_T后的struct T {};-->struct ResultSetDelegate_S {};
#define T ResultSetDelegate_T后的typedef struct T *T;-->typedef struct ResultSetDelegate_S *T;
6.1.7 Cop
typedef struct Cop_T { } *Cop_T; -->typedef struct Cop_S { } *Cop_T;
即将所有的struct Cop_T改为struct Cop_S
6.1.8 Pop
typedef struct Pop_T { } *Pop_T; -->typedef struct Pop_S { } *Pop_T;
即将所有的struct Pop_T改为struct Pop_S
6.1.9 Rop
typedef struct Rop_T { } *Rop_T; -->typedef struct Rop_S { } *Rop_T;
即将所有的struct Rop_T改为struct Rop_S
6.1.10 column
typedef struct column_t{ } *column_t; -->typedef struct column_s { } *column_t;
即将所有的struct column_t改为struct column_s
6.1.11 param
typedef struct param_t { } *param_t; --> typedef struct param_s { } *param_t;
即将所有的struct param_t改为struct param_s
6.2 风格统一
通过对源代码的阅读,发现_S(_s)都是结构体的定义,_T(_t)代表指针
6.2.1 Exception_Frame
typedef struct Exception_Frame Exception_Frame;
struct Exception_Frame {};
-->
typedef struct Exception_Frame_S Exception_Frame;
struct Exception_Frame_S {};
将所有的struct Exception_Frame-->struct Exception_Frame_S
6.2.2 UnlockNotification
typedef struct UnlockNotification {
        int fired;
        Sem_T cond;
        Mutex_T mutex;
} UnlockNotification_T;
-->
struct UnlockNotification_S {
        int fired;
        Sem_T cond;
        Mutex_T mutex;
};
将所有的UnlockNotification_T-->UnlockNotification_S
6.3 “struct”类型重定义 (不强制)
注:原因可以参见以下博文:
visual studio 2008 提示 “函数xxx 已有主体” - lengshuangfei的专栏
http://blog.csdn.net/lengshuangfei/article/details/6027792
6.3.1 struct param_s
src\net\URL.re
src\net\URL.c
中struct param_s和各数据库xxxPreparedStatement.c实现中的struct param_s定义重定义了,为了方便修改,我们将以上两个文件中的struct param_s定义更改为
typedef struct param_url_s {
        char *name;
        char *value;
        struct param_url_s *next;
} *param_url_t;
并将以上两文件中的所有param_s、param_t作相应更改。
6.4 函数“xxx”已有主体 (不强制)
注:原因可以参见以下博文:
visual studio 2008 提示 “函数xxx 已有主体” - lengshuangfei的专栏
http://blog.csdn.net/lengshuangfei/article/details/6027792
6.4.1 void _ensureCapacity()
src\db\mysql\MysqlResultSet.c
src\util\Vector.c
原因同6.3.1,将src\util\Vector.c中所有的_ensureCapacity()更改为_ensureCapacity_Vector;
6.4.2 _prepare() 
src\util\StringBuffer.c
和各数据库xxxConnection.c实现文件
原因同6.3.1,将src\util\StringBuffer.c中所有的_prepare()更改为_prepare_StringBuffer;
6.4.3 _ctor() 
src\utilsrc\net\StringBuffer.c
src\net\URL.re
原因同6.3.1,将src\util\StringBuffer.c中所有的_ctor()更改为_ctor_StringBuffer;

7 关于libzdb_unity工程的说明

7.1 修改zdb.h原因:为了方便直接引用,并不需要在工程中libzdb其它头文件的引用路径(只在statci库中适用),所以将其内容改为如下内容
/* Mask out __attribute__ extension for non- GCC/llvm-clang compilers. */
#if (! (defined(__GNUC__) || defined(__clang__)))
#define __attribute__(x)
#endif

#if defined(_MSC_VER) && !defined(inline)
#define inline __inline
#endif

#if defined(_MSC_VER) && !defined(__func__)
#define __func__ __FUNCTION__
#endif

/* libzdb API interfaces */
#include "exceptions/SQLException.h"
#include "net/URL.h"
#include "db/ResultSet.h"
#include "db/PreparedStatement.h"
#include "db/Connection.h"
#include "db/ConnectionPool.h"
7.2 在zdb.h同级目录添加文件zdb.c文件,其内容是对所有实现文件的链接,避免一个文件一个文件的添加 (此种方式,必须进行步骤6.3、6.4)。内容如下(顺序是msvc的编译顺序)
#include "./util/Vector.c"
#include "./util/StringBuffer.c"
#include "./util/Str.c"

#include "./system/Time.c"
#include "./system/System.c"
#include "./system/Mem.c"
#ifdef _MSC_VER
#include "./system/gettimeofday.c"
#endif

#include "./net/URL.c"

#include "./exceptions/Exception.c"
#include "./exceptions/assert.c"

#include "./db/ResultSet.c"
#include "./db/PreparedStatement.c"

#ifdef HAVE_LIBMYSQLCLIENT
#include "./db/mysql/MysqlResultSet.c"
#include "./db/mysql/MysqlPreparedStatement.c"
#include "./db/mysql/MysqlConnection.c"
#endif

#ifdef HAVE_ORACLE
#include "./db/oracle/OracleResultSet.c"
#include "./db/oracle/OraclePreparedStatement.c"
#include "./db/oracle/OracleConnection.c"
#endif

#ifdef HAVE_LIBPQ
#include "./db/postgresql/PostgresqlResultSet.c"
#include "./db/postgresql/PostgresqlPreparedStatement.c"
#include "./db/postgresql/PostgresqlConnection.c"
#endif

#ifdef HAVE_LIBSQLITE3
#include "./db/sqlite/SQLiteResultSet.c"
#include "./db/sqlite/SQLitePreparedStatement.c"
#include "./db/sqlite/SQLiteConnection.c"
#endif

#ifdef HAVE_SQLSERVER
#include "./db/sqlserver/SqlServerResultSet.c"
#include "./db/sqlserver/SqlServerPreparedStatement.c"
#include "./db/sqlserver/SqlServerConnection.c"
#endif

#include "./db/ConnectionPool.c"
#include "./db/Connection.c"
7.3 要将libzdb嵌入自己的工程,只需用将zdb.c添加到自己的工程中,头文件只需引用zdb.h文件即可;
否则,就将源文件(.c或.re)一个一个地加入工程, 此时不需要执行步骤6.3、6.4

8 头文件提取

8.1 如果我们将libzdb编译成静态库(动态库目前没有提供,可以自己加),需要提供头文件,所以需要提供zdb.h文件及其涉及到的其它头文件,请从libzdb_msvc/src目录中按目录层级拷贝以下文件到libzdb_msvc/include,列表如下
exceptions/SQLException.h
exceptions/Exception.h//(由exceptions/SQLException.h引入)
net/URL.h
db/ResultSet.h
db/PreparedStatement.h
db/Connection.h
db/ConnectionPool.h
8.2 修改相关头文件
在以下文件
db/ResultSet.h
db/PreparedStatement.h
db/Connection.h
中语句
//<< Protected methods
之前添加
#if 0
在语句
//>> End Protected methods
之后添加
#endif
见libzdb_msvc\include
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值