使用Shiboken為C++和Qt庫建立Python綁定

來源:互聯網
上載者:User


Shiboken的資料真少,僅僅為了寫一個小小的demo就大費周折。但不管怎樣,經過幾個月斷斷續續的瞭解,總算可以為純C++的庫和Qt的庫建立python的綁定了。

本文前提:

  • 熟悉cmake,能夠用cmake構建C++與Qt的程式和庫
  • 安裝有Python和Shiboken的開發環境
  • 安裝有PySide和Qt4(4.6及以上)的開發環境

  • 注意:若在windows下,Shiboken和PySide開發環境需要自己編譯

接下來記錄兩個例子:(本文例子僅在Windows和Ubuntu環境下進行過測試)

建立C++庫的綁定

步驟:

  • 建立一個普通的 C++ 的庫 foo.dll 或 libfoo.so
  • 使用shiboken產生膠水代碼
  • 編譯膠水代碼產生綁定庫 foo.pyd 或 foo.so
  • 編寫python程式進行測試

看一下本例用到的所有檔案:

|-- 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

頂層的CMakeLists.txt 檔案內容如下:

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

libfoo 中是我們原始的需要綁定的C++代碼,可以快速一下這3個檔案

  • 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;}

這部分沒有什麼特別的東西,直接看綁定部分

foobinding

CMakeLists.txt 檔案內容

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)

內容本身是簡單的,就是

  • 尋找python和shiboken的開發包
  • 尋找可執行程式generatorrunner
  • 加入兩個子目錄

只是中間加入的那堆最佳化選項是檔案看起來很亂

foo

這才是我們最重要的部分,先看CMakeLists.txt檔案:

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..."                  )

痛點就是產生膠水代碼(這兒的custom命令):

  • 它需要一個輸入的 global.h 檔案和 typesystem_foo.xml 檔案
  • 通過 --include-paths 指定global.h中包含的標頭檔所在的路徑

看一下這兩個簡單的檔案:

  • 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

產生東西能否工作呢?需要測試來說話:

  • 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}")
  • 呵呵,這個檔案看起來真煩!!
  • 概念上還算簡單的東西:
    • 調用python來執行我們的測試程式
    • 告訴python我們的綁定在那麼路徑下(通過環境變數PYTHONPATH)
    • 告訴python去哪兒找需要的動態庫(環境變數Unix下LD_LIBRARY_PATH,win下PATH)
    • windows下需要對路徑分隔字元進行調整(煩)

至於我們的測試程式,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()

至此,所有代碼看完了。環境準備好,編譯就比較簡單了,無非

  • mkdir build
  • cd build
  • cmake ..
  • make
  • make test
建立自訂Qt庫的綁定

操作步驟和檔案位置與前面非Qt的情況完全相同:

  • 建立一個普通的 Qt 動態庫 foo.dll 或 libfoo.so
  • 使用shiboken產生膠水代碼(需要PySide)

  • 編譯膠水代碼產生綁定庫 foo.pyd 或 foo.so
  • 編寫python程式進行測試

頂層CMakeLists檔案和前面完全相同(此處略)。

libfoo

libfoo 中是我們原始的需要綁定的Qt 代碼,可以快速一下這3個檔案(與前面相比,就是多了點Qt的元素)

  • 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")

此處使用的是qt4_automoc方式,你也可以用qt4_wrap_cpp方式(不做解釋)

  • 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

派生自QObject,代碼整體上比前面的短,因為Q_DECL_EXPORT可以使我們避免自己根據不同平台去定義宏。

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

注意,最後的include語句是配合cmake的qt4_automoc工作的,如果你有在qmake下使用它的經驗,請注意它們截然不同。

接下來看綁定部分

foobinding

CMakeLists.txt 檔案內容

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)

和它的前任比較,它複雜很多

  • 除了尋找python和shiboken的開發包,它還需要PySide開發環境

  • 除了尋找可執行程式generatorrunner,還需要注意它的選項(對Win下,必須 --avoid-protected-hack)
  • 因為要用到多重路徑,注意不同平台的路徑分割符!
foo

同前面一樣,此處是產生綁定的地方,也是我們最重要的部分,先看CMakeLists.txt檔案:

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..."                  )

注意和它前任比較,變複雜的部分:

  • 標頭檔路徑,需要Qt的標頭檔路徑和PySide的標頭檔路徑(注意子路徑)

  • 連結庫,需要Qt的庫和PySide的庫

  • generatorrunner 參數中的 --include-paths,添加Qt和PySide路徑

  • generatorrunner 參數中 --typesystem-paths 添加相應路徑

看一下另外兩個檔案:

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

幾個宏看不太懂,多了一個pyside_global.h檔案,屬於PySide內建的檔案

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

需要載入PySide 的 typesystem_core.xml

tests

產生東西能否工作,仍然需要測試來說話:

  • 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}")

恩,基本沒變化,就是多了個PYSIDE_PYTHONPATH

至於 test_foo.py 檔案,和第一部分完全一樣,此處略。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

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.