单片机裸机移植sqlite3

上一篇文章中提供了vfs支持,此处主要是sqlite3移植所需的线程,信号量支持,sqlite3源码在官方下载,需要很大的内存支持,移植基本原理就是实现sqlite3所需的linux api接口即可,可以通过宏定义去掉一些不用的功能,减少API依赖。

ucos_iii_pthread.c

/*************************************************************************************************************
 * 文件名		:	ucos_iii_pthread.c
 * 功能			:	ucos_iii虚拟的pthread相关接口
 * 作者			:	cp1300@139.com
 * 创建时间		:	2020-09-29
 * 最后修改时间	:	2020-09-29
 * 详细			:	用于实现sqlite3移植所需的信号量,线程操作相关接口
*************************************************************************************************************/
#include "system.h"
#include "ucos_iii_pthread.h"
#include <stdlib.h>
#include "errno-base.h"

#define MUTEXA_INIT_ID		0x12546f24		//用于标示互斥锁是否初始化了

//内存申请接口
static void* (*pthread_malloc)(size_t size) = malloc;		//默认的内存申请接口
static void (*pthread_free)(void *ptr) = free;				//默认的内存释放接口

/*************************************************************************************************************************
* 函数			:	int pthread_mutexattr_init(pthread_mutexattr_t *mattr)
* 功能			:	初始化互斥锁属性
* 参数			:	mattr:互斥锁属性对象
* 返回			:	0:成功;负数:返回错误码
* 依赖			:	ucos iii
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	pshared 属性的 缺省值为 PTHREAD_PROCESS_PRIVATE。 该值表示可以在进程内使用经过初始化的 互斥锁。
					对于 互斥锁属性对象,必须首先通过调用 pthread_mutexattr_destroy(3C) 将其销毁,才能重新初始化该对象
					不会申请内存
*************************************************************************************************************************/
int pthread_mutexattr_init(pthread_mutexattr_t *mattr)
{
	if(mattr == NULL) return -EINVAL;
	mattr->InitId = MUTEXA_INIT_ID;
	return 0;
}


/*************************************************************************************************************************
* 函数			:	int pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type)
* 功能			:	设置互斥锁属性类型
* 参数			:	attr:互斥锁指针;type:设置的属性
* 返回			:	0:成功;负数:返回错误码
* 依赖			:	ucos iii
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	此方法并没有被实现
					PTHREAD_MUTEX_NORMAL 描述: 此类型的 互斥锁不会检测 死锁
					PTHREAD_MUTEX_ERRORCHECK 描述: 此类型的 互斥锁可提供错误检查
					PTHREAD_MUTEX_RECURSIVE 描述: 如果线程在不首先解除锁定 互斥锁的情况下尝试重新锁定该互斥锁,则可成功锁定该互斥锁
					PTHREAD_MUTEX_DEFAULT 描述: 如果尝试以 递归方式锁定此类型的 互斥锁,则会产生不确定的行为
*************************************************************************************************************************/
int pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type)
{
	if(attr == NULL || attr->InitId != MUTEXA_INIT_ID) return -EINVAL;

	uart_printf("pthread_mutexattr_settype(xxx, %d);\r\n", type);
	return 0;
}

/*************************************************************************************************************************
* 函数			:	int pthread_mutexattr_destroy(pthread_mutexattr_t *mattr)
* 功能			:	销毁互斥锁属性对象
* 参数			:	mattr:互斥锁属性对象
* 返回			:	0:成功;负数:返回错误码
* 依赖			:	ucos iii
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	由于之前也没有涉及到内存申请,此处只去掉初始化ID即可
*************************************************************************************************************************/
int pthread_mutexattr_destroy(pthread_mutexattr_t *mattr)
{
	if(mattr == NULL) return -EINVAL;
	mattr->InitId = 0;
	
	return 0;
}

/*************************************************************************************************************************
* 函数			:	int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr)
* 功能			:	互斥锁初始化
* 参数			:	mutex:互斥锁指针;attr:提前初始化的属性信息
* 返回			:	0:成功;负数:返回错误码
* 依赖			:	ucos iii
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	不会申请内存,调用ucos接口初始化互斥锁
*************************************************************************************************************************/
int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t *attr)
{
	OS_ERR err;
	
	if(mutex == NULL || attr == NULL) return -EINVAL;
	OSMutexCreate(&mutex->mutex, "Sqlite3", &err);
	if(err == OS_ERR_NONE) 
	{
		mutex->mattr = (pthread_mutexattr_t *)attr;	//记录互斥锁属性
		mutex->InitId = MUTEXA_INIT_ID;
		return 0;				//初始化成功了
	}
	else
	{
		DEBUG("Sqlite3初始化互斥锁失败,错误:%d\r\n", err);
		return -1;
	}
}

