diff options
Diffstat (limited to 'examples/samplebinding/doc/samplebinding.rst')
-rw-r--r-- | examples/samplebinding/doc/samplebinding.rst | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/examples/samplebinding/doc/samplebinding.rst b/examples/samplebinding/doc/samplebinding.rst new file mode 100644 index 000000000..defb55d6b --- /dev/null +++ b/examples/samplebinding/doc/samplebinding.rst @@ -0,0 +1,288 @@ +Sample Bindings Example +======================= + +This example showcases how to generate Python bindings for a +non-Qt C++ library. + +The example defines a CMake project that builds two libraries: + +* ``libuniverse`` - a sample library with two C++ classes. + +* ``Universe`` - the generated Python extension module that contains + bindings to the library above. + +The project file is structured in such a way that a user can copy-paste +in into their own project, and be able to build it with a minimal amount +of modifications. + +Description ++++++++++++ + +The libuniverse library declares two classes: ``Icecream`` and ``Truck``. + +``Icecream`` objects have a flavor, and an accessor for returning the +flavor. + +``Truck`` instances store a vector of ``Icecream`` objects, and have various +methods for adding new flavors, printing available flavors, delivering +icecream, etc. + +From a C++ perspective, ``Icecream`` instances are treated as +*object types* (pointer semantics) because the class declares virtual +methods. + +In contrast ``Truck`` does not define virtual methods and is treated as +a *value type* (copy semantics). + +Because ``Truck`` is a value type and it stores a vector of ``Icecream`` +pointers, the rule of five has to be taken into account (implement the +copy constructor, assignment operator, move constructor, move assignment +operator and destructor). + +And due to ``Icecream`` objects being copyable, the type has to define an +implementation of the ``clone()`` method, to avoid type slicing issues. + +Both of these types and their methods will be exposed to Python by +generating CPython code. The code is generated by ``shiboken`` and +placed in separate ``.cpp`` files named after each C++ type. The code is +then compiled and linked into a shared library. The shared library is a +CPython extension module, which is loaded by the Python interpreter. + +Beacuse the C++ language has different semantics to Python, shiboken +needs help in figuring out how to generate the bindings code. This is +done by specifying a special XML file called a typesystem file. + +In the typesystem file you specify things like: + + * Which C++ classes should have bindings (Icecream, Truck) and with what + kind of semantics (value / object) + + * Ownership rules (who deletes the C++ objects, C++ or Python) + + * Code injection (for various special cases that shiboken doesn't know + about) + + * Package name (name of package as imported from Python) + +In this example we declare ``Icecream`` as an object type and ``Truck`` +as a value type. The ``clone()`` and ``addIcecreamFlavor(Icecream*)`` +need additional info about who owns the parameter objects when passing +them across language boundaries (in this case C++ will delete the objects). + +The ``Truck`` has getters and setters for the string ``arrivalMessage``. +In the type system file, we declare this to be a property in Python: + +.. code-block:: xml + + <property type="std::string" name="arrivalMessage" get="getArrivalMessage" set="setArrivalMessage"/> + + +It can then be used in a more pythonic way: + +.. code-block:: python + + special_truck.arrivalMessage = "A new SPECIAL icecream truck has arrived!\n" + +After shiboken generates the C++ code and CMake makes an extension +module from the code, the types can be accessed in Python simply by +importing them using the original C++ names. + +.. code-block:: python + + from Universe import Icecream, Truck + + +Constructing C++ wrapped objects is the same as in Python + +.. code-block:: python + + icecream = Icecream("vanilla") + truck = Truck() + + +And actual C++ constructors are mapped to the Python `__init__` method. + +.. code-block:: python + + class VanillaChocolateIcecream(Icecream): + def __init__(self, flavor=""): + super().__init__(flavor) + + +C++ methods can be accessed as regular Python methods using the C++ +names + +.. code-block:: python + + truck.addIcecreamFlavor(icecream) + +Inheritance works as with regular Python classes, and virtual C++ +methods can be overridden simply by definining a method with the same +name as in the C++ class. + +.. code-block:: python + + class VanillaChocolateIcecream(Icecream): + # ... + def getFlavor(self): + return "vanilla sprinked with chocolate" + + +The ``main.py`` script demonstrates usages of these types. + +The CMake project file contains many comments explaining all the build +rules for those interested in the build process. + +Building the project +++++++++++++++++++++ + +This example can only be built using ``CMake``. +The following requirements need to be met: + +* A PySide package is installed into the current active Python + environment (system or virtualenv) + +* A new enough version of CMake (3.16+). + +* ninja + +For Windows you will also need: + +* a Visual Studio environment to be active in your terminal + +* Correct visual studio architecture chosen (32 vs 64 bit) + +* Make sure that your Python intepreter and bindings project build + configuration is the same (all Release, which is more likely, + or all Debug). + +The build uses the ``pyside_config.py`` file to configure the project +using the current PySide/Shiboken installation. + +Using CMake +=========== + +You can build and run this example by executing the following commands +(slightly adapted to your file system layout) in a terminal: + +macOS/Linux: + +.. code-block:: bash + + cd ~/pyside-setup/examples/samplebinding + +On Windows: + +.. code-block:: bash + + cd C:\pyside-setup\examples\samplebinding + +.. code-block:: bash + + mkdir build + cd build + cmake -S.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=cl.exe + ninja + ninja install + cd .. + +Use the Python module ++++++++++++++++++++++ + +The final example can then be run by: + +.. code-block:: bash + + python main.py + +In the ``main.py`` script, two types are derived from :code:`Icecream` for +different “flavors” after importing the classes from the :code:`Universe` +module. Then, a :code:`truck` is created to deliver some regular flavored +Icecreams and two special ones. + +If the delivery fails, a new :code:`truck` is created with the old flavors +copied over, and a new *magical* flavor that will surely satisfy all customers. + +Try running it to see if the ice creams are delivered. + +Windows troubleshooting ++++++++++++++++++++++++ + +It is possible that ``CMake`` can pick up the wrong compiler +for a different architecture, but it can be addressed explicitly +by setting the ``CC`` environment variable: + +.. code-block:: bash + + set CC=cl + +passing the compiler on the command line: + +.. code-block:: bash + + cmake -S.. -B. -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe + +or by using the -G option: + +.. code-block:: bash + + cmake -S.. -B. -G "Visual Studio 14 Win64" + +If the ``-G "Visual Studio 14 Win64"`` option is used, a ``sln`` file +will be generated, and can be used with ``MSBuild`` +instead of ``ninja``. +The easiest way to both build and install in this case, is to use +the cmake executable: + +.. code-block:: bash + + cmake --build . --target install --config Release + +Note that using the ``"Ninja"`` generator is preferred to +the MSBuild one, because the MSBuild one generates configs for both +Debug and Release, and this might lead to building errors if you +accidentally build the wrong config at least once. + +Virtualenv Support +++++++++++++++++++ + +If the python application is started from a terminal with an activated +python virtual environment, that environment's packages will be used for +the python module import process. +In this case, make sure that the bindings were built while the +``virtualenv`` was active, so that the build system picks up the correct +python shared library and PySide6 / shiboken package. + +Linux Shared Libraries Notes +++++++++++++++++++++++++++++ + +For this example's purpose, we link against the absolute path of the +dependent shared library ``libshiboken`` because the +installation of the library is done via a wheel, and there is +no clean solution to include symbolic links in a wheel package +(so that passing -lshiboken to the linker would work). + +Windows Notes ++++++++++++++ + +The build config of the bindings (Debug or Release) should match +the PySide build config, otherwise the application will not properly +work. + +In practice this means the only supported configurations are: + +#. release config build of the bindings + + PySide ``setup.py`` without ``--debug`` flag + ``python.exe`` for the + PySide build process + ``python39.dll`` for the linked in shared + library. + +#. debug config build of the application + + PySide ``setup.py`` *with* ``--debug`` flag + ``python_d.exe`` for the + PySide build process + ``python39_d.dll`` for the linked in shared + library. + +This is necessary because all the shared libraries in question have to +link to the same C++ runtime library (``msvcrt.dll`` or ``msvcrtd.dll``). +To make the example as self-contained as possible, the shared libraries +in use (``pyside6.dll``, ``shiboken6.dll``) are hard-linked into the build +folder of the application. |