I’m trying to write a compact ImGui server interface, so I’m reinventing my own bike on the C++ side (but what else…). However, I came across an error that I partially understand, but do not understand how to resolve it, since my idea partially works. I use a python function callback in C++, the code works great if the function has a function type, if it has a method type, that is, it is a member of a class. I get these kinds of spam messages.
TypeError: TaskManager.keyboardInterruptHandler() missing 1 required positional argument: 'stackFrame'
My base class code python, where there are two functions, one global (works), the other is a class method (does not work).
from direct.showbase.ShowBase import ShowBase
from panda3d.imgui import PImGui
# Work
def instrution(index):
print(index)
class DemoImGui(ShowBase):
def __init__(self):
ShowBase.__init__(self)
base.exitFunc = self.exitfunc
# Create context ImGui
display_region = base.cam2d.node().get_display_region(0)
PImGui.init(display_region, self.instrution);
# Not work
def instrution(self, index):
print(index)
def exitfunc(self):
PImGui.destroy()
app = DemoImGui()
app.run()
The actual call to these functions is on the C++ side.
void PImGui::do_callback(CallbackData* cbdata){
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
PyObject* arglist = Py_BuildValue("(i)", index);
PyObject* result = PyObject_CallObject(instrution, arglist);
Py_DECREF(arglist);
if (result == NULL) { PyErr_Print(); }
else { Py_DECREF(result); }
PyGILState_Release(gstate);
cbdata->upcall();
}
This looks like a problem with threads, since disabling signal.SIG_IGN processing fixes the situation, but the function is not called in the derived thread.
rdb
January 4, 2026, 9:08am
2
I would guess you have forgotten to use Py_INCREF on the instrution object passed in from Python before storing it in C++.
Otherwise I need to see more code. Is this in a custom CallbackObject?
I have a test call that works if it was made from the interpreter, the problem starts if I use a callback on the C++ side.
I’ve cleaned up my code:
p3imgui.h
#ifndef P3IMGUI_H
#define P3IMGUI_H
#include "dtoolbase.h"
#include "displayRegion.h"
#include "callbackObject.h"
#include "callbackData.h"
#include <Python.h>
#include <stdexcept>
#include <cstdio>
static PT(DisplayRegion) display_region;
static PyObject* instrution = nullptr;
static bool is_context = false;
static int index = 0;
BEGIN_PUBLISH
class PImGui : public CallbackObject{
PUBLISHED:
public:
ALLOC_DELETED_CHAIN(PImGui);
PImGui(const PImGui&) = delete;
PImGui& operator=(const PImGui&) = delete;
static void init(PT(DisplayRegion) display_region, PyObject* function);
static void call_test();
void do_callback(CallbackData* cbdata);
private:
PImGui();
~PImGui();
};
END_PUBLISH
#endif
p3imgui.cpp
#include "p3imgui.h"
PImGui::PImGui(){
is_context = true;
}
PImGui::~PImGui(){
}
void PImGui::init(PT(DisplayRegion) dr, PyObject *fun){
if (is_context != true) {
if (Py_TYPE(fun) == &PyFunction_Type || Py_TYPE(fun) == &PyMethod_Type) {
instrution = fun;
display_region = dr;
PImGui* callback = new PImGui();
display_region->set_draw_callback(callback);
} else {
try { throw std::runtime_error ("PImGui: error, the second argument is not a function or method"); }
catch (const std::exception& error) { puts(error.what()); std::abort(); }
}
} else {
std::cout << "PImGui: warning, the context has already been created" << std::endl;
}
}
void PImGui::do_callback(CallbackData* cbdata){
PyGILState_STATE gstate = PyGILState_Ensure();
PyObject* arglist = Py_BuildValue("(i)", index);
PyObject* result = PyObject_CallObject(instrution, arglist);
Py_DECREF(arglist);
if (result == NULL) { PyErr_Print(); }
else { Py_DECREF(result); }
PyGILState_Release(gstate);
cbdata->upcall();
}
void PImGui::call_test(){
PyGILState_STATE gstate = PyGILState_Ensure();
PyObject* arglist = Py_BuildValue("(i)", index);
PyObject* result = PyObject_CallObject(instrution, arglist);
Py_DECREF(arglist);
if (result == NULL) { PyErr_Print(); }
else { Py_DECREF(result); }
PyGILState_Release(gstate);
}
main.py
from direct.showbase.ShowBase import ShowBase
from panda3d.imgui import PImGui
# Work
def instrution(index):
print("Call function: ", index)
class DemoImGui(ShowBase):
def __init__(self):
ShowBase.__init__(self)
display_region = base.cam2d.node().get_display_region(0)
PImGui.init(display_region, self.instrution);
PImGui.call_test()
# Not work do_callback(C++) / Work use call_test()
def instrution(self, index):
print("Call method: ", index)
app = DemoImGui()
app.run()
I’m not doing the check you specified, because in the init function, I’m checking the type.
I’ve narrowed down the range of problems, it seems to be a ShowBase problem after all and somehow related to the system’s signal processing.
This works great in an improvised alternative ShowBase class.
from panda3d.imgui import PImGui
from panda3d.core import *
# Work
def instrution(index):
print("Call function: ", index)
class MyShowBase():
def __init__(self):
pipe = GraphicsPipeSelection.get_global_ptr().make_default_pipe()
self.engine = GraphicsEngine.get_global_ptr()
fb_prop = FrameBufferProperties()
fb_prop.rgb_color = 1
fb_prop.color_bits = 24
fb_prop.depth_bits = 24
fb_prop.back_buffers = 1
self.win = self.engine.make_output(pipe, name="window", sort=0, fb_prop=fb_prop,
win_prop=WindowProperties(size=(800, 600)),
flags=GraphicsPipe.BF_require_window)
self.win.set_clear_color_active(True)
self.win.set_clear_color((0.5, 0.5, 0.5, 1))
self.render = NodePath("render")
self.cam = self.render.attach_new_node(Camera("camera"))
self.cam.node().set_lens(PerspectiveLens())
self.dr = self.win.make_display_region()
PImGui.init(self.dr, self.instrution);
self.dr.camera = self.cam
# Work
def instrution(self, index):
print("Call method: ", index)
def run(self):
while not self.win.is_closed():
self.engine.render_frame()
app = MyShowBase()
app.run()
rdb
January 4, 2026, 12:41pm
5
The issue is exactly as I suggested in my previous post. You’re not incrementing the reference count when storing instrution on the C++ side. Try:
instrution = Py_NewRef(fun);
@rdb
Thank you very much, using the link counting system provided by the python API fixed the situation.