/*************************************************************************************************************************
* 函数			:	int pthread_mutex_destroy(pthread_mutex_t *mutex)
* 功能			:	销毁一个互斥锁
* 参数			:	mutex:互斥锁指针
* 返回			:	0:成功;负数:返回错误码
* 依赖			:	ucos iii
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	调用ucos接口删除互斥锁
*************************************************************************************************************************/
int pthread_mutex_destroy(pthread_mutex_t *mutex)
{
	OS_ERR err;
	
	if(mutex == NULL || mutex->InitId != MUTEXA_INIT_ID) return -EINVAL;
	OSMutexDel(&mutex->mutex, OS_OPT_DEL_ALWAYS, &err);
	if(err == OS_ERR_NONE) 
	{
		mutex->mattr = NULL;	//记录互斥锁属性清除
		mutex->InitId = 0;
		return 0;				//初始化成功了
	}
	else
	{
		DEBUG("Sqlite3删除互斥锁失败,错误:%d\r\n", err);
		return -1;
	}
}

/*************************************************************************************************************************
* 函数			:	int pthread_mutex_lock(pthread_mutex_t *mutex)
* 功能			:	申请互斥锁
* 参数			:	mutex:互斥锁指针
* 返回			:	0:成功;负数:返回错误码
* 依赖			:	ucos iii
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	调用ucos接口申请互斥锁
*************************************************************************************************************************/
int pthread_mutex_lock(pthread_mutex_t *mutex)
{
	OS_ERR err;
	CPU_TS ts;
	
	if(mutex == NULL || mutex->InitId != MUTEXA_INIT_ID) return -EINVAL;
	OSMutexPend(&mutex->mutex, 0, OS_OPT_PEND_BLOCKING, &ts,  &err);			//OS_OPT_PEND_BLOCKING阻塞等待
	if(err == OS_ERR_NONE || err == OS_ERR_MUTEX_OWNER) 
	{
		return 0;
	}
	else
	{
		DEBUG("Sqlite3申请互斥锁失败,错误:%d\r\n", err);
		return -1;
	}
}

/*************************************************************************************************************************
* 函数			:	int pthread_mutex_trylock( pthread_mutex_t *mutex)
* 功能			:	非阻塞申请互斥锁
* 参数			:	mutex:互斥锁指针
* 返回			:	0:成功;负数:返回错误码
* 依赖			:	ucos iii
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	如果mutex参数所指定的互斥锁已经被锁定的话,调用pthread_mutex_trylock函数不会阻塞当前线程,而是立即返回一个值来描述互斥锁的状况
*************************************************************************************************************************/
int pthread_mutex_trylock( pthread_mutex_t *mutex)
{
	OS_ERR err;
	CPU_TS ts;
	
	if(mutex == NULL || mutex->InitId != MUTEXA_INIT_ID) return -EINVAL;
	OSMutexPend(&mutex->mutex, 0, OS_OPT_PEND_NON_BLOCKING, &ts,  &err);			//非阻塞,设置超时无意义
	if(err == OS_ERR_NONE)
	{
		return 0;	//信号量可用
	}
	else if(err == OS_ERR_PEND_WOULD_BLOCK) 	//当前信号量不可用
	{
		return -EBUSY;
	}
	else
	{
		DEBUG("Sqlite3申请互斥锁失败,错误:%d\r\n", err);
		return -1;
	}
}

/*************************************************************************************************************************
* 函数			:	int pthread_mutex_unlock(pthread_mutex_t *mutex)
* 功能			:	互斥锁解锁
* 参数			:	mutex:互斥锁指针
* 返回			:	0:成功;负数:返回错误码
* 依赖			:	ucos iii
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	
*************************************************************************************************************************/
int pthread_mutex_unlock(pthread_mutex_t *mutex)
{
	OS_ERR err;

	if(mutex == NULL || mutex->InitId != MUTEXA_INIT_ID) return -EINVAL;
	OSMutexPost(&mutex->mutex, OS_OPT_POST_NONE, &err);								//释放信号量
	if(err == OS_ERR_NONE || err == OS_ERR_MUTEX_NESTING)
	{
		return 0;	//成功
	}
	else
	{
		DEBUG("Sqlite3释放互斥锁失败,错误:%d\r\n", err);
		return -1;
	}
}

