shiboken封装qt供python使用

14 篇文章 0 订阅
2 篇文章 0 订阅

上一篇写了封装C++给python使用。这篇是封装qt给python使用,突然不知道怎么描述了,就简单的说一下思路上代码。可参考上一个。
同样是分为三部分,封装一个dll库,不管你用什么vs,qt,或者cmake,什么都好,只要生成能用的库就行.这部分注意包含qt所需要的文件夹和库。
第二部分也就是根据利用shiboken生成器生成pythonc++文件。具体参数参看自己需求,但是编译不过的话多注意有没有这个东西和是否包含了。具体参数写法看代码或者参考上一篇。
第三部分,就是根据第一和第二部分生成的库和源码来制作一个Py库。


------------------------------------------MainWindow.cpp----------------------------------------------
#include <QtGui>
#include <QSplitter>
#include <QVBoxLayout>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QGraphicsScene>
#include <QGraphicsView>
#include "MainWindow.h"

MainWindow::MainWindow(QWidget * parent):QMainWindow(parent) {

    QSplitter * splitter = new QSplitter;

    setCentralWidget(splitter);

    QWidget * editorContent = new QWidget;

    splitter->addWidget(editorContent);

    QVBoxLayout * layout = new QVBoxLayout;

    editorContent->setLayout(layout);

    editor = new QPlainTextEdit;

    layout->addWidget(editor);

    pb_commit = new QPushButton(tr("Commit"));

    connect(pb_commit, SIGNAL(clicked()), 

            this, SLOT(runPythonCode()));

    layout->addWidget(pb_commit);

    scene = new QGraphicsScene(this);

    viewer = new QGraphicsView;

    viewer->setScene(scene);

    splitter->addWidget(viewer);

    splitter->setSizes(QList<int>() << 400 << 600);

}

MainWindow::~MainWindow() {;}

void MainWindow::runPythonCode() {

    emit runSignal();
    emit runSignalInt(12);
    emit runPythonCode(editor->toPlainText());
    setWindowTitle(editor->toPlainText());

}
---------------------------------------MainWindow.h------------------------------------------------
#ifndef MainWindow_H
#define MainWindow_H

#include <QMainWindow>
#include "macros.h"

class QPushButton;

class QGraphicsView;

class QGraphicsScene;

class QPlainTextEdit;

 

class  BINDINGS_API MainWindow : public  QMainWindow {
     Q_OBJECT

public:

     MainWindow(QWidget * parent = 0L);

    virtual  ~MainWindow();

//signals:
Q_SIGNALS:
    void  runSignal();
    void  runSignalInt(int);
    void  runPythonCode(QString);

//private slots:
public slots://Q_SLOTS:
    void runPythonCode();
    

 

public:

    QGraphicsView * viewer;

    QGraphicsScene * scene;

    QPlainTextEdit * editor;

    QPushButton * pb_commit;

};
#endif
---------------------------------------macros.h-----------------------------------------------
#ifndef MACROS_H
#define MACROS_H

#if defined _WIN32 || defined __CYGWIN__
    // Export symbols when creating .dll and .lib, and import them when using .lib.
    #if BINDINGS_BUILD
        #define BINDINGS_API __declspec(dllexport)
    #else
        #define BINDINGS_API __declspec(dllimport)
    #endif
    // Disable warnings about exporting STL types being a bad idea. Don't use this in production
    // code.
    #pragma warning( disable : 4251 )
#else
    #define BINDINGS_API
#endif

#endif // MACROS_H
--------------------------------------bindings.h-------------------------------------------------------


#ifndef BINDINGS_H
#define BINDINGS_H

#include "MainWindow.h"

#endif // BINDINGS_H

------------------------------------bindings.xml-----------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<typesystem package="Universe">

    <primitive-type name="Qstring"/>
    <load-typesystem name="typesystem_widgets.xml" generate="no"/>
    <load-typesystem name="typesystem_core.xml" generate="no"/>
    <load-typesystem name="typesystem_gui.xml" generate="no"/>

 
    <object-type name="MainWindow">
    </object-type>

