aboutsummaryrefslogtreecommitdiffstats
path: root/examples/samplebinding/README.md
diff options
context:
space:
mode:
authorAlexandru Croitor <alexandru.croitor@qt.io>2018-05-08 14:15:57 +0200
committerAlexandru Croitor <alexandru.croitor@qt.io>2018-05-16 09:11:43 +0000
commit15273fe0fe52569013dd4811bf9ed770ce7fb287 (patch)
treef58eab1b18998592f8f9dd541232b58253529aad /examples/samplebinding/README.md
parent9d9144b2b44677d2862e389b7a83900ee3e8e44c (diff)
Add an example that demonstrates bindings to a custom C++ library
A CMake project is included that builds two shared libraries: 1) libuniverse - a hypothetical C++ library for which bindings need to be created. 2) Universe - a Python module containing bindings to the above library. The example showcases the following concepts: * primitive type bindings (bool, std::string) * types with object and value semantics (pass by pointer VS pass by copy) * inheritance and overriding virtual methods * ownership of heap-allocated C++ objects * constructors with default parameters * general structure of CMakeLists.txt file for generating bindings Task-number: PYSIDE-597 Change-Id: I7b0f203e2844e815aa611af3de2b50a9aa9b5bfc Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Diffstat (limited to 'examples/samplebinding/README.md')
-rw-r--r--examples/samplebinding/README.md223
1 files changed, 223 insertions, 0 deletions
diff --git a/examples/samplebinding/README.md b/examples/samplebinding/README.md
new file mode 100644
index 000000000..85e96ddbe
--- /dev/null
+++ b/examples/samplebinding/README.md
@@ -0,0 +1,223 @@
+# 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 three has to be taken into account (implement the
+copy constructor, assignment operator, 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++ primitive types should have bindings (int, bool, float)
+ * which C++ classes should have bindings (Icecream) and 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 `bool` and `std::string` as primitive types,
+`Icecream` as an object type, `Truck` as a value type,
+and 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).
+
+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.
+
+```
+from Universe import Icecream, Truck
+```
+
+Constructing C++ wrapped objects is the same as in Python
+```
+icecream = Icecream("vanilla")
+truck = Truck()
+```
+
+
+And actual C++ constructors are mapped to the Python `__init__` method.
+```
+class VanillaChocolateIcecream(Icecream):
+ def __init__(self, flavor=""):
+ super(VanillaChocolateIcecream, self).__init__(flavor)
+```
+
+
+C++ methods can be accessed as regular Python methods using the C++
+names
+```
+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.
+```
+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 PySide2 package is installed into the current active Python
+ environment (system or virtualenv)
+* A new enough version of CMake (**3.1+**).
+
+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 `pyside2_config.py` file to configure the project
+using the current PySide2/Shiboken2 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:
+
+On macOS/Linux:
+```bash
+cd ~/pyside-setup/examples/samplebinding
+mkdir build
+cd build
+cmake -H.. -B. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release
+make
+make install
+python ../main.py
+```
+
+On Windows:
+```bash
+cd C:\pyside-setup\examples\samplebinding
+mkdir build
+cd build
+cmake -H.. -B. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
+# or if you have jom available
+# cmake -H.. -B. -G "NMake Makefiles JOM" -DCMAKE_BUILD_TYPE=Release
+nmake # or jom
+nmake install # or jom install
+python ..\main.py
+```
+
+#### Windows troubleshooting
+
+It is possible that **CMake** can pick up the wrong compiler
+for a different architecture, but it can be addressed explicitly
+using the -G option:
+
+```bash
+cmake -H.. -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 `nmake/jom`.
+The easiest way to both build and install in this case, is to use
+the cmake executable:
+
+```bash
+cmake --build . --target install --config Release
+```
+
+Note that using the "NMake Makefiles JOM" 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 PySide2 / 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 PySide2 build config, otherwise the application will not properly
+work.
+
+In practice this means the only supported configurations are:
+
+1. release config build of the bindings +
+ PySide2 `setup.py` without `--debug` flag + `python.exe` for the
+ PySide2 build process + `python36.dll` for the linked in shared
+ library.
+2. debug config build of the application +
+ PySide2 `setup.py` **with** `--debug` flag + `python_d.exe` for the
+ PySide2 build process + `python36_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 (`pyside2.dll`, `shiboken2.dll`) are hard-linked into the build
+folder of the application.