Use shiboken to create Python Bindings for C ++ and QT Libraries

Source: Internet
Author: User


There are really few shiboken materials. It takes a lot of money to write a small demo. However, after several months of intermittent understanding, we can finally create Python Bindings for libraries of pure C ++ and QT.

Prerequisites:

  • Familiar with cmake and able to build C ++ and QT programs and libraries with cmake
  • Install a development environment with Python and shiboken
  • Install a development environment with pyside and qt4 (4.6 and above)

  • Note: In Windows, you must compile the shiboken and pyside development environments by yourself.

The following two examples are recorded: (this example has only been tested in windows and Ubuntu environments)

Create a C ++ library binding

Steps:

  • Create a common C ++ library Foo. dll or libfoo. So
  • Use shiboken to generate glue code
  • Compile the glue code to generate the binding library Foo. PYD or Foo. So.
  • Compile a python program for testing

Take a look at all the files used in this example:

|-- CMakeLists.txt||-- libfoo/|      |-- CMakeLists.txt|      |-- foo.h|      `-- foo.cpp||-- foobinding/|      |-- CMakeLists.txt|      |-- foo/|      |      |-- CMakeLists.txt|      |      |-- global.h|      |      `-- typesystem_foo.xml|      `-- tests/|             |-- CMakeLists.txt|             `-- test_foo.py

The content of the top-layer cmakelists.txt file is as follows:

cmake_minimum_required(VERSION 2.8)add_subdirectory(libfoo)add_subdirectory(foobinding)enable_testing() 
Libfoo

Libfoo is the original C ++ code to be bound.

  • Cmakelists.txt
project(libfoo)set(LIB_SRC foo.cpp)add_definitions("-DLIBFOO_BUILD")add_library(libfoo SHARED ${LIB_SRC})set_target_properties(libfoo PROPERTIES OUTPUT_NAME "foo")
  • Foo. h
#ifndef FOO_H#define FOO_H#if defined _WIN32    #if LIBFOO_BUILD        #define LIBFOO_API __declspec(dllexport)    #else        #define LIBFOO_API __declspec(dllimport)    #endif#else    #define LIBFOO_API#endifclass LIBFOO_API Math{public:    Math(){}    ~Math(){}    int squared(int x);};#endif // FOO_H
  • Foo. cpp
#include "foo.h"int Math::squared(int x){    return x * x;}

There is nothing special about this part. Check the binding part directly.

Foobinding

Cmakelists.txt File Content

project(foobinding) cmake_minimum_required(VERSION 2.6) find_package(PythonLibs REQUIRED)find_package(Shiboken REQUIRED) find_program(GENERATOR generatorrunner REQUIRED)if (NOT GENERATOR)    message(FATAL_ERROR "You need to specify GENERATOR variable (-DGENERATOR=value)")endif() if(CMAKE_HOST_UNIX)    option(ENABLE_GCC_OPTIMIZATION "Enable specific GCC flags to optimization library size and performance. Only available on Release Mode" 0)    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fvisibility=hidden -Wno-strict-aliasing")    set(CMAKE_CXX_FLAGS_DEBUG "-g")    if(ENABLE_GCC_OPTIMIZATION)        set(CMAKE_BUILD_TYPE Release)        set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Os -Wl,-O1")        if(NOT CMAKE_HOST_APPLE)            set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--hash-style=gnu")        endif()    endif()endif() enable_testing() add_subdirectory(foo)add_subdirectory(tests)

The content itself is simple, that is

  • Find Python and shiboken Development Kits
  • Find the executable program generatorrunner
  • Add two subdirectories

The heap optimization option added in the middle is that the file looks messy.

Foo

This is the most important example. First, check the cmakelists.txt file:

