[PyKDE] embedding python widgets in C++ app

David Boddie david at boddie.org.uk
Tue Jan 23 00:08:50 GMT 2007


On Monday 22 January 2007 10:31:08 -0900, Patrick Stinson wrote:

> Well, the function above would return the widget instance to code that
> would call QWidget::setParent(). The only goal is to write a widget in
> python and use it in my C++ app. If there is a better way..

OK. Let's take a look at some code that might do want you want. I'm pasting
some existing code into an e-mail message and editing it heavily, so all the
usual disclaimers apply. I'm sure people will say if there's something wrong
with it.

The widget is returned by a C++ function:

QWidget *createWidget()
{
    if (!Py_IsInitialized()) {
        QLibrary *library = new QLibrary(PYTHON_LIB);
        library->setLoadHints(QLibrary::ExportExternalSymbolsHint);
        library->load();
        Py_Initialize();
    }

We start by checking whether the interpreter is initialized and, if not,
dynamically load the interpreter library before initializing it. We're
using a specific flag with QLibrary to allow Python itself to load shared
library modules. PYTHON_LIB is the path to the library.

We then import some useful modules and ensure that the module that contains
the Python widget implementation can be found by the import system by
appending its path to the list in the sys module:

    // Locate the path to the Python module and append it to sys.path list.
    PyRun_SimpleString("import os, sys\n");
    PyRun_SimpleString("sys.path.append('"MODULE_PATH"')\n");

MODULE_PATH is defined to be a string here. I think I could probably have
removed all the unnecessary quotes.

    PyObject *pyModule = PyImport_ImportModule(MODULE_NAME);
    // pyModule is a new reference.

    if (!pyModule) {
        qDebug() << "***failed to import module\n";
        PyErr_Print();
        return 0;
    }

If the module defined by MODULE_NAME couldn't be imported, we'll report the
error and return 0.

To create an instance of the widget, we obtain the module's dictionary and
create a factory function in it:

    PyObject *pyDict = PyModule_GetDict(pyModule);
    /* pDict is a borrowed reference */

    Py_XDECREF(pyModule);

    PyRun_String("from PyQt4 import QtGui\nimport sip\n"
                 "def __embedded_factory__(parent = None):\n"
                 "    widget = "CLASS_NAME"(parent)\n"
                 "    return widget\n",
                 Py_file_input, pyDict, pyDict);

It's probably just as easy to call the class's constructor directly, but I
wanted to do other things besides that.

We obtain the factory we just created from the module, checking that it was
created properly:

    // Get the Python module's factory function from the header file at
    // compile time.
    PyObject *pyFactory = PyDict_GetItemString(pyDict,
                          "__embedded_factory__");
    // pyFactory is a borrowed reference.

    if (!pyFactory) {
        qDebug() << "Failed to create embedded functions.\n";
        return 0;
    }

Again, if there was an error, we just return 0.

Now, we can call the function and obtain a Python wrapper around a widget:

    // Instantiate the class and receive a pointer.
    PyObject *pyWidget = call_function(pyFactory, 0);
    // pyWidget is a new reference to a sip wrapper instance.

This should be OK. The call_function() function is not given here, but it
just creates an empty argument tuple and calls the pyFactory object using
PyObject_CallObject().

The following code to extract a pointer to the widget is probably not OK
because it assumes that the object is a valid wrapper and just accesses
its structure directly to get the pointer:

    QWidget *widget = 0;
    void *cppPtr = ((sipWrapper*)pyWidget)->u.cppPtr;
    if (cppPtr)
        widget = (QWidget *)cppPtr;

    return widget;
}

As far as I can see this late at night, I've just returned a pointer to a
widget, wrapped in Python, back to the caller. There should be a single
reference to this wrapper that we lose track of when we return.

Ideally, we'd transfer ownership of the widget to C++ and decrement the
reference count of the wrapper - or would we? It seems to me that we need
to pass a parent widget to the new widget, transfer ownership to that, then
decrement the reference count. I have another version of the above code that
does that, but there's already far too much code in this message. I'll post
it in a separate message if you want.

David




More information about the PyQt mailing list