4.3.5. Bindings

Shiboken is a Python binding generator. It uses CLang to parse and analyze the structure of the C++ code (both from Qt, and our own widget), and typesystem to create the generated code through instructions.

../_images/qtforpython-underthehood.png

Qt uses Shiboken to create its Python bindings, and already provides typesystem files for all its libraries. This is how PySide is built.

The mayor issue of Shiboken, is that it uses CMake3 and a huge CMakeList.txt that is very difficult to understand. There is no other build system supported. In this page, I will explain how Shiboken works, and how the CMakeLists.txt file was translated.

4.3.5.1. Input for Bindings

Input files for binding generation in Shiboken are simple. Two files are needed:

  • C++ header file, that includes every class we need to bind.

  • Typesetting XML file, that indicates which will be the name of the target Python module, and which classes will be bind into said module.

The header file is just a list of #include sentences, no extra code is needed. Every class that needs binding has to be included in this file, through the use of common #include macro instructions. If two classes are defined in the same file, then only one include is needed.

 1/****************************************************************************
 2* ESO License
 3****************************************************************************/
 4
 5#ifndef BINDINGS_H
 6#define BINDINGS_H
 7
 8#define QT_ANNOTATE_ACCESS_SPECIFIER(a) __attribute__((annotate(#a)))
 9
10#include "QeExamplesWidgets.h"
11
12#endif // BINDINGS_H
  • Line 8 indicates to the C++ preprocessor that it needs to make classifiers used by Qt visible. These classifiers are used in QObjects. (signals:, slots: in the header files).

  • In Line 10, you should include every widget you need. In here, we recommend to use only file named as the resulting target name. This will ensure compabitility with Python module names, allowing both C++ and Python version of the widget to provide correct include/import statements.

The typesystem file is a bit more complex.

 1<?xml version="1.0"?>
 2<typesystem package="QeExamplesWidgets">
 3    <load-typesystem name="typesystem_core.xml" generate="no"/>
 4    <load-typesystem name="typesystem_core_common.xml" generate="no" />
 5    <load-typesystem name="typesystem_widgets.xml" generate="no" />
 6    <load-typesystem name="typesystem_gui_common.xml" generate="no" />
 7    <primitive-type name="double"/>
 8    <object-type name="QeExampleDmsLabel">
 9    </object-type>
10</typesystem>
  • As you see here, the XML defines in line 2 a package called QeExamplesWidgets. This will be the name of the resulting Python package.

  • Lines 3 to 6 indicate the typesystem engine to load more typesystem definitions. These ones are the base set that Qt needs for widgets.

  • Then one primitive is declare in line 7, as it is used in slots and signals in the QeExampleDmsLabel class. If any other primitive is used, but it is not in the signature, then no definition for them is needed.

  • In line 8 an object is declared. This object is a C++ class named QeExampleDmsLabel. This should be the same class name as in the C++ code declaration. The resulting Python library will have a class in package QeExamplesWidgets named QeExampleDmsLabel.

4.3.5.2. Code generation

In Qt’s examples, CMake is used. I will indicate here how to do this without a build system (and eventually, how to do it in WAF).

The Code generation is one command shiboken with several options for includes of options, and passes two mandatory arguments, the C++ header file, and the typesystem XML file:

42  code_generation = bld(
43    rule="${SHIBOKEN} "\
44         "--generator-set=shiboken "\
45         "--enable-parent-ctor-heuristic "\
46         "--enable-return-value-heuristic "\
47         "--enable-pyside-extensions "\
48         "--use-isnull-as-nb_nonzero "\
49         "--avoid-protected-hack "\
50         "--language-level=c++17 "\
51         "-I${SRC_DIR}/../../widgets/src/include "\
52         "-I${SRC_DIR} "\
53         "-I${INTROOT}/include "\
54         + bld.env.GEN_QT_INCLUDES +
55         "-I/usr/include/ "\
56         "-T" + str(bld.env.GEN_PYSIDE_TS_typesystemdir) + " "\
57         "-T${SRC_DIR} "\
58         "--output-directory=${DEST_DIR} "\
59         "${SRC_DIR}/bindings.h "\
60         "${SRC_DIR}/bindings.xml",
61    source=['src/bindings.xml', 'src/bindings.h'],
62    target=[
63        'src/QeExamplesWidgets/qeexampleswidgets_module_wrapper.cpp',
64        'src/QeExamplesWidgets/qeexampledmslabel_wrapper.cpp'
65        ],
66    name='bindings-generate',
67  )

The SHIBOKEN variable is path to the code generator. We use waf to locate the executable in the configuration section. At the moment, the include statements -I for the code generation are still manually constructed from pkg-config information.

  • Line 51 indicates where the header files for the source widgets are.

  • Line 53 indicates where the bindings.h file is located

  • Line 54 give access to QtWidgets, QtCore and QtGui shiboken, and pyside obtained through pkg-config.

The ones in -T entries are special. -T indicates where the typesettings files are located, in case we need to “include” other libraries.

  • Line 56 indicates the location of all typesettings files from Qt. In this case, I do not want to specify how to bind a QWidget, QPen, QBrush classes, so I just indicate that my project uses typesettings files from Qt project.

  • Line 57 indicate where the typesetting file from our project is located.

The options do not change from one project to another, only the target entry form the generation stage.

Tip

If you need more example, we suggest to look at typesystem files from Qt:

As a result, this will generate a directory in DEST_DIR, that has 2 files for each binded class, and 2 more for each Python module indicated as target.

4.3.5.3. Compilation

The compilation of these are more or less straight C++ objects, that need to be linked together as a shared library.

A compilation step prepares the shared library, using as source the same list as the target from the generation step.

69  compilation = bld(
70    features='pyext cxx cxxshlib wdep ' + bld.env.GEN_COMP_QT_FEATURE,
71    source=[
72        'src/QeExamplesWidgets/qeexampleswidgets_module_wrapper.cpp',
73        'src/QeExamplesWidgets/qeexampledmslabel_wrapper.cpp'
74        ],
75    target='QeExamplesWidgets',
76    includes=[
77      str(bld.env.COMP_PYSIDE_I_includedir)+'/QtCore',
78      str(bld.env.COMP_PYSIDE_I_includedir)+'/QtWidgets',
79      str(bld.env.COMP_PYSIDE_I_includedir)+'/QtGui',
80    ],
81    use=
82        bld.env.GEN_COMP_QT_USE +
83        [
84        "widgets"
85        ],
86    name='bindings',
87    # Fix for ELTDEV-853
88    pytest_path  = [ bld.path.get_bld().abspath() ],
89  )
90  compilation.cxxflags = ["-Wno-pedantic"]

This compilation step has the necessary features that provides its with the compiler options to properly generate a c++ shared library, that has python and qt as depedency.

An extra set of includes entries for the PySide module are added, and a use statement is introduced to depend on the widgets waf module from the previous chapter of this guide.

This is how the module would look like with bindings added:

bindings
├── src
│   ├── bindings.h
│   └── bindings.xml
└── wscript