project(foo) set(foo_SRC    ${CMAKE_CURRENT_BINARY_DIR}/foo/foo_module_wrapper.cpp    ${CMAKE_CURRENT_BINARY_DIR}/foo/math_wrapper.cpp) set(foo_INCLUDE_DIRECTORIES    ${SHIBOKEN_INCLUDE_DIR}    ${PYTHON_INCLUDE_PATH}    ${libfoo_SOURCE_DIR}) set(foo_LINK_LIBRARIES    ${SHIBOKEN_PYTHON_LIBRARIES}    ${SHIBOKEN_LIBRARY}    libfoo) include_directories(foo ${foo_INCLUDE_DIRECTORIES})add_library(foo MODULE ${foo_SRC})set_property(TARGET foo PROPERTY PREFIX "")if(WIN32)    set_property(TARGET foo PROPERTY SUFFIX ".pyd")endif()target_link_libraries(foo ${foo_LINK_LIBRARIES})add_custom_command(OUTPUT ${foo_SRC}                   COMMAND ${GENERATOR}                   --generatorSet=shiboken                   ${CMAKE_CURRENT_SOURCE_DIR}/global.h                   --include-paths=${libfoo_SOURCE_DIR}                   --output-directory=${CMAKE_CURRENT_BINARY_DIR}                   ${CMAKE_CURRENT_SOURCE_DIR}/typesystem_foo.xml                   WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}                   COMMENT "Running generator for libfoo..."                  )

The difficulty is to generate the glue Code (the custom command here ):

  • It requires an input global. h file and a typesystem_foo.xml file.
  • Use -- include-paths to specify the path of the header file contained in global. h.

Let's take a look at these two simple files:

  • Global. h
#include "foo.h"
  • Typesystem_foo.xml
<?xml version="1.0"?><typesystem package="foo">    <primitive-type name="int"/>    <value-type name='Math'/></typesystem>
Tests

Can generate something to work? Test is required:

  • Cmakelists.txt
if(WIN32)    set(TEST_PYTHONPATH     "${foo_BINARY_DIR}")    set(TEST_LIBRARY_PATH   "${libfoo_BINARY_DIR};$ENV{PATH}")    set(LIBRARY_PATH_VAR    "PATH")    string(REPLACE "//" "/" TEST_PYTHONPATH "${TEST_PYTHONPATH}")    string(REPLACE "//" "/" TEST_LIBRARY_PATH "${TEST_LIBRARY_PATH}")     string(REPLACE ";" "//;" TEST_PYTHONPATH "${TEST_PYTHONPATH}")    string(REPLACE ";" "//;" TEST_LIBRARY_PATH "${TEST_LIBRARY_PATH}")else()    set(TEST_PYTHONPATH     "${foo_BINARY_DIR}")    set(TEST_LIBRARY_PATH   "${libfoo_BINARY_DIR}:$ENV{LD_LIBRARY_PATH}")    set(LIBRARY_PATH_VAR    "LD_LIBRARY_PATH")endif()add_test(math ${SHIBOKEN_PYTHON_INTERPRETER} ${CMAKE_CURRENT_SOURCE_DIR}/test_foo.py)set_tests_properties(math PROPERTIES ENVIRONMENT "PYTHONPATH=${TEST_PYTHONPATH};${LIBRARY_PATH_VAR}=${TEST_LIBRARY_PATH}")
  • Haha, this file looks really annoying !!
  • The concept is simple:
    • Call python to execute our test program
    • Tell Python that we are bound to this path (through the Environment Variable pythonpath)
    • Tell Python where to find the desired dynamic library (Environment Variable LD_LIBRARY_PATH under UNIX, path under Win)
    • Adjust the path Separator in Windows)

For our test program, test_foo.py:

# -*- coding: utf-8 -*-import unittestimport fooclass MathTest(unittest.TestCase):    def testMath(self):        '''Test case for Math class from foo module.'''        val = 5        math = foo.Math()        self.assertEqual(math.squared(5), 5 * 5) if __name__ == '__main__':    unittest.main()

So far, all the code has been read. When the environment is ready, compilation is easier.

  • Mkdir build
  • CD build
  • Cmake ..
  • Make
  • Make Test
Create a custom QT library binding

The operation steps and file location are exactly the same as those of the previous non-QT:

  • Create a common QT dynamic library Foo. dll or libfoo. So
  • Use shiboken to generate glue code (pyside required)

  • Compile the glue code to generate the binding library Foo. PYD or Foo. So.
  • Compile a python program for testing

The top-level cmakelists file is exactly the same as the previous one (omitted here ).

Libfoo

Libfoo is our original QT code to be bound. You can quickly look at these three files (compared with the previous one, we have added some QT elements)

  • Cmakelists.txt
