ssRender Plugin 基础

ssRender Plugin 基础

一.什么是Plugin

​ 插件(Plug-in,又称addin、add-in、addon或add-on,又译外挂)是一种遵循一定规范的应用程序接口编写出来的程序。其只能运行在程序规定的系统平台下(可能同时支持多个平台),而不能脱离指定的平台单独运行。因为插件需要调用原纯净系统提供的函数库或者数据。很多软件都有插件,插件有无数种。例如在IE中,安装相关的插件后,WEB浏览器能够直接调用插件程序,用于处理特定类型的文件。

​ 插件是一种可以添加到现有软件中的程序,它可以改善或更改软件的功能,而无需更改现有软件的源代码。插件是一种可以添加到软件中的组件,它可以改变或增强软件的功能,从而提高软件的性能和可用性。插件可以是第三方软件,也可以是由软件开发者自行开发的软件。

​ 通常是用于对某个现有的架构进行扩展。

二. ssRender Plugin

​ 顾名思义,就是用于ssRender的插件,是ssRender工具端的扩展应用,用户可以根据需求(自定义OpenGL效果、作为通信终端为ssRender工具端提供数据等),进行自定义设计,然后在ssRender工具端加载并应用。

​ ssRender Plugin可以实现的功能:

  • 自定义渲染效果:开发人员可以在Plugin插件程序中自定义OpenGL效果,然后加载到ssRender工具端进行预览和调试。
  • 自定义属性:在Plugin插件程序中可以创建自定义属性值,在ssRender工具端加载插件后,可以在属性栏中修改对应的属性参数,并在ssRender工具端的Preview预览界面中查看Plugin插件实现的效果。
  • 自定义信号:在Plugin插件程序中,定义与ssRender工具端交互的Event信号,在ssRender工具端加载插件后,可以在属性栏中查看,在ssRender工具端的Event 编辑器中进行表达式绑定,信号最终由Plugin插件程序主动触发。

​ 实现对ssRender工具端的功能扩展,丰富了开发人员对于ssRender工具端的使用方式和设计方式,满足了开发人员以及产品的个性定制化需求。

三.ssRender Plugin Demo

​ 下面用vs2015创建一个ssRender Plugin工程

1.创建动态链接库工程

在这里插入图片描述

2.修改工程属性,改为DLL

在这里插入图片描述

3.配置include和lib

①include配置
include\glm
include\freetype
include\ssRender
include\win_include
include\ssRender\plugin
include\ssRender\effects
include\ssRender\graphicItems

这些文件是ssRender工程中的include,直接include就行
在这里插入图片描述

②lib配置

ssRender工程中的lib也如下图这样包一下

在这里插入图片描述

还需要添加如下依赖库:

jpeg.lib
glfw3.lib
ssrCore.lib
freetype.lib
libpng16.lib
ssrPlugin.lib
pthreadVC2.lib

在这里插入图片描述

4.添加预编译宏定义

5.添加源文件

①添加PluginTest.h头文件

在这里插入图片描述

PluginTest.h

#pragma once
#include"PluginBase.h"
#ifdef _PLUGIN_TYPE_RENDER_
class UserPluginDLL : public PluginBase, public Item
#else
class UserPluginDLL : public PluginBase
#endif
{
public:
	UserPluginDLL();
	~UserPluginDLL();

	//实例化SSR对象
	SSRObject* createSSRObject();

	//删除plugin实例化
	void deleteSSRObject(SSRObject* ssrObject);

	//更新数据
	int upDateData(SSRObject* ssrObject);

#ifdef _PLUGIN_TYPE_RENDER_ //该宏定义是为了区分数据类型的plugin和渲染类型的plugin
	SSR_PLUGIN_DECLARATION(DataSource, PLUGIN_RENDER, "Author:**", "2022/**/**") //定义作者
#else
	SSR_PLUGIN_DECLARATION(UserPluginDLL, PLUGIN_DATA, "Author:zhanghao", "2023/03/14")
#endif

		SSR_PLUGIN_PROPERTY_DEF_BEGIN(UserPluginDLL) //定义属性开始

		SSR_PLUGIN_PLUGIN_PROPERTY("DataSource", "MoveX",TYPE_INT,"0",0,600,"PROPTY_TIPS")
		SSR_PLUGIN_PLUGIN_PROPERTY("DataSource", "MoveY",TYPE_INT,"0",0,600,"PROPTY_TIPS")

		//可以多次调用SSR_PLUGIN_PLUGIN_PROPERTY()创建不同的属性
		SSR_PLUGIN_PROPERTY_DEF_END() //定义属性结束

		SSR_PLUGIN_EVENT_DEF(UserPluginDLL) //定义事件开始
		SSR_PLUGIN_EVENT("CustomEventId")
		//可以多次调用SSR_PLUGIN_EVENT()创建不同的自定义EventId
		SSR_PLUGIN_EVENT_DEF_END()//定义事件结束
		int m_runningFlg;
	pthread_t Key_thread;
	int MoveX = 0;
	int MoveY = 0;

};
②添加PluginTest.cpp源文件

