I would like to return to a topic that I opened few months ago – about creation of “hybrid” applications consisting of one part of c++ code and another part is python based, plus both part access each other with Qt-like Api, so they understand QString, etc and can exchange qt signals between those two parts. Even more, c++ part of application can generate python qt-like code to execute during runtime, he-he… In my previous post I explained how to implement this based on PyQt libraries. Also it can be interpreted as embedding PySide into Qt applications.
I got a lot of questions about this ideas, but one of frequent questions was about doing same with PySide. Well, PySide is LGPL so this makes the framework applicable for much more projects. So, let’s have a try of doing same but with PySide now.
First, I tried binary packages, but looks like they do not have generatorrunner executable included and other stuff for making bindings for Qt-like libraries – let’s wish for maintainers to include them. So, we need to build it manually. Needed CMake, installed Qt Frameworks, Python.
So I followed instructions listed on PySide web site and got it after hour-two. Let’s better show step-by-step for sure (I have done it on Mac OS 10.6, Qt 4.7 binaries from Nokia, Python 2.6):
# git clone git://gitorious.org/pyside/apiextractor.git # cd apiextractor # mkdir build && cd build # cmake .. && make && make install && cd ../.. # # git clone git://gitorious.org/pyside/generatorrunner.git # cd generatorrunner # mkdir build && cd build # cmake .. && make && make install && cd ../.. # # git clone git://gitorious.org/pyside/shiboken.git # cd shiboken # mkdir build && cd build # cmake .. && make && make install && cd ../.. # # git clone git://gitorious.org/pyside/pyside.git # cd pyside # mkdir build && cd build # cmake .. && make && make install && cd ../..
Now we have additionaly generatorrunner and typesystems needed for making wrappings to c++ mudules.
Let’s return to our hybrid application – create next folder structure:
HybridApp/ |-data/ | |-global.h | |-typesystem.xml |-hybrid/ | |-MainWindow.h | |-MainWindow.cpp | |-hybrid.pro |-hybridpy/ | |-hybridpy.pro |-build.sh |-Main.py
Let’s explain:
- Folder hybrid contains c++ part of application which is built into shared lib.
- Folder hybridpy contains wrapping for c++ part – it builds Python module which will be imported into python part of application.
- Folder data contains definition of typesystem used in c++ part. This is Xml file which describes types of used objects and their especialities when converting into python objects. More details about typesystems – on pyside page with following links.
Now we go into hybrid folder and create c++ part of hybrid. First let’s do it as c++ only application with own main() methon.
hybrid/hybrid.pro
TEMPLATE = app CONFIG += qt QT += core gui UI_DIR = build RCC_DIR = build MOC_DIR = build OBJECTS_DIR = build HEADERS += MainWindow.h SOURCES += MainWindow.cpp Main.cpp
hybrid/Main.cpp:
#include <QtGui> #include "MainWindow.h" int main(int argc, char ** argv) { QApplication app(argc, argv); MainWindow window; window.resize(1000,700); window.show(); return app.exec(); }
hybrid/MainWindow.h:
#ifndef MainWindow_H #define MainWindow_H #include <QMainWindow> class QPushButton; class QGraphicsView; class QGraphicsScene; class QPlainTextEdit; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget * parent = 0L); virtual ~MainWindow(); signals: void runPythonCode(QString); private slots: void runPythonCode(); public: QGraphicsView * viewer; QGraphicsScene * scene; QPlainTextEdit * editor; QPushButton * pb_commit; }; #endif // MainWindow_H
hybrid/MainWindow.cpp:
#include <QtGui> #include "MainWindow.h" MainWindow::MainWindow(QWidget * parent):QMainWindow(parent) { QSplitter * splitter = new QSplitter; setCentralWidget(splitter); QWidget * editorContent = new QWidget; splitter->addWidget(editorContent); QVBoxLayout * layout = new QVBoxLayout; editorContent->setLayout(layout); editor = new QPlainTextEdit; layout->addWidget(editor); pb_commit = new QPushButton(tr("Commit")); connect(pb_commit, SIGNAL(clicked()), this, SLOT(runPythonCode())); layout->addWidget(pb_commit); scene = new QGraphicsScene(this); viewer = new QGraphicsView; viewer->setScene(scene); splitter->addWidget(viewer); splitter->setSizes(QList<int>() << 400 << 600); } MainWindow::~MainWindow() {;} void MainWindow::runPythonCode() { emit runPythonCode(editor->toPlainText()); }
As result we have a window with editor (left) and canvas (right):
Now it is time to to turn it into PySide-based application. We are going to:
- Change c++ part to dynamic library
- Create wrappings for MainWindow class
- Make Main.py instead of C main() routine
For first point we just change TEMPLATE to lib, exclude Main.cpp, and build the shared lib. Also set TARGET to have shared libraries in root folder of HybridApp
hybrid/hybrid.pro:
TEMPLATE = lib TARGET = ../Hybrid CONFIG += qt QT += core gui UI_DIR = build RCC_DIR = build MOC_DIR = build OBJECTS_DIR = build HEADERS += MainWindow.h SOURCES += MainWindow.cpp
As result you should get libHybrid.dylib in root of our project (HybrydApp)
Now it is time to generate wrappings for c++ part to use in python. The generation is made by build.sh script in root of project. (Actually it builds everything – both c++ and wrapping parts)
build.sh
#!/bin/sh cd hybrid qmake make cd .. cd hybridpy QTGUI_INC=/Library/Frameworks/QtGui.framework/Versions/4/Headers QTCORE_INC=/Library/Frameworks/QtCore.framework/Versions/4/Headers QTTYPESYSTEM=/usr/local/share/PySide/typesystems generatorrunner --generatorSet=shiboken \ ../data/global.h \ --include-paths=../hybrid:$QTCORE_INC:$QTGUI_INC:/usr/include \ --typesystem-paths=../data:$QTTYPESYSTEM \ --output-directory=. \ ../data/typesystem.xml qmake make cd .. rm -rf PyHybrid.so ln -s libPyHybrid.dylib PyHybrid.so
You see that this is just one call of generatorrunner plus you provide paths for Qt includes, qt typesystem and your typesystem. Then in folder hybridpy we with qmake build the python module.
Listing of our typesystem.xml and global.h:
data/typesystem.xml
<?xml version="1.0"?> <typesystem package="PyHybrid"> <load-typesystem name="typesystem_core.xml" generate="no"/> <load-typesystem name="typesystem_gui.xml" generate="no"/> <object-type name="MainWindow"/> </typesystem>
data/global.h (fyi: more details about global.h – here)
#undef QT_NO_STL #undef QT_NO_STL_WCHAR #ifndef NULL #define NULL 0 #endif #include <MainWindow.h>
And pro file for building python module:
hybridpy/hybridpy.pro
TEMPLATE = lib QT += core gui INCLUDEPATH += hybrid INCLUDEPATH += ../hybrid INCLUDEPATH += /usr/include/python2.6 INCLUDEPATH += /usr/local/include/shiboken INCLUDEPATH += /usr/local/include/PySide INCLUDEPATH += /usr/local/include/PySide/QtCore INCLUDEPATH += /usr/local/include/PySide/QtGui LIBS += -ldl -lpython2.6 LIBS += -lpyside LIBS += -lshiboken LIBS += -L.. -lHybrid TARGET = ../PyHybrid SOURCES += \ pyhybrid/pyhybrid_module_wrapper.cpp \ pyhybrid/mainwindow_wrapper.cpp \
You can mention that most of used paths of python headers and qt headers can be extracted from utils like pkg-config etc – that’s true, but I showed exact paths intentionally just for demoing what is really included. Also, of course you can do it all with cmake, but I made a choice for qmake pro files to have better (plain) presentation of used files and logic.
Looks complete – if you just run build.sh you will get PyHybrid.so – python module.
Everything is ready, time to connect both parts in Main.py script:
Main.py
import sys from PySide.QtCore import * from PySide.QtGui import * from PyHybrid import * class RunScript(QObject): def __init__(self, mainWindow): QObject.__init__(self) self.mainWindow = mainWindow def runScript(self, script): mainWindow = self.mainWindow exec(str(script)) a = QApplication(sys.argv) w = MainWindow() r = RunScript(w) w.setWindowTitle('PyHybrid') w.resize(1000,800) w.show() a.connect(w, SIGNAL('runPythonCode(QString)'), r.runScript) a.connect(a, SIGNAL('lastWindowClosed()'), a, SLOT('quit()') ) a.exec_()
If you run it you will get same window as in c++ application except that python code entered in left panel with editor can be executed just here and the python code has control of our c++ part.
Let’s try small code snippets like:
mainWindow.statusBar().show()
Oh! status bar is appeared in our application. Well, more complex maybe – let’s change background of canvas:
mainWindow.scene.setBackgroundBrush(QColor('#e0e0ff'))
Can we create new objects? Of course yes:
li1 = QGraphicsLineItem(10,10, 500,500) li1.setPen(QPen(QBrush(QColor("#ff0000")), 3.0, Qt.DashLine)) mainWindow.scene.addItem(li1)
Can we create new classes just in real time and instanciate them???
Sure yes!!!. Let’s create subclass of QGraphicsItem and make custom painting:
mainWindow.viewer.setRenderHint(QPainter.Antialiasing) class MyItem(QGraphicsItem): def boundingRect(self): return QRectF(-100,-100,200,200) def paint(self, painter, option, widget): g = QLinearGradient(-100,-100, 100,100) g.setColorAt(0, QColor('#00ff00')) g.setColorAt(1, QColor('#ffffff')) painter.setBrush(g) p = QPen(QBrush(QColor("#ff0000")), 4, Qt.DashLine) painter.setPen(p) painter.drawRoundedRect(-100,-100,200,200, 30,30) my1 = MyItem() mainWindow.scene.addItem(my1) my1.setPos(200,200)
Well, so we can do everything with our small application – we can program it in real time. We can even generate code by program itself to execute it and more. Anyway, this also can be used for performance balancing when you can quickly prototype your code in python, and then transfer such code into C++ to make it running fast.
Good example – developing canvas-based games, when it can be really helpful and convenient to work with python code to make the logic, drawing etc and then migrate some code into C++.
Also it is very good for mobile devices – you program with python-pyside until you encounter performance issues and in this point you can easily separate critical part into c++ and keep Qt-like interface between these two parts.
Regarding our small application above – you can ask What is the difference to just programming with your editor? – You are coding your application while it is running. This is very new approach to programming with Qt. There is one especiality – you can loose your programming when exit :), So, it makes sense to modify the application to save all your code snippets to files.
It’s up to you.
I would like to thanks to PySide team. They are doing really great work and potential of PySide looks very exciting to be used in many projects.
16 Comments
Great. Awesome. That’s what I needed to start with PySide.
I need exactly this solution for some test application.
You may want to suggest to download the latest tag version using git .
git tag -l # list tags
git checkout
Hi budy, awsome job u did. I tried this demo but using Ubuntu, things were quite successful except that when I execute Main.py it returns this error to me:
$ python Main.py
Traceback (most recent call last):
File “Main.py”, line 17, in
w.setWindowTitle(‘PyHybrid’)
AttributeError: ‘PyHybrid.MainWindow’ object has no attribute ‘show’
Segmentation fault
Any idea about that??
Thanks in advance
Because it does not see show() method, I assume you have not bound correct with Qt typesystem. Have a look and check QTTYPESYSTEM in build.sh – make sure you have these files because they exactly describe methods and properties of Qt classes
I’ve fixed this problem. The point was that generatorrunner was not asumming to know QtGui inside /usr/include path in –include-paths so I tried to fixit putting /usr/include/qt4 and din’t work. So it worked successfully putting /usr/include/qt4/QtGui explicitly. I don’t know why its happening because generator runner should find qt include subdirs automatically. Nevertheless I’m still having this kind of warnings.
Any idea is welcome.
$ sh build.sh
make: Nothing to be done for `first’.
Generating class model… [OK]
Generating enum model… [OK]
Generating namespace model… [OK]
Resolving typedefs… [OK]
Fixing class inheritance… [OK]
Detecting inconsistencies in class model… [OK]
Detecting inconsistencies in typesystem… [WARNING]
type ‘QAbstractPageSetupDialog’ is specified in typesystem, but not defined. This could potentially lead to compilation errors.
type ‘QSortFilterProxyModel’ is specified in typesystem, but not defined. This could potentially lead to compilation errors.
type ‘QSpacerItem’ is specified in typesystem, but not defined. This could potentially lead to compilation errors.
…
2 Ernesto: why not ask PySide people? their build system could change meanwhile…
Ok, thanks I´ll ask aorund pyside comunity
Hi, I’m trying to run the build.sh but it fails with the message below: How can I create the Qt typesystem? Is this why I’m getting these errors?
Thanks/Bruno.
$ ./build.sh
Fatal error: line=3, column=62, message=Failed to parse: ‘typesystem_core.xml’
Cannot parse file: ../data/typesystem.xmlWARNING: Failure to find: pyhybrid/pyhybrid_module_wrapper.cpp
WARNING: Failure to find: pyhybrid/mainwindow_wrapper.cpp
Makefile:121: *** multiple target patterns. Stop.
Thanks for a great article!
If anyone is still interested i’ve managed to make
it work with latest python2.7 and pyside from
macports: fetch it here.
Is it possible to use pyside for calling python from C++ so one doesn’t have to start the application from python?
$./build.shn…ngeneratorrunner: Error loading generator-set plugin: shiboken_generator module not found.n…nnHow to access for shiboken module? Debian wheezy linux. PySide and others installed, “HelloWorld” example run successfully.
So generatorrunner can’t see shiboken and I edit build.sh as:nn#generatorrunner –generatorSet=shiboken
# ../data/global.h
# –include-paths=../cps::/usr/include:/usr/local/include
# –typesystem-paths=../data:
# –output-directory=.
# ../data/typesystem.xmlnnshiboken
–include-paths=../hybrid:::/usr/include
–typesystem-paths=../data:
–output-directory=.
../data/global.h
../data/typesystem.xmlnnNow all libs build fine, but when I try run: nn Main.pynTraceback (most recent call last):n File “Main.py”, line 4, in n from PyHybrid import *nImportError: libHybrid.so.1: cannot open shared object file: No such file or directorynn$ ls libHybrid.so.1nlibHybrid.so.1nn
ok, do this:n LD_LIBRARY_PATH=/home/user/devel/HybridApp:
how to generate wrapper ,by generatorrunner ? please tell me where to donwnload binary
Hi, nnafter shiboken runs through, when it tries building hypridpy, ni get this Error: “WARNING: Failure to find: PyHybrid/mainwindow_wrapper.cpp” nand then fails latter in the building because of this missing file.nnAny idea why this file isn’t getting generated? nnPyHybrid/pyhybrid_module_wrapper.cpp is generated just finenn(Using Mac Yosemite with shiboken 1.2.2, python 2.7, qt 4.8.6, pyside 1.2.2 installed over homebrew)
One Trackback
[…] to be able to call a C++ Qt library from Python? It turns out it’s pretty easy to do. This tutorial explains how to do it in Mac OS X, but it’s slightly out of date. I’d like to provide the world with a way to make it […]