project(libfoo)find_package(Qt4 COMPONENTS QtCore REQUIRED)include(${QT_USE_FILE})include_directories(${CMAKE_CURRENT_BINARY_DIR})set(LIB_SRC foo.cpp)qt4_automoc(${LIB_SRC})add_definitions("-DLIBFOO_BUILD")add_library(libfoo SHARED ${LIB_SRC})target_link_libraries(libfoo ${QT_LIBRARIES})set_target_properties(libfoo PROPERTIES OUTPUT_NAME "foo")

The qt4_automoc method is used here. You can also use the qt4_wrap_cpp method (not explained)

  • Foo. h
#ifndef FOO_H#define FOO_H#include <QtCore/QObject>#if LIBFOO_BUILD    #define LIBFOO_API Q_DECL_EXPORT#else    #define LIBFOO_API Q_DECL_IMPORT#endifclass LIBFOO_API Math : public QObject{    Q_OBJECTpublic:    Math();    ~Math();    int squared(int x);};#endif // FOO_H

Derived from qobject, the code is shorter than the previous one, because q_decl_export can avoid defining macros based on different platforms.

  • Foo. cpp
#include "foo.h"Math::Math()    :QObject(NULL){}Math::~Math(){}int Math::squared(int x){    return x * x;}#include "foo.moc"

Note that the final include statement works with qt4_automoc of cmake. If you have experience using qmake, note that they are completely different.

Next, let's look at the binding part.

Foobinding

Cmakelists.txt File Content

project(foobinding) cmake_minimum_required(VERSION 2.6) find_package(PythonLibs REQUIRED)find_package(Shiboken REQUIRED)find_package(PySide REQUIRED)find_package(Qt4 REQUIRED) include(${QT_USE_FILE})find_program(GENERATOR generatorrunner REQUIRED)if (NOT GENERATOR)    message(FATAL_ERROR "You need to specify GENERATOR variable (-DGENERATOR=value)")endif() if(MSVC)    set(CMAKE_CXX_FLAGS "/Zc:wchar_t- /GR /EHsc /DNOCOLOR /DWIN32 /D_WINDOWS /D_SCL_SECURE_NO_WARNINGS")else()    if(CMAKE_HOST_UNIX)        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fvisibility=hidden -Wno-strict-aliasing")    endif()    set(CMAKE_CXX_FLAGS_DEBUG "-g")    option(ENABLE_GCC_OPTIMIZATION "Enable specific GCC flags to optimization library size and performance. Only available on Release Mode" 0)    if(ENABLE_GCC_OPTIMIZATION)        set(CMAKE_BUILD_TYPE Release)        set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Os -Wl,-O1")        if(NOT CMAKE_HOST_APPLE)            set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--hash-style=gnu")        endif()    endif()    if(CMAKE_HOST_APPLE)        if (NOT QT_INCLUDE_DIR)            set(QT_INCLUDE_DIR "/Library/Frameworks")        endif()        if(ALTERNATIVE_QT_INCLUDE_DIR)            set(QT_INCLUDE_DIR ${ALTERNATIVE_QT_INCLUDE_DIR})        endif()        string(REPLACE " " ":" QT_INCLUDE_DIR ${QT_INCLUDE_DIR})    endif()endif()set(GENERATOR_EXTRA_FLAGS --generator-set=shiboken --enable-parent-ctor-heuristic --enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb_nonzero)if(WIN32 OR DEFINED AVOID_PROTECTED_HACK)    message(STATUS "PySide will be generated avoiding the protected hack!")    set(GENERATOR_EXTRA_FLAGS ${GENERATOR_EXTRA_FLAGS} --avoid-protected-hack)    add_definitions(-DAVOID_PROTECTED_HACK)else()    message(STATUS "PySide will be generated using the protected hack!")endif()if (WIN32)    set(PATH_SEP "/;")else()    set(PATH_SEP ":")endif()enable_testing() add_subdirectory(foo)add_subdirectory(tests)

Compared with its predecessor, it is much more complicated

  • In addition to the python and shiboken Development kits, it also requires the pyside Development Environment

  • In addition to finding the executable program generatorrunner, you also need to pay attention to its options (for win, you must -- avoid-protected-hack)
  • Because you need to use multiple paths, pay attention to the path delimiters of different platforms!
Foo

In the same way as before, this is the place where the binding is generated, and the most important part. First, see the cmakelists.txt file:

