basysKom AnwendungsEntwicklung

Using Shiboken2 to create Python bindings for a Qt library
Essential Summary
With the release of Qt 5.12, Qt for Python is officially supported and can be used to write full-fledged Qt applications using Python as the main programming language. This prompted us to also take a closer look at the bindings (the Python module is called PySide2) and also the underlying technology, namely the binding generator called Shiboken2.
Shiboken2
Pyhton

With the release of Qt 5.12, Qt for Python is officially supported and can be used to write full-fledged Qt applications using Python as the main programming language.

This prompted us to also take a closer look at the bindings (the Python module is called PySide2) and also the underlying technology, namely the binding generator called Shiboken2.

PySide2

The Qt bindings themselves are easy to install and to use. If you have a python interpreter and the pip package manager set up on your system, whether it is Windows or Linux, you can install the module via pip install PySide2 and are ready to go.

Shiboken2

While it is nice to use the Qt framework from Python it is even better to be able to quickly create Python bindings for your own Qt libraries and use their API from Python with relatively little work.

Getting started

All examples on this article have been run on Ubuntu 18.04, the setup steps for other platforms might vary.

Since the most recent documentation advises to build Shiboken2 from source for building your own bindings, we will do that now.

Precondition

This posts assumes that you have a Qt 5.12 installation ready at $HOME/Qt/5.12.0/gcc_64/, which is the default location when using the online installer from qt.io.

“If you use Qt from a different location adjust the paths accordingly.”

Install all required tools and libraries

The following apt command line works for Ubuntu 18.04 and should be a good starting point for other distributions.

sudo apt update
sudo apt install llvm-6.0 virtualenvwrapper python3 python3-dev cmake build-essential git clang-6.0 libclang-6.0-dev libxslt-dev mesa-common-dev libgl1-mesa-glx libglib2.0-0 wget 

# We require CMake >= 3.12 due to the improved Python support 
wget https://github.com/Kitware/CMake/releases/download/v3.13.2/cmake-3.13.2-Linux-x86_64.tar.gz 

tar xvf cmake-3.13.2-Linux-x86_64.tar.gz 

export PATH=$PWD/cmake-3.13.2-Linux-x86_64/bin/:$PATH 

Get the sourcecode

The fastest way to get the PySide2 and Shiboken2 source is to clone the git repository.

git clone git://code.qt.io/pyside/pyside-setup.git
cd pyside-setup
git checkout 5.12.0
git submodule update --init 

Set up a python virtualenv

We advise you to work in python virtual environments, as that will keep your system installation clean and you should be able to do anything else in this post without requiring root privileges.

source /etc/bash_completion.d/virtualenvwrapper
mkvirtualenv -p `which python3` pyside2build 

This will set up a python3 virtual environment in your home directory (~/.local/share/virtualenvs/pyside2build) and activate it. Your shell should have a prefix indicating that. To activate the virtualenv in the future use: 

workon pyside2build 

Build

Now that we have prepared everything, the actual build is easy.

# Change into your clone of pyside-setup.git
cd pyside-setup

# Adjust the jobs parameter for the number of cores on your system
python setup.py install --qmake=$HOME/Qt/5.12.0/gcc_64/bin/qmake --jobs=8 

After the build has finished, which can take some time, you should have a fully working and properly linked PySide2 and Shiboken2 installation in your virtualenv.

Verify our build 

Try it by running the shiboken2 command line tool:

(pyside2build) swid@spica:~/src/pyside-setup (5.12)$ shiboken2
shiboken: Required argument header-file is missing.Command line: 

If you see output similar to this, you are good to go.

Wrapping your library with Shiboken2

Note: All files mentioned in this section can be cloned at GitHub. Clone the repository!

Shiboken2 requires mainly three things to work:

The library to generate bindings for.

An ‘entry-point’ header file which includes all other headers of classes for which bindings are generated for.

A type description XML file which (at a minimum) tells shiboken which types to wrap.

The library

Our trivial library contains only a single QObject with signals, slots and a Q_ENUM which is used in the API. Checkout the source code for implementation details.

qobjectwithenum.h:

#pragma once
#include 
 
class QObjectWithEnum : public QObject
{
    Q_OBJECT
 
public:
    enum class MyEnum {
        Some,
        Values,
        For,
        The,
        Enum,
    };
    Q_ENUM(MyEnum)
 
    explicit QObjectWithEnum(QObject *parent = nullptr);
 
    QString nonSlotFunction(const MyEnum value) const;
 
signals:
    void someSignal(QString stringArg);
 
public slots:
    void aSlot();
}; 

qobjectwithenum.cpp:

#include "qobjectwithenum.h"

#include 
#include 

QObjectWithEnum::QObjectWithEnum(QObject *parent) : QObject(parent)
{
}

QString QObjectWithEnum::nonSlotFunction(const QObjectWithEnum::MyEnum value) const {
    const auto ret = metaObject()->enumerator(0).valueToKey(int(value));
    qDebug() << __FUNCTION__ << "returning:" << ret;
    return ret;
}

void QObjectWithEnum::aSlot() {
    qDebug() << __FUNCTION__ << "slot called";
    emit someSignal("from aSlot");
} 

The ‘entry-point’ header file