</typesystem>
-------------------------------------CMakeLists.txt----------------------------------------

#最低版本要求
cmake_minimum_required(VERSION 3.1)
cmake_policy(VERSION 3.1)

# Enable policy to not use RPATH settings for install_name on macOS.
if(POLICY CMP0071)
  cmake_policy(SET CMP0071 NEW)
endif()

# Consider changing the project name to something relevant for you.
project(SampleBinding)
#项目名称

# ================================ General configuration ======================================

# Set CPP standard to C++11 minimum.
set(CMAKE_CXX_STANDARD 11)

set(CMAKE_PREFIX_PATH $ENV{QTDIR5122})
# Find required Qt packages.
find_package(Qt5 5.12 REQUIRED COMPONENTS Core Gui Widgets)
#查找qt包

# The sample library for which we will create bindings. You can change the name to something
# relevant for your project.
set(sample_library "libuniverse")

#设置库的名称已备下面使用

# The name of the generated bindings module (as imported in Python). You can change the name
# to something relevant for your project.
set(bindings_library "Universe")

# The header file with all the types and functions for which bindings will be generated.
# Usually it simply includes other headers of the library you are creating bindings for.
set(wrapped_header ${CMAKE_SOURCE_DIR}/bindings.h)

#设置需要绑定的头文件

# The typesystem xml file which defines the relationships between the C++ types / functions
# and the corresponding Python equivalents.
set(typesystem_file ${CMAKE_SOURCE_DIR}/bindings.xml)
#设置xml文件路径

# Specify which C++ files will be generated by shiboken. This includes the module wrapper
# and a '.cpp' file per C++ type. These are needed for generating the module shared
# library.
set(generated_sources
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/universe_module_wrapper.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/MainWindow_wrapper.cpp)

#设置资源enerated_sources


# ================================== Shiboken detection ======================================
# Use provided python interpreter if given.
if(NOT python_interpreter)
    find_program(python_interpreter "python") #设置python的exe#####################################################
endif()
message(STATUS "Using python interpreter: ${python_interpreter}")

# Macro to get various pyside / python include / link flags and paths.
# Uses the not entirely supported utils/pyside2_config.py file.
macro(pyside2_config option output_var)
    if(${ARGC} GREATER 2)
        set(is_list ${ARGV2})
    else()
        set(is_list "")
    endif()

    execute_process(
      COMMAND ${python_interpreter} "${CMAKE_SOURCE_DIR}/../utils/pyside2_config.py"
              ${option}
      OUTPUT_VARIABLE ${output_var}
      OUTPUT_STRIP_TRAILING_WHITESPACE)

    if ("${${output_var}}" STREQUAL "")
        message(FATAL_ERROR "Error: Calling pyside2_config.py ${option} returned no output.")
    endif()
    if(is_list)
        string (REPLACE " " ";" ${output_var} "${${output_var}}")
    endif()
endmacro()

# Query for the shiboken generator path, Python path, include paths and linker flags.
pyside2_config(--shiboken2-module-path shiboken2_module_path)
pyside2_config(--shiboken2-generator-path shiboken2_generator_path)
pyside2_config(--python-include-path python_include_dir)
pyside2_config(--shiboken2-generator-include-path shiboken_include_dir 1)
pyside2_config(--shiboken2-module-shared-libraries-cmake shiboken_shared_libraries 0)
pyside2_config(--python-link-flags-cmake python_linking_data )

###-----------------------------------------------------------------------------------
pyside2_config(--pyside2-path PYSIDE2_PATH)
pyside2_config(--pyside2-include-path PYSIDE2_INCLUDE_DIR 1)
pyside2_config(--pyside2-shared-libraries-cmake PYSIDE2_SHARED_LIBRARIES 0)