#define PTHREAD_INIT_ID		0x55874187		//任务初始化id,标识任务是否被初始化了

//任务封装
void pthread_task(void *p_arg)
{
	ucosiii_pthread_type *pThread = (ucosiii_pthread_type*)p_arg;
	
	if((pThread != NULL) && (pThread->InitId == PTHREAD_INIT_ID))
	{
		pThread->pTaskReturnValue = pThread->p_task(pThread->p_arg);	//执行任务函数,记录任务返回值
		pThread->isExit = TRUE;											//任务结束
	}
}


		
/*************************************************************************************************************************
* 函数			:	int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, void *(*start_rtn)(void*), void *arg)
* 功能			:	线程创建
* 参数			:	tidp:线程指针;attr:线程属性;start_rtn:线程执行的函数;arg:线程执行函数的参数
* 返回			:	0:成功;负数:返回错误码
* 依赖			:	ucos iii
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	
*************************************************************************************************************************/
int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, void *(*start_rtn)(void*), void *arg)
{
	static u32 cnt;			//每次调用自增一次,用于产生不同的名称
	ucosiii_pthread_type *pThread;
	OS_ERR err;
	
	if(start_rtn == NULL) return -EINVAL;
	pThread = pthread_malloc(sizeof(ucosiii_pthread_type));						//申请内存
	if(pThread == NULL)
	{
		DEBUG("Sqlite3创建线程失败,内存不足!\r\n");
		return -ENOMEM;
	}
	sprintf(pThread->p_name, "Sqlite_%d", cnt++);								//产生随机任务名称
	pThread->p_task = start_rtn;												//任务函数
	pThread->p_arg = arg;														//任务参数
	pThread->prio = SQLITE_PTHREAD_PRIO;										//任务优先级
	pThread->pThis = pThread;													//记录当前任务信息指针,并将任务指针当做任务参数传递到任务中
	pThread->pTaskReturnValue = NULL;											//任务返回值
	pThread->isExit = FALSE;													//任务未结束
	pThread->InitId  = 0;														//任务初始化ID无效
	
	OSTaskCreate(&pThread->TCB,                               					//任务控制块
					(CPU_CHAR *)pThread->p_name,                    			//任务的名字
					pthread_task,                        						//任务函数
					pThread,                                     				//传递参数
					pThread->prio,                            					//任务的优先级        
					pThread->p_stk_base,                    					//任务堆栈基地址
					SQLITE_PTHREAD_STK_SIZE-64,                 				//任务堆栈深度限位,用到这个位置,任务不能再继续使用
					SQLITE_PTHREAD_STK_SIZE,                    				//任务堆栈大小            
					(OS_MSG_QTY)0,                                              //禁止任务消息队列
					(OS_TICK)0,                                                 //默认时间片长度                                                                
					(void  *)0,                                                 //不需要补充用户存储区
					(OS_OPT)OS_OPT_TASK_NONE,                                   //没有任何选项
					&err                                                        //返回的错误码
				);					
		if(err == OS_ERR_NONE) 	//任务创建成功
		{
			*tidp = (u32)pThread;												//返回任务的指针
			pThread->InitId = PTHREAD_INIT_ID;									//任务创建成功
			DEBUG("SQLITE任务%s建立成功\r\n",pThread->p_name);
			return 0;
		}
		else
		{
			pthread_free(pThread);												//释放掉申请的内存
			*tidp = NULL;
			DEBUG("SQLITE任务%s建立失败,错误:%d\r\n",pThread->p_name, err);
			return -1;
		}
}

			
/*************************************************************************************************************************
* 函数			:	int pthread_join(pthread_t thread, void **retval)
* 功能			:	等待一个线程的结束
* 参数			:	tidp:线程指针;retval: 用户定义的指针,用来存储被等待线程的返回值
* 返回			:	0:成功;负数:返回错误码
* 依赖			:	ucos iii
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	
*************************************************************************************************************************/
int pthread_join(pthread_t thread, void **retval)
{
	ucosiii_pthread_type *pThread = (ucosiii_pthread_type*)thread;
	OS_ERR err;
	
	if((pThread != NULL) && (pThread->InitId == PTHREAD_INIT_ID))
	{
		while(pThread->isExit == FALSE)	//等待任务退出
		{
			Sleep(20);
		}
		//任务退出后就要销毁任务了,可是这个返回值是个指针,如果是现成中申请的内存,需要用户自己在返回数据使用完成后释放掉
		*retval = pThread->pTaskReturnValue;	//任务的返回值
		
		//开始销毁任务,释放内存了
		OSTaskDel(&pThread->TCB, &err);
		if(err == OS_ERR_NONE) 	//任务删除成功
		{
			DEBUG("SQLITE任务%s删除成功\r\n",pThread->p_name);
			//释放内存前,先清除掉一些重要数据,防止残存在内存中
			pThread->p_task = NULL;													//任务函数
			pThread->p_arg = 0;														//任务参数
			pThread->prio = 255;													//任务优先级
			pThread->pThis = NULL;													//记录当前任务信息指针,并将任务指针当做任务参数传递到任务中
			pThread->pTaskReturnValue = NULL;										//任务返回值
			pThread->isExit = FALSE;												//任务未结束
			pThread->InitId  = 0;													//任务初始化ID无效
			pthread_free(pThread);													//释放内存
			
			return 0;
		}
		else
		{
			DEBUG("SQLITE任务%s删除失败,错误:%d\r\n",pThread->p_name, err);
			return -1;
		}
	}
	else return -1;
}