Our ‘entry-point’ header pulls in the required library headers, which is pretty simple in our case, and also sets up a define required for generating code for the Qt specialities.

bindings.h:

#define QT_ANNOTATE_ACCESS_SPECIFIER(a) __attribute__((annotate(#a)))
 
#include "qobjectwithenum.h" 

The typesystem description

Although the typesystem description XML file can do much more than just describe which types to expose, we will stick to a rather simple version within our example.

bindings.xml:

<?xml version="1.0"?>

<typesystem package="Shiboken2QtExample">
    <load-typesystem name="typesystem_core.xml" generate="no"/>
    <object-type name="QObjectWithEnum">
        <enum-type name="MyEnum" />
    </object-type>
</typesystem>
 

Generate the bindings

Note: CMake >= 3.12 is required here.

To simplify building and linking all the parts we adapt a CMakeLists.txt file from the sample bindings example.

The example CMakeLists.txt installs the resulting library into the source folder and adds all relevant paths as RPATHS for the built libraries which makes local usage convenient.

CMakeLists.txt:

cmake_minimum_required(VERSION 3.12)
cmake_policy(VERSION 3.12)

find_package(Qt5 REQUIRED Core)

get_target_property(QtCore_location Qt5::Core LOCATION)
get_filename_component(QtCore_libdir ${QtCore_location} DIRECTORY)

set(CMAKE_AUTOMOC ON)

project(Shiboken2-Qt-Example)

...  

The CMake build file has been adapted from the official sample binding. The content is not relevant to this article. It can be found in the git repository.

Make a shadow build and install

mkdir build
cd build
cmake ..
make install 

Using the module

Now that the bindings have been generated and built we can write a python script which uses the new binding module.

from Shiboken2QtExample import *
 
a = QObjectWithEnum()
a.someSignal.connect(lambda x: print("Signal emitted: %s" % x))

a.aSlot()

print("int(QObjectWithEnum.MyEnum.Values) =", int(QObjectWithEnum.MyEnum.Values))

a.nonSlotFunction(QObjectWithEnum.MyEnum.Some) 

The easiest way to use the wrapped library is to activate the virtual environment, which contains the correct PySide2 version, and run it

$ workon pyside2build

(pyside2build)$ python main.py 

The expected output is:


aSlot slot called
Signal emitted: from aSlot
int(QObjectWithEnum.MyEnum.Values) = 1
nonSlotFunction returning: Some 

That’s it. Zero to Qt library bindings in one blog post. If you have any question, just leave a comment down below.

Picture of Sumedha Widyadharma

Sumedha Widyadharma

Sumedha Widyadharma is a senior software developer and consultant for basysKom GmbH, creating software solutions for and with customers, usually based on Qt. He joined basysKom in 2011 as a system integrator, bringing customers solutions to life on their embedded Linux platforms. These roots are still visible as a special interest in software development practices which help increase ease of development and product quality, such as automated testing, continuous integration/deployment and development tooling.

7 Antworten

  1. Hi, I cannot build your example under Windows 10 64bit. My toolkits are: MSVC 2017, Qt 5.12.3 msvc 2017 64bit, Python 2.7.3 64bit. The error is: fatal error LNK1107: file invalid: cannot read at 0x320
    Could you provide me any hints?

    • Hi,

      the error you posted is unfortunately not enough information and I do not have a similar setup available at the moment to reproduce the issue. Can you paste the whole build output somewhere?

      • Hi, I have the same error on Windows, everything works fine on Linux, maybe you know what the reason could be?

        qobjectwithenum.cpp
        [ 44%] Linking CXX shared library libexamplebinding.dll
        LINK: command “C:\PROGRA~2\MICROS~3\2017\COMMUN~1\VC\Tools\MSVC\1416~1.270\bin\Hostx64\x64\link.exe /nologo @CMakeFiles\libexamplebinding.dir\objects1.rsp /out:libexamplebinding.dll /implib:libexamplebinding.lib /pdb:K:\project\env\pysi\pyside-setup\examples\Example_Qt\build\libexamplebinding.pdb /dll /version:0.0 /machine:x64 /INCREMENTAL:NO C:\Qt\5.12.9\msvc2017\bin\Qt5Core.dll kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTFILE:libexamplebinding.dll.manifest” failed (exit code 1107) with the following output:
        C:\Qt\5.12.9\msvc2017\bin\Qt5Core.dll : fatal error LNK1107: invalid or corrupt file: cannot read at 0x2D8

  2. Thank you very much – this already helped a lot!
    But how to link against additional Qt modules like Qt::WebSockets or Qt::Quick? I’m not vey familiar with CMake .. so I’m a bit lost.

    • You can add the modules you need to the `find_packages` (e.g. `find_package(Qt5 REQUIRED Core WebSockets Quick)`) and `target_link_libraries` (e.g. `
      target_link_libraries(${bindings_library} PRIVATE Qt5::WebSockets Qt5::Quick)`)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Weitere Blogartikel

basysKom Newsletter

We collect only the data you enter in this form (no IP address or information that can be derived from it). The collected data is only used in order to send you our regular newsletters, from which you can unsubscribe at any point using the link at the bottom of each newsletter. We will retain this information until you ask us to delete it permanently. For more information about our privacy policy, read Privacy Policy