在这里插入图片描述

PluginTest.cpp

//#ifndef _PLUGIN_TYPE_RENDER_   
//#define _PLUGIN_TYPE_RENDER_
//#endif

#include"PluginTest.h"
#include<sstream>
#include <math.h>

void* keyStatus(void* arg);
UserPluginDLL::UserPluginDLL()
{
	m_runningFlg = false;

}

UserPluginDLL::~UserPluginDLL()
{
	m_runningFlg = false;
}

SSRObject* UserPluginDLL::createSSRObject()
{
#ifdef _PLUGIN_TYPE_RENDER_
	SSRObject* ssrObj = new Item;
#else
	SSRObject* ssrObj = new SSRObject;
#endif

	ssrObj->m_ssRptr_plugin = this;
	ssrObj->m_ssRtype = OBJECT_TYPE_PLUGIN;

	m_runningFlg = true;
	pthread_create(&Key_thread, NULL, keyStatus, (void *)ssrObj);
	//Common::printlog(LOG_LEV_ERROR, "zzzzzzzzzcreateSSRObjectzzzzzzzz");
	return ssrObj;
}

void UserPluginDLL::deleteSSRObject(SSRObject* ssrObject)
{
	if (ssrObject)
	{
		m_runningFlg = false;
		Common::sleep(150);
		delete ssrObject;
		ssrObject = NULL;
	}
}

SSR_PLUGIN_DEFINITION(UserPluginDLL)//调用此宏定义,创建预定义函数接口

int UserPluginDLL::upDateData(SSRObject* ssrObject)
{
	ProptyList::iterator it = UserPluginDLL::m_listPropties.begin();

	for (; it != UserPluginDLL::m_listPropties.end(); it++)
	{
		Property prop((*it));
		if (prop.name == "MoveX")
		{
			prop.value = Common::int2str(MoveX);
		}
		else if (prop.name == "MoveY")
		{
			prop.value = Common::int2str(MoveY);
		}
		if (m_renderEngine)
		{
			m_renderEngine->getPropertySystem()->setPropertyValue(ssrObject, prop.name, prop.value);
		}
	}
	return 0;
}

void* keyStatus(void* arg)
{
	SSRObject* ssrObject = (SSRObject*)arg;
	UserPluginDLL* dataSource = (UserPluginDLL*)ssrObject->m_ssRptr_plugin;
	if (dataSource)
	{
		int X = 0, Y = 0;
		while (dataSource->isRunning() && dataSource->m_runningFlg)
		{
			//Common::printlog(LOG_LEV_ERROR, "zzzzzzzzzkeyStatustzzzzzzzz");
			if (X > 0 && X < 300 && Y == 0)
			{
				X += 1;
				Y = 0;
			}
			else if (X == 300 && Y == 0)
			{
				X -= 1;
				Y += 1;
			}
			else if (X > 150 && X < 300 && Y < 150)
			{
				X -= 1;
				Y += 1;
			}
			else if (X == 150 && Y == 150)
			{
				X -= 1;
				Y -= 1;
			}
			else if (X > 0 && X < 150 && Y < 150)
			{
				X -= 1;
				Y -= 1;
			}
			else if (X == 0 && Y == 0)
			{
				X += 1;
				Y = 0;
			}
			dataSource->MoveX = X;
			dataSource->MoveY = Y;
			dataSource->upDateData(ssrObject);
			Common::sleep(10);

		}
	}
	return 0;
}

6.编译工程

在这里插入图片描述