project(foo) set(foo_SRC    ${CMAKE_CURRENT_BINARY_DIR}/foo/foo_module_wrapper.cpp    ${CMAKE_CURRENT_BINARY_DIR}/foo/math_wrapper.cpp) set(foo_INCLUDE_DIRECTORIES    ${SHIBOKEN_INCLUDE_DIR}    ${SHIBOKEN_PYTHON_INCLUDE_DIR}    ${PYSIDE_INCLUDE_DIR}    ${PYSIDE_INCLUDE_DIR}/QtCore    ${libfoo_SOURCE_DIR}) set(foo_LINK_LIBRARIES    ${SHIBOKEN_PYTHON_LIBRARIES}    ${SHIBOKEN_LIBRARY}    ${PYSIDE_LIBRARY}    ${QT_LIBRARIES}    libfoo) include_directories(foo ${foo_INCLUDE_DIRECTORIES})add_library(foo MODULE ${foo_SRC})set_property(TARGET foo PROPERTY PREFIX "")if(WIN32)    set_property(TARGET foo PROPERTY SUFFIX ".pyd")endif()target_link_libraries(foo ${foo_LINK_LIBRARIES})add_custom_command(OUTPUT ${foo_SRC}                   COMMAND ${GENERATOR}                   --generatorSet=shiboken ${GENERATOR_EXTRA_FLAGS}                   ${CMAKE_CURRENT_SOURCE_DIR}/global.h                   --include-paths=${libfoo_SOURCE_DIR}${PATH_SEP}${QT_INCLUDE_DIR}${PATH_SEP}${PYSIDE_INCLUDE_DIR}                   --output-directory=${CMAKE_CURRENT_BINARY_DIR}                   --typesystem-paths=${typesystem_path}${PATH_SEP}${PYSIDE_TYPESYSTEMS}                   ${CMAKE_CURRENT_SOURCE_DIR}/typesystem_foo.xml                   WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}                   COMMENT "Running generator for libfoo..."                  )

Note that compared with its predecessor, the complex section is as follows:

  • Header file path, which requires the header file path of QT and the header file path of pyside (note the sub-path)

  • Link Library, which requires QT library and pyside Library

  • -- Include-paths in the generatorrunner parameter to add the QT and pyside paths.

  • In the generatorrunner parameter, add the corresponding path -- typesystem-paths.

Take a look at the other two files:

  • Global. h
#undef QT_NO_STL#undef QT_NO_STL_WCHAR #ifndef NULL#define NULL    0#endif #include "pyside_global.h"#include "foo.h"

I don't know much about several macros. I have an additional pyside_global.h file, which belongs to the pyside file.

  • Typesystem_foo.xml
<?xml version="1.0"?><typesystem package="foo">    <load-typesystem name="typesystem_core.xml" generate="no"/>    <object-type name='Math' /></typesystem>

Typesystem_core.xml of pyside to be loaded

Tests

To generate something that can work, you still need to test it:

  • Cmakelists.txt
if(WIN32)    set(TEST_PYTHONPATH     "${foo_BINARY_DIR};${PYSIDE_PYTHONPATH}")    set(TEST_LIBRARY_PATH   "${libfoo_BINARY_DIR};$ENV{PATH}")    set(LIBRARY_PATH_VAR    "PATH")    string(REPLACE "//" "/" TEST_PYTHONPATH "${TEST_PYTHONPATH}")    string(REPLACE "//" "/" TEST_LIBRARY_PATH "${TEST_LIBRARY_PATH}")     string(REPLACE ";" "//;" TEST_PYTHONPATH "${TEST_PYTHONPATH}")    string(REPLACE ";" "//;" TEST_LIBRARY_PATH "${TEST_LIBRARY_PATH}")else()    set(TEST_PYTHONPATH     "${foo_BINARY_DIR}:${PYSIDE_PYTHONPATH}")    set(TEST_LIBRARY_PATH   "${libfoo_BINARY_DIR}:$ENV{LD_LIBRARY_PATH}")    set(LIBRARY_PATH_VAR    "LD_LIBRARY_PATH")endif()add_test(math ${SHIBOKEN_PYTHON_INTERPRETER} ${CMAKE_CURRENT_SOURCE_DIR}/test_foo.py)set_tests_properties(math PROPERTIES ENVIRONMENT "PYTHONPATH=${TEST_PYTHONPATH};${LIBRARY_PATH_VAR}=${TEST_LIBRARY_PATH}")

Well, there is basically no change, but there is another pyside_pythonpath.

The test_foo.py file is exactly the same as the first part.

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.