set(shiboken_path "${shiboken2_generator_path}/shiboken2${CMAKE_EXECUTABLE_SUFFIX}")
if(NOT EXISTS ${shiboken_path})
    message(FATAL_ERROR "Shiboken executable not found at path: ${shiboken_path}")
endif()

#一些路径的配置获取
###-----------------------------------------------------------------------------------
# Get all relevant Qt include dirs, to pass them on to shiboken.
#获取qt的一些头文件库路径
get_property(QT_CORE_INCLUDE_DIRS TARGET Qt5::Core PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
get_property(QT_GUI_INCLUDE_DIRS TARGET Qt5::Gui PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
get_property(QT_WIDGETS_INCLUDE_DIRS TARGET Qt5::Widgets PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
set(QT_INCLUDE_DIRS ${QT_CORE_INCLUDE_DIRS} ${QT_GUI_INCLUDE_DIRS} ${QT_WIDGETS_INCLUDE_DIRS})
set(INCLUDES "")
foreach(INCLUDE_DIR ${QT_INCLUDE_DIRS})
    list(APPEND INCLUDES "-I${INCLUDE_DIR}")
endforeach()

#包含qt的文件夹,以便解释qt有关的文件
include_directories (${QT_INCLUDE_DIRS}) 

 

###-----------------------------------------------------------------------------------

# We need to include the headers for the module bindings that we use.
set(PYSIDE2_ADDITIONAL_INCLUDES "")
foreach(INCLUDE_DIR ${PYSIDE2_INCLUDE_DIR})
    list(APPEND PYSIDE2_ADDITIONAL_INCLUDES "${INCLUDE_DIR}/QtCore")
    list(APPEND PYSIDE2_ADDITIONAL_INCLUDES "${INCLUDE_DIR}/QtGui")
    list(APPEND PYSIDE2_ADDITIONAL_INCLUDES "${INCLUDE_DIR}/QtWidgets")
endforeach()

# ==================================== RPATH configuration ====================================


# =============================================================================================
# !!! (The section below is deployment related, so in a real world application you will want to
# take care of this properly with some custom script or tool).
# =============================================================================================
# Enable rpaths so that the built shared libraries find their dependencies.
set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_INSTALL_RPATH ${shiboken2_module_path} ${CMAKE_CURRENT_SOURCE_DIR} ${PYSIDE2_PATH})
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
# =============================================================================================
# !!! End of dubious section.
# =============================================================================================


# =============================== CMake target - sample_library ===============================

 

# =============================== CMake target - sample_library ===============================

#处理object问题
set(mocfile  MainWindow.h )#包含qobject的文件
QT5_WRAP_CPP(HEADER_MOCS ${mocfile})
#qt5_generate_moc(MainWindow.h MainWindow.moc)
#生产moc文件来解释qobject

# Define the sample shared library for which we will create bindings.

#库的资源注意需要包含HEADER_MOCS来解决qobject问题
set(${sample_library}_sources  MainWindow.cpp  ${HEADER_MOCS})#${HEADER_MOCS}
add_library(${sample_library} SHARED ${${sample_library}_sources})
#生成添加一个库,参数1名称 2类型  3资源
message( "wwwwwHEADER_MOCS: ${${sample_library}_sources}")
#因为用到Widgets的相关部件,所有需要链接这个库
target_link_libraries(${sample_library} PRIVATE Qt5::Widgets)
#target_link_libraries(${sample_library} PRIVATE Qt5::Core)
#target_link_libraries(${sample_library} PRIVATE Qt5::Gui)

set_property(TARGET ${sample_library} PROPERTY PREFIX "")

# Needed mostly on Windows to export symbols, and create a .lib file, otherwise the binding
# library can't link to the sample library.
target_compile_definitions(${sample_library} PRIVATE BINDINGS_BUILD)


###-----------------------------------------------------------------------------------
#库需要包含的文件夹和第三方库文件
target_include_directories(${sample_library} PRIVATE ${python_include_dir})
target_include_directories(${sample_library} PRIVATE ${shiboken_include_dir})
target_include_directories(${sample_library} PRIVATE ${CMAKE_SOURCE_DIR})
target_include_directories(${sample_library} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

#target_include_directories(${sample_library} PRIVATE ${PYSIDE2_PATH})
target_include_directories(${sample_library} PRIVATE ${QT_INCLUDE_DIRS})
target_include_directories(${sample_library} PRIVATE ${PYSIDE2_ADDITIONAL_INCLUDES})


#target_link_libraries(${sample_library} PRIVATE ${PYSIDE2_SHARED_LIBRARIES})

#set(CMAKE_AUTOMOC ON)

 


# ====================== Shiboken target for generating binding C++ files  ====================

#shiboken操作,注意包含的路径,例如INCLUDES来解决qt需要的头文件 --enable-pyside-extensions信号槽
# Set up the options to pass to shiboken.
set(shiboken_options --generator-set=shiboken --enable-parent-ctor-heuristic
    --enable-return-value-heuristic --use-isnull-as-nb_nonzero --enable-pyside-extensions
    --avoid-protected-hack
     ${INCLUDES}###-----------------------------------------------------------------------------------
    -I${CMAKE_SOURCE_DIR}
    -T${CMAKE_SOURCE_DIR}
    -T${PYSIDE2_PATH}/typesystems
    --output-directory=${CMAKE_CURRENT_BINARY_DIR}
    )

#设置依赖项
set(generated_sources_dependencies ${wrapped_header} ${typesystem_file} )

# Add custom target to run shiboken to generate the binding cpp files.
add_custom_command(OUTPUT ${generated_sources}
                    COMMAND ${shiboken_path}
                    ${shiboken_options} ${wrapped_header} ${typesystem_file}
                    DEPENDS ${generated_sources_dependencies}
                    IMPLICIT_DEPENDS CXX ${wrapped_header}
                    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
                    COMMENT "Running generator for ${typesystem_file}.")


# =============================== CMake target - bindings_library =============================


# Set the cpp files which will be used for the bindings library.
set(${bindings_library}_sources ${generated_sources} )

# Define and build the bindings library.
add_library(${bindings_library} MODULE ${${bindings_library}_sources})

# Apply relevant include and link flags.
target_include_directories(${bindings_library} PRIVATE ${python_include_dir})
target_include_directories(${bindings_library} PRIVATE ${shiboken_include_dir})
target_include_directories(${bindings_library} PRIVATE ${CMAKE_SOURCE_DIR})

target_link_libraries(${bindings_library} PRIVATE ${shiboken_shared_libraries})
target_link_libraries(${bindings_library} PRIVATE ${sample_library})

###-----------------------------------------------------------------------------------
#set_property(TARGET ${bindings_library} PROPERTY AUTOMOC 1)#为修改信号槽问题增加的

target_compile_definitions(${bindings_library} PRIVATE LIBSHIBOKEN_EXPORTS)##################################################

target_include_directories(${sample_library} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(${bindings_library} PRIVATE ${PYSIDE2_PATH})
target_include_directories(${bindings_library} PRIVATE ${PYSIDE2_INCLUDE_DIR})
target_include_directories(${bindings_library} PRIVATE ${QT_INCLUDE_DIRS})
target_include_directories(${bindings_library} PRIVATE ${PYSIDE2_ADDITIONAL_INCLUDES})
target_link_libraries(${bindings_library} PRIVATE Qt5::Widgets)
#target_link_libraries(${bindings_library} PRIVATE Qt5::Core)
#target_link_libraries(${bindings_library} PRIVATE Qt5::Gui)
#target_link_libraries(${bindings_library} PRIVATE ${PYSIDE2_PATH})
target_link_libraries(${bindings_library} PRIVATE ${PYSIDE2_SHARED_LIBRARIES})#pyside2.abi3.lib

##一些打印信息
message( "PYSIDE2_PATH: ${PYSIDE2_PATH}")
message( "PYSIDE2_SHARED_LIBRARIES: ${PYSIDE2_SHARED_LIBRARIES}")
message( "PYSIDE2_INCLUDE_DIR: ${PYSIDE2_INCLUDE_DIR}")
message( "PYSIDE2_ADDITIONAL_INCLUDES: ${PYSIDE2_ADDITIONAL_INCLUDES}")
message( "lpython_include_dir: ${python_include_dir}")
message( "shiboken_include_dir: ${shiboken_include_dir}")
message( "CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")
message( "shiboken_shared_libraries: ${shiboken_shared_libraries}")
message( "sample_library: ${sample_library}")
message( "QT_INCLUDE_DIRS: ${QT_INCLUDE_DIRS}")
message( "CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}")

##


# Adjust the name of generated module.
set_property(TARGET ${bindings_library} PROPERTY PREFIX "")
set_property(TARGET ${bindings_library} PROPERTY OUTPUT_NAME_DEBUG#OUTPUT_NAME #OUTPUT_NAME_DEBUG
             "${bindings_library}${PYTHON_EXTENSION_SUFFIX}")
if(WIN32)
    set_property(TARGET ${bindings_library} PROPERTY SUFFIX ".pyd")
endif()

 

# Find and link to the python import library only on Windows.
# On Linux and macOS, the undefined symbols will get resolved by the dynamic linker
# (the symbols will be picked up in the Python executable).
if (WIN32)
    list(GET python_linking_data 0 python_libdir)
    list(GET python_linking_data 1 python_lib)
  #######注意${python_lib}调试版本debug

    find_library(python_link_flags ${python_lib} PATHS ${python_libdir} HINTS ${python_libdir})
    target_link_libraries(${bindings_library} PRIVATE ${python_link_flags})
   message( "python_link_flags: ${python_link_flags}")

endif()

# Same as CONFIG += no_keywords to avoid syntax errors in object.h due to the usage of the word Slot
#target_compile_definitions(${bindings_library} PRIVATE QT_NO_KEYWORDS)


# ================================= Dubious deployment section ================================


if(WIN32)
    # =========================================================================================
    # !!! (The section below is deployment related, so in a real world application you will
    # want to take care of this properly (this is simply to eliminate errors that users usually
    # encounter.
    # =========================================================================================
    # Circumvent some "#pragma comment(lib)"s in "include/pyconfig.h" which might force to link
    # against a wrong python shared library.

    set(python_versions_list 3 32 33 34 35 36 37 38)
    set(python_additional_link_flags "")
    foreach(ver ${python_versions_list})
        set(python_additional_link_flags
            "${python_additional_link_flags} /NODEFAULTLIB:\"python${ver}_d.lib\"")
        set(python_additional_link_flags
            "${python_additional_link_flags} /NODEFAULTLIB:\"python${ver}.lib\"")
    endforeach()

#message( "python_additional_link_flags: ${python_additional_link_flags}")

    set_target_properties(${bindings_library}
                           PROPERTIES LINK_FLAGS "${python_additional_link_flags}")

    # Add custom target to hard-link shiboken shared libraries into the build folder, so that
    # the user doesn't have to set the PATH manually to point to the PySide2 package.
set(shared_libraries ${shiboken_shared_libraries} ${PYSIDE2_SHARED_LIBRARIES})
    foreach(library_path ${shared_libraries})
        string(REGEX REPLACE ".lib$" ".dll" library_path ${library_path})
        get_filename_component(base_name ${library_path} NAME)
        file(TO_NATIVE_PATH ${library_path} source_path)
        file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${base_name}" dest_path)
        add_custom_command(OUTPUT "${base_name}"
                            COMMAND mklink /H "${dest_path}" "${source_path}"
                            DEPENDS ${library_path}
                            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                #WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
                            COMMENT "Creating hardlink to shiboken shared library ${base_name}")

        # Fake target that depends on the previous one, but has special ALL keyword, which means
        # it will always be executed.
        add_custom_target("fake_${base_name}" ALL DEPENDS ${base_name})
    endforeach()
    # =========================================================================================
    # !!! End of dubious section.
    # =========================================================================================
endif()


# =============================================================================================
# !!! (The section below is deployment related, so in a real world application you will want to
# take care of this properly with some custom script or tool).
# =============================================================================================
# Install the library and the bindings module into the source folder near the main.py file, so
# that the Python interpeter successfully imports the used module.
install(TARGETS ${bindings_library} ${sample_library}
        LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}
        RUNTIME DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}
        )
# =============================================================================================
# !!! End of dubious section.
# =============================================================================================
-----------------------------------------------------------------------------------------------------------

怎么执行;把上面的文件放在一个文件夹上,然后在该文件夹上新建一个空的文件夹build,进入到build文件夹下,打开cmd命令行。然后cmake -H.. -B. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
生成一个makefile,继续执行nmake,就能生成我们需要的库,把生成的库一个.dll,一个.pyd,可能还有其余pyside,shiboken放一起,或者你设置的环境变量都能找到也没问题。

python调用:
main.py:
------------------------------------------------------------------------------------------
import sys

from PySide2.QtGui import *
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from Universe import *


class RunScript(QObject):
    def __init__(self, mainWindow):
        QObject.__init__(self)
        self.mainWindow = mainWindow
        
       

    def runScript(self, script):
        print ("runScript" )
        mainWindow = self.mainWindow
        exec(str(script))
                  
    def SlotTest(self):
        print (w.editor.toPlainText())
        self.runScript(w.editor.toPlainText())
        #QObject.connect(SIGNAL('runPythonCodegg(Qstring)'),self.runScript)
        
    @Slot(int)   
    def SlotTestInt(num):
        print ("SlotTestInt" )

    speak_words = Signal(str)
    signaltest = Signal()
        

@Slot(str)
def say_some_words(words):
    print(words)

a = QApplication(sys.argv)

print ("Start" )
w = MainWindow()
print ("END" )
r = RunScript(w)


w.setWindowTitle('PyHybrid')
w.resize(1000,800)
w.show()

w.connect(w, SIGNAL('runPythonCode(Qstring)'), r.runScript)

a.exec_()

------------------------------------------------------------------------------------------------------------
在制作过程中可能遇到很多问题:
问题1:遇到一些编译器问题,环境变量设置不对,调用不到自己需要的编译器,这个一般是出现在生成makefile的过程中出现,找出你电脑中安装的vs等编译器中vcvars32.bat,这种批量处理的脚本,在控制台执行就会帮助你配置一些库和exe的调用。

问题2:解析xml或者是.h时候出错。.h问题一般是没有在cmake中设置包含,或者是没有包含相关文件夹。
xml问题可能会找不到shiboken,那就是可能电脑没有安装shiboken2_generator的生成器。
生成过程中需要暴露的东西,需要在xml说明。bindings.h包含相关头文件.
https://download.qt.io/official_releases/QtForPython/shiboken2-generator/
这个链接是生成器的下载路径,选择对于版本。
xml头绑定的一些规则:
http://pyside.github.io/docs/api-extractor/typesystem.html
生成器的一些参数设置:http://pyside.github.io/docs/shiboken/

上面需要安装的东西:最基本的,pyside2,shiboken,cmake,shiboken2_generator,vs或者其他编译器.
上面例子可在你安装的D:\PyThon3.5\Lib\site-packages\PySide2\examples\samplebinding

注意:使用qt特别注意的一个是qt库头文件等的包含。qt信号和槽的处理,也就是object的处理另外一个就是资源文件的处理。这个大概就是C++封装和qt的区别了吧。

上文资源下载地址:https://download.csdn.net/download/dreamsongo/11259681

不足之处请大家指出一起讨论。。这些是已经试验过成功的。。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值