在这里插入图片描述

ssRenderPluginTest.dll就是我们编译完生成的插件了。

四.Plugin导入到ssRender

1.打开ssRender,新建一个工程

在这里插入图片描述

2.添加ssRenderPlginTest工程生成的插件

在这里插入图片描述

3.加载Plugin

将添加的ssRenderPluginTest插件拖拽到Page上即可,右边就会显示Plugin的属性。
在这里插入图片描述

五.ssRender Plugin 接口定义

1. SSR_PLUGIN_DEFINITION

①声明

SSR_PLUGIN_DEFINITION(DERIVED_CLASS)

②描述

​ 用于定义自定义Plugin插件。

③参数
NameData typeDescription
DERIVED_CLASS类名继承于PluginBase类
④示例

PluginTest.cpp:

UserPluginDLL:: UserPluginDLL ()
{ 
}
UserPluginDLL::~ UserPluginDLL ()
{ 
}
SSR_PLUGIN_DEFINITION(UserPluginDLL)

2. SSR_PLUGIN_DECLARATION

①声明

SSR_PLUGIN_DECLARATION(DERIVED_CLASS, PLUGIN_TYPE,PLUGIN_AUTHOR,CREATE_DATE)

②描述

​ 用于声明自定义Plugin插件的信息。

③参数
NameData typeDescription
DERIVED_CLASS类名继承于PluginBase类
PLUGIN_TYPE枚举类型定义Plugin插件的类型:
PLUGIN_DATA, 定义插件为数据类型;
PLUGIN_RENDER,定义插件为渲染类型。
PLUGIN_AUTHOR字符串类型Plugin插件的作者名字
例:“AuthorXX”
CREATE_DATE字符串类型Plugin插件的创建时间
例:“20XX/XX/XX”
④示例

PluginTest.h:

class UserPluginDLL : public PluginBase {  
	SSR_PLUGIN_DECLARATION(UserPluginDLL, PLUGIN_DATA, "XX","2022/XX/XX")
}

3. SSR_PLUGIN_PROPERTY_DEF_BEGIN

①声明

SSR_PLUGIN_PROPERTY_DEF_BEGIN(DERIVED_CLASS)

②描述

​ 用于声明Plugin插件的自定义属性的开始。

③参数
NameData typeDescription
DERIVED_CLASS类名继承于PluginBase类
④示例

PluginTest.h:

class UserPluginDLL : public PluginBase {    
    SSR_PLUGIN_PROPERTY_DEF_BEGIN(UserPluginDLL)    
        SSR_PLUGIN_PLUGIN_PROPERTY("group_name", "property_name1", TYPE_INT, "1", 0, 100,
"This is a custom property node!")    
        SSR_PLUGIN_PLUGIN_PROPERTY("group_name", "property_name2", TYPE_INT, "1", 0, 100,
"This is a custom property node!")    
    SSR_PLUGIN_PROPERTY_DEF_END()    
} 

4. SSR_PLUGIN_PLUGIN_PROPERTY

①声明
SSR_PLUGIN_PLUGIN_PROPERTY(GROUP_NAME_TYPE_STR, 
                            PROPTY_NAME_TYPE_STR, 
                            PROPTY_VALUE_TYPE_ENUM,
                            PROPTY_VALUE_TYPE_STR, 
                            PROPTY_VALUE_MIN_TYPE_NUM,
                            PROPTY_VALUE_MAX_TYPE_NUM, 
                            PROPTY_TIPS_TYPE_STR)
②描述

​ 用于声明Plugin插件的自定义属性。

③参数
NameData typeDescription
GROUP_NAME_TYPE_STR字符串类型字符串类型属性分组的组名
PROPTY_NAME_TYPE_STR字符串类型属性节点的名字
PROPTY_VALUE_TYPE_ENUM枚举类型属性节点的值类型:
TYPE_BOOL, 布尔类型;
TYPE_FLOAT, 单精度浮点类型;
TYPE_INT, 整数类型;
TYPE_SHORT, 短整形
TYPE_UNSIGED_SHORT, 无符号短整形
TYPE_STRING, 字符类型;
PROPTY_VALUE_TYPE_STR字符串类型属性节点的值
PROPTY_VALUE_MIN_TYPE_NUMint类型属性节点的最小值
PROPTY_VALUE_MAX_TYPE_NUMint类型属性节点的最大值
PROPTY_TIPS_TYPE_STR字符串类型属性节点的描述注释
④示例

