Skip to main content

rootwin

There are a few ways of embedding C++ into python. All of them requires cling. Unlike what many people believe, there exist a working C++ implementation that is able to execute C++ code by interpreting it. Namely, cling. Moreover, there is a C++ library called root that accesses this interpreter programmatically. Additionally, root has python bindings which support C++17 but they only work on linux. Also, there is a seperate library called cppyy which works on windows if you manage to win the fight against its broken build system. By the way, it claims to support C++20 by default, but in practice it only supports C++14 and there is no way of increasing this revision. Lastly there is this library, rootwin, which works on windows and also supports C++17 and it's not fake. However, it's interoperability capabilities are more limited compared to root and cppyy. Specifically, C++ symbols you define are not automatically wrapped into python objects and made available to python. Nonetheless, you can use some techniques which are explained below to get some amount of interoperability.

Installation

pip install rootwin

Python Api

rootwin.ProcessLine(code)

Execute the given C++ code (or cling metacommand). code can be a bytes literal. code will be converted to ctypes.c_char_p since this is actually a dll function. Unlike what the name indicates, code can contain multiple lines. The only reason why this name is chose is to emphesize that this function does nothing but calls the upstream C++ root library's ProcessLine() function.

rootwin.root_interface

This is the underlying ctypes.CDLL instance. Currently, no functions other than ProcessLine() are exported since the need haven't arised.

C++ Api

This is the api available to the C++ code executed by rootwin.ProcessLine().

template<typename T>
T py_to_pointer(const char *name, bool local = true)

Convert a python int to a C++ pointer and return the result. T can be any type that can be given to reinterpret_cast<target-type>(expression) as a target type. name is expected to be the name of a python int. Expected scope of this int is determined by the local argument. If local is true, name is searched in the local variables of the currently executing python function. Otherwise, it is searched in python globals.

PyOStream pyout;

An alternative to std::cout. The problem is when you use an IDE that hooks sys.stdout (e.g, IDLE) std::cout still writes to the original stdout. This is from IDLE docs:

By default, IDLE runs user code in a separate OS process rather than in the user interface process that runs the shell and editor. In the execution process, it replaces sys.stdin, sys.stdout, and sys.stderr with objects that get input from and send output to the Shell window.

Write operations to original stdout are effectless since the user process' console window is invisible. In order to solve this problem, rootwin redirects std::cout and std::cerr to sys.stdout and sys.stderr. Rootwin achieves this by using named pipes. A side effect of that is early return from a stream insertion operation, before the data shows up in the shell window. The reason for that is, such an operation completes when the data is sent to the named pipe, but not necessarily after it is read from the named pipe by a worker thread which has been created beforehand. Data will be printed only after the worker thread reads it and sends it to sys.stdout or sys.stderr. If you want the stream insertion operation to block until the data is printed to screen, use pyout instead of std::cout.

Passing C++ objects to python

Unlike cppyy and root libraries, manual work is needed to access C++ objects from python. High level steps are below:

  1. Prepare the python world for the next steps by instantiating a ctypes.c_longlong. This will be our "out" argument.
  2. Take the address of this variable and assign it to another variable
  3. Call rootwin.ProcessLine(code) where code is a 2 liner trick which exports the address of any C++ object to python.
  4. In the first line, call py_to_pointer<long long*>("p2") where "p2" is the name of the python variable we created in the step 2.
  5. Next, dereference the pointer returned by py_to_pointer and assign the address of the C++ object you want to access from python to it.
  6. ctypes.c_longlong we created in the first step will be updated by the operation we performed on step 5.
  7. Convert the ctypes data type to an ordinary python int by accessing its value attribute.
  8. Use pyptrs in order to dereference the final int.

Example:

import ctypes
import rootwin
import pyptrs

pointer = ctypes.c_longlong(0)
pointer2 = ctypes.addressof(pointer)
rootwin.ProcessLine(b"int cpp_var = 100;")
rootwin.ProcessLine(b"""long long *pointer2 = py_to_pointer<long long*>("pointer2");""")
rootwin.ProcessLine(b"""*pointer2 = reinterpret_cast<long long>(&cpp_var);""")
cpp_pointer = pyptrs.pointer_to_address(pointer.value, ctype = ctypes.c_int)
cpp_var = pyptrs.dereference(cpp_pointer)
var = cpp_var.value
print(var) # prints 100

This is a rather simple example but by applying the same logic, you can also transfer more complex objects to python. This is possible since pyptrs supports pointers to custom ctypes structures.

Another example:

import ctypes
import rootwin
import pyptrs

pointer = ctypes.c_longlong(0)
pointer2 = ctypes.addressof(pointer)
code = b"""
struct A
{
    int a = 10;
    int b = 20;
    int c = 30;
};

A cpp_object;
"""
rootwin.ProcessLine(code)
rootwin.ProcessLine(b"""long long *pointer2 = py_to_pointer<long long*>("pointer2");""")
rootwin.ProcessLine(b"""*pointer2 = reinterpret_cast<long long>(&cpp_object);""")
class A(ctypes.Structure):
    _fields_ = [("a", ctypes.c_int),
                ("b", ctypes.c_int),
                ("c", ctypes.c_int)]
    
cpp_pointer = pyptrs.pointer_to_address(pointer.value, ctype = A)
cpp_object = pyptrs.dereference(cpp_pointer)
b = cpp_object.b
print(b)

Metadata

Release files for rootwin 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rootwin 1.1.0
File Size Uploaded
rootwin-1.1.0.tar.gz 64.0 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for rootwin 1.1.0
File Interpreter ABI Platform
rootwin-1.1.0-py2.py3-none-any.whl Python 2, Python 3 none any Details

Total release size: 130.2 MB

Release files / rootwin-1.1.0.tar.gz

Download URL rootwin-1.1.0.tar.gz
Size 64.0 MB
Tags Source
SHA-256 checksum
How to use checksums
41449f600f117ad46965a7577cb7ad5c74087ed07f87ccbc7c7f1f1cb94eb129
BLAKE2b-256 checksum
How to use checksums
4c1796dd58d65c3414b143d26a5fa5a8e445a192a1792ed5db90e71dfd67a603
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.0

Release files / rootwin-1.1.0-py2.py3-none-any.whl

Download URL rootwin-1.1.0-py2.py3-none-any.whl
Size 66.3 MB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
76e8a3b39404020d9f823166197ae2052bfb3622fe70180961ae83e80dc88d97
BLAKE2b-256 checksum
How to use checksums
c896c355f8a15a4537b86df3ab55dabd2909aa25891d0c1210d13a46aa75e5f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.0

Release history Release notifications | RSS feed

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

This release

1.1.0 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page