/*************************************************************************************************************************
* 函数			:	pid_t getpid(void)
* 功能			:	获取当前程序pid
* 参数			:	无
* 返回			:	PID
* 依赖			:	
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	
*************************************************************************************************************************/
pid_t getpid(void)
{
	uart_printf("getpid()\r\n");
	return 0;
}

/*************************************************************************************************************************
* 函数			:	void sleep(u32 seconds)
* 功能			:	线程延时(单位秒)
* 参数			:	无
* 返回			:	无
* 依赖			:	
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	
*************************************************************************************************************************/
void sleep(u32 seconds)
{
	Sleep(seconds * 1000);
}

/*************************************************************************************************************************
* 函数			:	void usleep(u64 usec)
* 功能			:	线程延时(单位us)
* 参数			:	无
* 返回			:	无
* 依赖			:	
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	
*************************************************************************************************************************/
void usleep(u64 usec)
{
	Sleep(usec / 1000);
}

/*************************************************************************************************************************
* 函数			:	int gettimeofday(struct timeval *tp, void *ignore)
* 功能			:	获取时间戳
* 参数			:	tv:存放当前时间;ignore:忽略
* 返回			:	0:成功;负数:返回错误码
* 依赖			:	
* 作者			:	cp1300@139.com
* 时间			:	2020-09-29
* 最后修改时间 	: 	2020-09-29
* 说明			: 	
*************************************************************************************************************************/
int gettimeofday(struct timeval *tp, void *ignore)
{
	uart_printf("gettimeofday(,)\r\n");
	return 0;
}

//ucos_iii_pthread.h

/*************************************************************************************************************
 * 文件名		:	ucos_iii_pthread.h
 * 功能			:	ucos_iii虚拟的pthread相关接口
 * 作者			:	cp1300@139.com
 * 创建时间		:	2020-09-29
 * 最后修改时间	:	2020-09-29
 * 详细			:	用于实现sqlite3移植所需的信号量,线程操作相关接口
*************************************************************************************************************/
#ifndef _UCOS_III_PTHREAD_H_
#define _UCOS_III_PTHREAD_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include "system.h"
#include "main.h"
#include "ff_vfs.h"

//SQLITE相关配置宏定义
#define SQLITE_MAX_MMAP_SIZE		0		//不使用mmap
#define SQLITE_OMIT_WAL				1		//WAL机制
#define SQLITE_OMIT_LOAD_EXTENSION	1		//不使用动态加载库
#define SQLITE_MALLOC(x)            malloc(x)
#define SQLITE_FREE(x)             	free(x)
#define SQLITE_REALLOC(x,y)         realloc((x),(y))

//互斥锁属性
typedef struct
{
	u32 InitId;
}pthread_mutexattr_t;

//互斥锁控制
typedef struct
{
	OS_MUTEX mutex;
	u32 InitId;						//自己定义的一个,用于标示是否初始化了
	pthread_mutexattr_t *mattr;		//互斥锁属性
} pthread_mutex_t;


//互斥锁类型设置
enum
{
    PTHREAD_MUTEX_NORMAL = 0,
    PTHREAD_MUTEX_RECURSIVE = 1,
    PTHREAD_MUTEX_ERRORCHECK = 2,
    PTHREAD_MUTEX_ERRORCHECK_NP = PTHREAD_MUTEX_ERRORCHECK,
    PTHREAD_MUTEX_RECURSIVE_NP  = PTHREAD_MUTEX_RECURSIVE,
    PTHREAD_MUTEX_DEFAULT = PTHREAD_MUTEX_NORMAL
};