​ 同SSR_PLUGIN_PROPERTY_DEF_BEGIN。

5. SSR_PLUGIN_PROPERTY_DEF_END

①声明

SSR_PLUGIN_PROPERTY_DEF_END()

②描述

​ 用于声明Plugin插件的自定义属性的结束,跟SSR_PLUGIN_PROPERTY_DEF_BEGIN成对出现。

③参数

​ 无。

④示例

​ 同SSR_PLUGIN_PROPERTY_DEF_BEGIN。

6.SSR_PLUGIN_EVENT_DEF

①声明

SSR_PLUGIN_EVENT_DEF(DERIVED_CLASS)

②描述

​ 用于声明Plugin插件的自定义信号的开始。

③参数
NameData typeDescription
DERIVED_CLASS类名继承于PluginBase类
④示例

PluginTest.h:

class UserPluginDLL : public PluginBase {  
    SSR_PLUGIN_EVENT_DEF(UserPluginDLL)  
        SSR_PLUGIN_EVENT("custom_event1")  
        SSR_PLUGIN_EVENT("custom_event2")  
    SSR_PLUGIN_EVENT_END()  
}

7. SSR_PLUGIN_EVENT

①声明

SSR_PLUGIN_EVENT(EVENT)

②描述

​ 用于声明Plugin插件的自定义信号。

③参数
NameData typeDescription
EVENT字符串类型信号的名字,不可以用特殊字符或者空格
④示例

​ 同SSR_PLUGIN_EVENT_DEF。

8. SSR_PLUGIN_EVENT_END

①声明

SSR_PLUGIN_EVENT_END()

②描述

​ 用于声明Plugin插件的自定义信号的结束,跟SSR_PLUGIN_EVENT_DEF成对出现。

③示例

同SSR_PLUGIN_EVENT_DEF。

9.默认需要定义的接口

Plugin源文件中需要默认声明三个接口函数,ssRender引擎会按照程序执行状态自动调用。需要注意的是这三个接口必须声明。

(1)createSSRObject
①声明

SSRObject* createSSRObject()

②描述

​ 此函数会在ssRender工具端创建Plugin节点的时候自动调用,需要在此时添加的自定义内容,可写在此函数当中。

③示例

PluginTest.cpp:

void UserPluginDLL::createSSRObject()  
{  
    #ifdef _PLUGIN_TYPE_RENDER_  
    SSRObject* ssrObj = new UserPluginDLL;   
    #else  
    SSRObject* ssrObj = new SSRObject;  
    #endif  
    ssrObj->m_ssRptr_plugin = this; 
    ssrObj->m_ssRtype = OBJECT_TYPE_PLUGIN;      
    //以上的代码部分除了渲染类型分支的new类名需要修改之外,其他代码变更,拷贝即可  
    //此处可以加其他自定义业务逻辑内容   
    return ssrObj;   
} 
(2)deleteSSRObject
①声明

void DERIVED_CLASS::deleteSSRObject(SSRObject* ssrObject)

②描述

​ 此函数会在ssRender工具端删除Plugin节点,并释放资源的时候自动调用,需要在此时添加的自定义内容,可写在此函数当中。

③参数
NameData typeDescription
SSRObject*ssrObjectSSRObject类型指针表示当前plugin的对象
③示例

PluginTest.cpp:

UserPluginDLL.cpp:  
void UserPluginDLL:: deleteSSRObject (SSRObject* ssrObject)  
{       
    if (ssrObject)  
    {  
        delete ssrObject;  
        ssrObject = NULL;  
    }   
    //以上的代码部分无需变更,拷贝即可 
    //此处可以加其他自定义业务逻辑内容 
}
(3)createUI
①声明

void DERIVED_CLASS:: createUI(SSRObject* ssrObject)

②描述

​ 此函数会在ssRender工具端创建Plugin节点的时候自动调用,时序在createSSRObject 之后,需要在此时添加的自定义渲染内容,可写在此函数当中。

