Using Shiboken2 to create Python bindings for a Qt library

Table of Contents

Qt for PythonWith 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 (https://blog.qt.io/blog/2018/12/18/qt-python-5-12-released/).

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.

This post will show you how to create a set of Python bindings for a small Qt library from scratch.

PySide2
The Qt bindings themselves are easy to install and 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.

Setup

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.

Install Qt 5.12

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.

Set up the environment

The API extractor of Shiboken2 requires a clang/llvm version newer than the default version in Ubuntu 18.04, and we need to tell the buildsystem where to find it.

export LLVM_INSTALL_DIR=`llvm-config-6.0 --prefix`
# export the Qt library path so the correct icu libraries are found
export LD_LIBRARY_PATH=$HOME/Qt/5.12.0/gcc_64/lib/
# Put correct the Qt binaries in the PATH
export PATH=$HOME/Qt/5.12.0/gcc_64/bin/:$PATH

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.

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

Note: All files mentioned in this section can be downloaded as a tarball

The Shiboken2 post on qt.io introduces the process quite well, so you can also take a look there to better understand the process: https://blog.qt.io/blog/2018/05/31/write-python-bindings/

Shiboken2 input

Shiboken requires mainly three things to work

  1. The library to generate bindings for
  2. An ‘entry-point‘ header file which includes all other headers of classes for which bindings are generated
  3. A type description XML file which (at a minimum) tells shiboken which types to wrap

Note that the the typesystem description can do much more than describe which types to expose using which semantics: https://doc.qt.io/qtforpython/shiboken2/contents.html

The library

Our trivial library contains only a single QObject with signals, slots and a Q_ENUM which is used in the API.

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

See the attached project for implementation details.

The glue header

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

In our case this is also pretty simple:

bindings.xml:

As I wrote before, the typesystem description is a powerful tool but out of the scope of this particular post.

Building the bindings

Note: Due to the greatly improved Python integration 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.

This is not the right thing to do for production projects, consult your local system integrator :-).

CMakeLists.txt

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 downloadable zip.

Make a shadow build and install

mkdir build
cd build
cmake ..
make install

Using the module

Once the bindings have been generated and built we can now write a python script which uses the 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

swid@spica:~/pyside-example/$ workon pyside2build
(pyside2build) swid@spica:~/pyside-example/$ python main.py
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.

basysKom is offering consulting, training and development services for Qt. We have recently added a Qt for Python module to our training. If you are interested get in contact with info@basyskom.com.

2 Responses

  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?

Schreibe einen Kommentar zu KillianDiz Antworten abbrechen

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

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.
Share on facebook
Share on twitter
Share on linkedin
Share on reddit
Share on xing
Share on email
Share on stumbleupon
Share on whatsapp
Share on pocket