#define SQLITE_PTHREAD_PRIO		SQLITE_TASK_PRIO	//sqlite创建的线程优先级
#define SQLITE_PTHREAD_STK_SIZE	(64*1024)			//任务堆栈大小
//实际任务的信息
typedef struct
{
	void 			*pThis;								//指向当前的指针(将线程执行函数进行封装,便于当线程结束后执行删除线程操作,并获取线程的返回值)
	void			*pTaskReturnValue;					//任务返回值
	bool			isExit;								//任务是否退出了
	OS_TCB 			TCB;								//线程控制块
	char			p_name[24];							//线程名称
	void*    		(*p_task)(void*);					//线程函数-兼容linux
	void          	*p_arg;								//线程参数
	OS_PRIO        	prio;								//线程优先级	
	CPU_STK		   	p_stk_base[SQLITE_PTHREAD_STK_SIZE];//任务堆栈
	u32				InitId;								//任务初始化id
}ucosiii_pthread_type;

//任务句柄,此处实际上是个指向任务块的指针
typedef unsigned int pthread_t;	//存储的是ucosiii_pthread_type的指针
typedef int			 pid_t;

//任务所需的信息-此处用不上,保留为一个整形数据就行
typedef unsigned int pthread_attr_t;

typedef int ssize_t;
#define timespec 	timeval

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



//互斥锁
int pthread_mutexattr_init(pthread_mutexattr_t *mattr);								//初始化互斥锁属性
int pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type);				//设置互斥锁属性类型
int pthread_mutexattr_destroy(pthread_mutexattr_t *mattr);							//销毁互斥锁属性对象
int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t *attr);	//互斥锁初始化
int pthread_mutex_destroy(pthread_mutex_t *mutex);									//销毁一个互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);										//申请互斥锁
int pthread_mutex_trylock( pthread_mutex_t *mutex);									//非阻塞申请互斥锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);									//互斥锁解锁
//线程相关
int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, void *(*start_rtn)(void*), void *arg);	//线程创建	
int pthread_join(pthread_t thread, void **retval);									//等待一个线程的结束
pid_t getpid(void);																	//获取当前程序pid
void sleep(u32 seconds);															//线程延时(单位秒)
void usleep(u64 usec);																//线程延时(单位us)
int gettimeofday(struct timeval *tp, void *ignore);									//获取时间戳


#ifdef __cplusplus
}
#endif
#endif //_UCOS_III_PTHREAD_H_

请先初始化ucos以及文件系统,然后就可以初始化sqlite3,并查询数据,测试代码如下:

{
		sqlite3 *ppDb;
		int sta;
		const char *pSQL = "SELECT *FROM \"tab\" WHERE id=2;";
		char *zErrMsg = 0;

		sqlite3_initialize();
		sta = sqlite3_open("test.db", &ppDb);
		if(sta == SQLITE_OK)
		{
			uart_printf("SQLITE打开数据库成功\r\n");
			
			 //查询数据    
			sta = sqlite3_exec( ppDb, pSQL, callback, NULL, &zErrMsg );  
			if( sta != SQLITE_OK)  
			{    
				uart_printf("%s: %s\r\n",pSQL, sqlite3_errmsg(ppDb));    
				if(zErrMsg)  
				{  
					uart_printf("ErrMsg = %s \r\n", zErrMsg);   
					sqlite3_free(zErrMsg);  
				}   
			}  
			
			
			sqlite3_close(ppDb);
		}
		else
		{
			uart_printf("SQLITE打开数据库错误:%d\r\n", sta);
		}
		
		Sleep(10000000);
	}



int callback(void* data, int ncols, char** values, char** headers)  
{  
    int i;  
    int len =0;  
    int ll=0;  
    for(i=0; i < ncols; i++)  
    {  
        if(strlen(headers[i])>len)  
            len = strlen(headers[i]);  
    }  
      
    for(i=0; i < ncols; i++)   
    {  
        ll = len-strlen(headers[i]);  
        while(ll)  
        {  
            uart_printf(" ");  
            --ll;  
        }  
        uart_printf("%s: %s\r\n", headers[i], values[i]);  
    }  
  
    uart_printf("\r\n");  
    return 0;  
} 

测试结果

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

cp1300

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

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

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

打赏作者

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

抵扣说明:

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

余额充值