③参数
NameData typeDescription
SSRObject*ssrObjectSSRObject类型指针表示当前plugin的对象
③示例
void UserPluginDLL::createUI(SSRObject* ssrObject)  
{        
    //创建一个根节点Root把createSSRObject()中的m_ssrObject传给Root
    Item* Root = (Item*) ssrObject;    
    if (Root) 
    {
        //创建一个ColorBlock(继承Item类)对象m_Color
        ColorBlock* m_Color = new ColorBlock;
        m_Color->initialize();//初始化节点
        m_Color->setColor(glm::vec4(1.0, 0.8, 0.1, 1.0));//设置颜色
        Root->addChild(m_Color);//把m_Color节点挂在Root节点下才可显示    
    } 
}  

注意:如果想在此函数内自定义渲染内容,要把自定义的节点挂在当前的plugin节点下面才可显示 ,上述代码示例可参考。

10.自定义信号的触发

①声明

EventSystem::create()->onEvent(this, "EventName")

②描述

​ 调用此函数触发Plugin插件的自定义信号,ssRender工具端,会受到对应的信号,插件类型为渲染类型时才可使用。

③参数
NameData typeDescription
thisthis指针继承PluginBase的派生类的this指针
EventName字符串类型利用SSR_PLUGIN_EVENT接口定义的信号
④例子
void UserPluginDLL::update()  
{  
    EventSystem::create()->onEvent(this,"custom_event1"); 
}  

11.日志打印

①声明

Common::printlog(LOG_LEV, “”,…)

②描述

​ 此函数要引用"common.h"文件,会根据设定的log等级和分类,打印指定级别的log,用法同常规的printf打印函数。

③参数
NameData typeDescription
LOG_LEV枚举类型LOG_LEV_ERROR 错误等级日志
LOG_LEV_INFORMATION 信息日志
LOG_LEV_DEBUG 调试日志
LOG_LEV_COREDUMP 内核错误日志
④示例
void UserPluginDLL::update()  
{  
    EventSystem::create()->onEvent(this,"custom_event1"); 
}  

12.插件工作状态的判断

①描述

​ 通过isRunning接口判定当前插件的工作状态,工作中返回true,否则返回false。

②示例
void UserPluginDLL::createSSRObject()  
{  
    #ifdef _PLUGIN_TYPE_RENDER_  
    SSRObject* ssrObj = new UserPluginDLL;   
    #else  
    SSRObject* ssrObj = new SSRObject;  
    #endif  
    ssrObj->m_ssRptr_plugin = this; 
    ssrObj->m_ssRtype = OBJECT_TYPE_PLUGIN;      
    //以上的代码部分除了渲染类型分支的new类名需要修改之外,其他代码变更,拷贝即可  
    //此处可以加其他自定义业务逻辑内容   
    pthread_create(&m_thread, NULL, work_thread, (void *)this);
    m_runningFlg = true; 
    return ssrObj;   
}  
void work_thread(void* arg)  
{  
    SSRObject* ssrObject = (SSRObject*)arg;
    UserPluginDLL* dataSource = (UserPluginDLL*)ssrObject->m_ssRptr_plugin; 
    //UserPluginDll继承于PluginBase    
    if (dataSource->isRunning() && dataSource->m_runningFlg)   
    {  
        ...//通过isRunning接口判定当前插件的工作状态,返回值为true/false   
    };    
} 
void UserPluginDLL:: deleteSSRObject (SSRObject* ssrObject)  
{   
    //注意:删除plugin时要把启动的线程标志置成false以免发生异常  
    if (ssrObject)  
    {  
        m_runningFlg=false; 
        Common::sleep(150); 
        delete ssrObject;  
        ssrObject = NULL;  
    }   
}

六.ssRender Plugin插件编码范例

UserPluginDLL头文件(.h),需要引入头文件PluginBase.h:

#pragma once    
#include "PluginBase.h"    
#ifdef _PLUGIN_TYPE_RENDER_ //该宏定义是为了区分数据类型的plugin和渲染类型的plugin    
class UserPluginDLL : public PluginBase, public Item  
#else    
class UserPluginDLL : public PluginBase  
#endif    
{  
    public:  
    UserPluginDLL();  
    ~UserPluginDLL();  

    //实例化SSR对象    
    SSRObject* createSSRObject();  
    //实例化之后停止stop
    void deleteSSRObject(SSRObject* ssrObject);   

#ifdef _PLUGIN_TYPE_RENDER_ //该宏定义是为了区分数据类型的plugin和渲染类型的plugin    
    void loadMyShader();  
    virtual void updateMyVar();  
    SSR_PLUGIN_DECLARATION(UserPluginDLL, PLUGIN_RENDER,"Author:**","2022/**/**")  
#else    
    SSR_PLUGIN_DECLARATION(UserPluginDLL,PLUGIN_DATA,"Author:**","2022/**/**")  
#endif
        
    SSR_PLUGIN_PROPERTY_DEF_BEGIN(UserPluginDLL)  	            								SSR_PLUGIN_PLUGIN_PROPERTY("GROUP_NAME","PROPTY_NMAE",
                   				TYPE_INT,"1",1,100,"PROPTY_TIPS")  
        //可以多次调用SSR_PLUGIN_PLUGIN_PROPERTY,创建不同的属性。    
    SSR_PLUGIN_PROPERTY_DEF_END()  

    SSR_PLUGIN_EVENT_DEF(UserPluginDLL)  
        SSR_PLUGIN_EVENT("CustomEventId")  
        //可以多次调用SSR_PLUGIN_EVENT()创建不同的自定义EventId   
    SSR_PLUGIN_EVENT_DEF_END()
};

UserPluginDLL源文件(.cpp):

#ifndef _PLUGIN_TYPE_RENDER_      
#define _PLUGIN_TYPE_RENDER_//该宏定义是为了区分数据类型的plugin和渲染类型的plugin      
#endif      

#include "UserPluginDLL.h"      
UserPluginDLL::UserPluginDLL()  
{  
#ifdef _PLUGIN_TYPE_RENDER_  
    loadMyShader();  
#endif  
}  

UserPluginDLL::~UserPluginDLL()  
{  
}   

SSRObject* UserPluginDLL::createSSRObject()  
{  
#ifdef _PLUGIN_TYPE_RENDER_//该宏定义是为了区分数据类型的plugin和渲染类型的plugin      
    SSRObject* ssrObj = new UserPluginDLL;  
    ssrObj->m_ssRptr_plugin = this;  
#else      
    SSRObject* ssrObj = new SSRObject;  
    ssrObj->m_ssRptr_plugin = this;  
#endif      

    ssrObj->m_ssRtype = OBJECT_TYPE_PLUGIN;  

    return ssrObj;  
}  

void UserPluginDLL::deleteSSRObject(SSRObject* ssrObject)  
{  
    if (ssrObject)  
    {  
        delete ssrObject;  
        ssrObject = NULL;  
    }  
}

SSR_PLUGIN_DEFINITION(UserPluginDLL)//调用此宏定义,创建预定义函数接口    

#ifdef _PLUGIN_TYPE_RENDER_     
//自定义Shader :    

//Vertex Shader     
char vShaderStr_my[] =  
	"attribute vec3 ssVertexPosition;            \n"  //【不可修改】变量名固定    
    "attribute vec2 ssVertexUV;                  \n"  //【不可修改】变量名固定    
    "uniform mat4   ssMVP;                       \n"  //【不可修改】变量名固定    
    "varying vec2   ssUV;                        \n"  //【不可修改】变量名固定    
    "void main()                                 \n"  
    "{                                           \n"  
    "gl_Position = ssMVP*vec4(ssVertexPosition, 1.0); \n"  
    "ssUV = ssVertexUV;                          \n"  
    "}\n";  

//Fragment Shader     
char fShaderStr_my[] =  
    "precision mediump float;                  \n"  
    "varying vec2 ssUV;                        \n"  
    "uniform sampler2D ssTextureSampler;       \n" //【不可修改】变量名固定    
    "uniform vec3 myColor;                     \n" //【自定义】变量    
    "void main()                               \n"  
    "{\n"  
    "gl_FragColor = texture2D(ssTextureSampler, ssUV)+vec4(myColor, 0.1);\n"  
    "}\n";  

void UserPluginDLL::loadMyShader()  
{  
    //需要定义Program名字     
    setMyShaderSource("Custom_MyItem", vShaderStr_my, fShaderStr_my);  
}   

void UserPluginDLL::updateMyVar()  
{  
    //修改自定义变量的值     
    setVec3("myColor", 1.0, 0.0, 0.0);  
}  

#endif 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值