GNU Radio 3.9 OOT Module Porting Guide

The major changes in the (in-progress) GNU Radio 3.9 release that will impact OOTs are:


 * C++ modernization (C++11/14?)
 * Replacement of SWIG with Pybind11

C++ Modernization
Boost shared pointers have been replaced with std:: shared pointers and memory management. At the top level of each block, the instantiation will need to change, e.g.

In include/blockname_xx.h:

typedef std::shared_ptr sptr;

Inbound message ports receive an update too. We move from boost::function to std::function. This affects how message handlers are registered. The preferred style is to use lambdas which is already compatible with GNU Radio 3.8:

set_msg_handler(pmt::mp("message"), [this](pmt::pmt_t msg) { this->handle_msg(msg); });

Pybind11 Python Bindings
As of the GNU Radio 3.9 release, python bindings are handled using pybind11, which is inherently different than they were in previous releases

Dependencies
curl -Lo pybind11.tar.gz https://github.com/pybind/pybind11/archive/v2.4.3.tar.gz mkdir pybind11 && tar xzf pybind11.tar.gz -C pybind11 --strip-components=1 && cd pybind11 mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DPYBIND11_TEST=OFF make && make install
 * pybind11 &gt; 2.4.3 https://pybind11.readthedocs.io/
 * pip does not provide the proper cmake (https://github.com/pybind/pybind11/issues/1379)
 * For some distributions (e.g. Ubuntu <20.04), this will need to be installed from source as 2.4.3 is not the supplied version with package managers


 * pygccxml https://pygccxml.readthedocs.io/en/develop/install.html
 * This is an optional dependency and basic functionality for OOT generation can be performed without pygccxml
 * It is required for automatically generating bindings for most of the GR source tree

Components
Python bindings are contained in the  directory

The bindings for each block exist in blockname_python.cc under the  directory. Additionally, a template header file for each block that is used as a placeholder for the scraped docstrings lives in the  dir

python_bindings.cc
The structure of this file is

Comment Block
Each block binding file contains an automatically generated and maintained comment block that informs CMake when the bindings are out of sync with the header file they refer to, and what to do about it

In this case, the flag should be set to 0, and the bindings need to be updated manually. If the flag is set to 1, CMake will override the binding file in the source tree when it detects out of sync bindings. This should only be done in simple cases.

If a block needs pygccxml for the bindings to be properly generated automatically, this should be set to





Out-of-Tree modules
The steps for creating an out of tree module with pybind11 bindings are as follows:


 * 1) Use   to create an out of tree module and add blocks

Update the parameters or functions in the public include file and rebind with 

NOTE: without pygccxml, only the make function is currently accounted for, similar to

If the public API changes, just call  to regenerate the bindings

When the public header file for a block is changed, CMake will fail as it checks the hash of the header file compared to the hash stored in the bindings file until the bindings are updated

Build and install

Docstrings
If Doxygen is enabled in GNU Radio and/or the OOT, Docstrings are scraped from the header files, and placed in auto-generated  files in the build directory on compile. Generated templates (via the binding steps described above) are placed in the  directory and are used as placeholders for the scraped strings

Upon compilation, docstrings are scraped from the module and stored in a dictionary (using ) and then the values are substituted in the template file (using  )

OOT Migration
The easiest way to migrate an OOT to 3.9 is to use  to create a new OOT, use   to create the blocks, and copy code from the previous OOT.

Steps to do this without regenerating a new module are TBD

Caveats
Pybind11 bound methods do not implicitly convert int to enum, so blocks that take enum as input, must have either "raw" or "enum" in the grc yml definition of the block. "Raw" will allow the value to be changed by another variable in the flowgraph.

Block inheritance must be specified completely in the python bindings in order to use the inherited methods. For instance, if a block inherits from sync_block, both block and basic_block must be included in the inheritance specification of the class:

py::class_>(       m, "atsc_interleaver", D(atsc_interleaver))

If your OOT module uses types from or its classes derive from another gr module, it is necessary in  to specify these modules.

For instance, since all OOT modules require the base block types, there is a line.

If you wanted to utilize  objects in your OOT, it would be necessary to add   so that pybind knows to use the bindings already compiled into gnuradio.digital for the constellation objects

If your OOT module uses other classes as parameter, you must either setup a python binding for this class or use an existing one. An example, how to setup a binding for the QWidget class of QT5 used in gr-qtgui, can be found here

If you want to use this binding in your OOT module, you have to import it. This has to be done in the python_bindings.cc inside the PYBIND11_MODULE macro. In case of the QWidget usage this would be

Using default values
For standard types like int,float, etc. you can set default values as usual. gr_modtool bind blockname will setup the correct bindings in blockname_python.cc.

But if you use something like  gr_modtool generates code like

But at this point pybind does not know the type of parent, so you have to modify the binding code and cast the type.

Unable to find pydoc.h
fatal error: blockname_pydoc.h: No such file or directory 28 | #include      |          ^ compilation terminated.

blockname_pydoc.h is generated during compilation based on the template in the docstring directory. When the block is first created in blocktool, this template does not exist. Run  to generate the appropriate template used as a placeholder for the scraped docstrings

Also, the scraping of docstrings only takes place at CMake time, so it may be necessary to do a  to re-trigger the scraping

TypeError: 'modulename_python.blockname' object is not subscriptable
This is caused by an incomplete inheritance chain specified in the binding declaration of the block.

Instead of

Try something like (taking into account your block type)