#ifndef __PY_KERNWIN__ #define __PY_KERNWIN__ //------------------------------------------------------------------------ // //------------------------------------------------------------------------ //------------------------------------------------------------------------- // Context structure used by add|del_idc_hotkey() struct py_idchotkey_ctx_t { qstring action_name; ref_t pyfunc; py_idchotkey_ctx_t( const char *_action_name, PyObject *_pyfunc) : action_name(_action_name), pyfunc(borref_t(_pyfunc)) {} }; static ref_t py_colorizer; static void py_snapshot_restore_callback(const char *err_msg, void *userdata); //------------------------------------------------------------------------ // //------------------------------------------------------------------------ //------------------------------------------------------------------------ // //------------------------------------------------------------------------ //------------------------------------------------------------------------ static PyObject *py_register_timer(int interval, PyObject *callback) { PYW_GIL_CHECK_LOCKED_SCOPE(); if ( callback == nullptr || !PyCallable_Check(callback) ) Py_RETURN_NONE; // An inner class hosting the callback method struct tmr_t { static int idaapi callback(void *ud) { PYW_GIL_GET; py_timer_ctx_t *ctx = (py_timer_ctx_t *)ud; newref_t py_result(PyObject_CallFunctionObjArgs(ctx->pyfunc.o, nullptr)); int ret = -1; if ( PyErr_Occurred() != nullptr ) { msg("Exception in timer callback. This timer will be unregistered.\n"); PyErr_Print(); } else if ( py_result ) { ret = PyLong_AsLong(py_result.o); } // Timer has been unregistered? if ( ret == -1 ) python_timer_del(ctx); return ret; }; }; py_timer_ctx_t *ctx = python_timer_new(callback); ctx->timer_id = register_timer( interval, tmr_t::callback, ctx); if ( ctx->timer_id != nullptr ) { return PyCapsule_New(ctx, VALID_CAPSULE_NAME, nullptr); } else { python_timer_del(ctx); Py_RETURN_NONE; } } //------------------------------------------------------------------------ static bool py_unregister_timer(PyObject *timer_obj) { PYW_GIL_CHECK_LOCKED_SCOPE(); if ( timer_obj == nullptr || !PyCapsule_IsValid(timer_obj, VALID_CAPSULE_NAME) ) return false; py_timer_ctx_t *ctx = (py_timer_ctx_t *) PyCapsule_GetPointer(timer_obj, VALID_CAPSULE_NAME); if ( ctx == nullptr || !unregister_timer(ctx->timer_id) ) return false; python_timer_del(ctx); // invalidate capsule; make sure we don't try and delete twice PyCapsule_SetName(timer_obj, INVALID_CAPSULE_NAME); return true; } //------------------------------------------------------------------------ static PyObject *py_choose_idasgn() { char *name = choose_idasgn(); PYW_GIL_CHECK_LOCKED_SCOPE(); if ( name == nullptr ) { Py_RETURN_NONE; } else { PyObject *py_str = PyUnicode_FromString(name); qfree(name); return py_str; } } //------------------------------------------------------------------------ static PyObject *py_get_highlight(TWidget *v, uint32 flags=0) { qstring buf; uint32 lflags; bool ok = get_highlight(&buf, v, &lflags, flags); PYW_GIL_CHECK_LOCKED_SCOPE(); if ( !ok ) Py_RETURN_NONE; return Py_BuildValue("(sk)", buf.c_str(), lflags); } //------------------------------------------------------------------------ static int py_load_custom_icon_fn(const char *filename) { return load_custom_icon(filename); } //------------------------------------------------------------------------ static int py_load_custom_icon_data(PyObject *data, const char *format) { Py_ssize_t len; char *s; PYW_GIL_CHECK_LOCKED_SCOPE(); if ( PyBytes_AsStringAndSize(data, &s, &len) == -1 ) return 0; else return load_custom_icon(s, len, format); } //------------------------------------------------------------------------ static int py_msg(const char *message) { int rc = 0; if ( message != nullptr ) { SWIG_PYTHON_THREAD_BEGIN_ALLOW; rc = msg("%s", message); SWIG_PYTHON_THREAD_END_ALLOW; } return rc; } //------------------------------------------------------------------------ static int py_warning(const char *message) { int rc; if ( message != nullptr ) { SWIG_PYTHON_THREAD_BEGIN_ALLOW; rc = warning("%s", message); SWIG_PYTHON_THREAD_END_ALLOW; } return rc; } //------------------------------------------------------------------------ static void py_error(const char *message) { if ( message != nullptr ) { SWIG_PYTHON_THREAD_BEGIN_ALLOW; error("%s", message); } } //------------------------------------------------------------------------ PyObject *py_ask_text(size_t max_size, const char *defval, const char *prompt) { PYW_GIL_CHECK_LOCKED_SCOPE(); qstring qbuf; PyObject *py_ret; if ( ask_text(&qbuf, max_size, defval, "%s", prompt) ) { py_ret = PyUnicode_FromStringAndSize(qbuf.begin(), qbuf.length()); } else { py_ret = Py_None; Py_INCREF(py_ret); } return py_ret; } //------------------------------------------------------------------------ PyObject *py_ask_str(qstring *defval, int hist, const char *prompt) { PYW_GIL_CHECK_LOCKED_SCOPE(); PyObject *py_ret; if ( ask_str(defval, hist, "%s", prompt) ) { py_ret = PyUnicode_FromStringAndSize(defval->begin(), defval->length()); } else { py_ret = Py_None; Py_INCREF(py_ret); } return py_ret; } //------------------------------------------------------------------------ static bool py_process_ui_action(const char *name, int flags = 0) { return process_ui_action(name, flags, nullptr); } //------------------------------------------------------------------------ bool py_del_hotkey(PyObject *ctx) { PYW_GIL_CHECK_LOCKED_SCOPE(); if ( !PyCapsule_IsValid(ctx, VALID_CAPSULE_NAME) ) return false; py_idchotkey_ctx_t *_ctx = (py_idchotkey_ctx_t *) PyCapsule_GetPointer(ctx, VALID_CAPSULE_NAME); if ( _ctx == nullptr || !unregister_action(_ctx->action_name.c_str()) ) return false; delete _ctx; // invalidate capsule; make sure we don't try and delete twice PyCapsule_SetName(ctx, INVALID_CAPSULE_NAME); return true; } //------------------------------------------------------------------------ PyObject *py_add_hotkey(const char *hotkey, PyObject *callable) { PYW_GIL_CHECK_LOCKED_SCOPE(); // Make sure a callable was passed if ( !PyCallable_Check(callable) ) return nullptr; // Form the function name qstring idc_func_name; idc_func_name.sprnt("py_hotkeycb_%p", callable); // Can add the hotkey? if ( add_idc_hotkey(hotkey, idc_func_name.c_str()) == IDCHK_OK ) { do { // Generate global variable name qstring idc_gvarname; idc_gvarname.sprnt("_g_pyhotkey_ref_%p", callable); // Now add the global variable idc_value_t *gvar = add_idc_gvar(idc_gvarname.c_str()); if ( gvar == nullptr ) break; // The function body will call a registered IDC function that // will take a global variable that wraps a PyCallable as a pvoid qstring idc_func; idc_func.sprnt("static %s() { %s(%s); }", idc_func_name.c_str(), S_PYINVOKE0, idc_gvarname.c_str()); // Compile the IDC condition qstring errbuf; if ( !compile_idc_text(idc_func.c_str(), &errbuf) ) break; // Create new context py_idchotkey_ctx_t *ctx = new py_idchotkey_ctx_t(idc_func_name.c_str(), callable); // Bind IDC variable w/ the PyCallable gvar->set_pvoid(callable); // Return the context return PyCapsule_New(ctx, VALID_CAPSULE_NAME, nullptr); } while (false); } // Cleanup unregister_action(idc_func_name.c_str()); Py_RETURN_NONE; } //------------------------------------------------------------------------ static PyObject *py_take_database_snapshot(snapshot_t *snapshot) { PYW_GIL_CHECK_LOCKED_SCOPE(); qstring err_msg; bool b = take_database_snapshot(snapshot, &err_msg); // Return (b, err_msg) return Py_BuildValue("(Ns)", PyBool_FromLong(b), err_msg.empty() ? nullptr : err_msg.c_str()); } //------------------------------------------------------------------------- static PyObject *py_restore_database_snapshot( const snapshot_t *snapshot, PyObject *callback, PyObject *userdata) { PYW_GIL_CHECK_LOCKED_SCOPE(); // If there is no callback, just call the function directly if ( callback == Py_None ) return PyBool_FromLong(restore_database_snapshot(snapshot, nullptr, nullptr)); // Create a new tuple or increase reference to userdata if ( userdata == Py_None ) { userdata = PyTuple_New(0); if ( userdata == nullptr ) return nullptr; } else { Py_INCREF(userdata); } // Create callback data tuple (use 'N' for userdata, since its // reference has already been incremented) PyObject *cb_data = Py_BuildValue("(ON)", callback, userdata); bool b = restore_database_snapshot(snapshot, py_snapshot_restore_callback, (void *) cb_data); if ( !b ) Py_DECREF(cb_data); return PyBool_FromLong(b); } //------------------------------------------------------------------------ static ssize_t py_execute_sync(PyObject *callable, int reqf) { PYW_GIL_CHECK_LOCKED_SCOPE(); ssize_t rc = -1; // Callable? if ( PyCallable_Check(callable) ) { struct py_exec_request_t : exec_request_t { ref_t py_callable; virtual ssize_t idaapi execute() override { PYW_GIL_GET; newref_t py_result(PyObject_CallFunctionObjArgs(py_callable.o, nullptr)); ssize_t ret = !py_result || !PyLong_Check(py_result.o) ? -1 : PyLong_AsLong(py_result.o); // if the requesting thread decided not to wait for the request to // complete, we have to self-destroy, nobody else will do it if ( (code & MFF_NOWAIT) != 0 ) delete this; return ret; } py_exec_request_t(PyObject *pyc) { // No need to GIL-ensure here, since this is created // within the py_execute_sync() scope. py_callable = borref_t(pyc); } virtual ~py_exec_request_t() { // Need to GIL-ensure here, since this might be called // from the main thread. PYW_GIL_GET; py_callable = ref_t(); // Release callable } }; py_exec_request_t *req = new py_exec_request_t(callable); // Release GIL before executing, or if this is running in the // non-main thread, this will wait on the req.sem, while the main // thread might be waiting for the GIL to be available. SWIG_PYTHON_THREAD_BEGIN_ALLOW; rc = execute_sync(*req, reqf); SWIG_PYTHON_THREAD_END_ALLOW; // destroy the request once it is finished. exception: NOWAIT requests // will be handled in the future, so do not destroy them yet! if ( (reqf & MFF_NOWAIT) == 0 ) delete req; } return rc; } //------------------------------------------------------------------------ static bool py_execute_ui_requests(PyObject *callable_list) { struct py_ui_request_t: public ui_request_t { private: ref_vec_t py_callables; size_t py_callable_idx; static int idaapi s_callable_list_walk_cb( const ref_t &py_item, Py_ssize_t /*index*/, void *ud) { PYW_GIL_CHECK_LOCKED_SCOPE(); // Not callable? Terminate iteration if ( !PyCallable_Check(py_item.o) ) return CIP_FAILED; // Append this callable and increment its reference py_ui_request_t *_this = (py_ui_request_t *)ud; _this->py_callables.push_back(py_item); return CIP_OK; } public: py_ui_request_t(): py_callable_idx(0) { } virtual bool idaapi run() override { PYW_GIL_GET; // Get callable ref_t py_callable = py_callables.at(py_callable_idx); bool reschedule; newref_t py_result(PyObject_CallFunctionObjArgs(py_callable.o, nullptr)); // execute_ui_requests() will cause this run() code to be executed // asynchronously. This means that, should an exception have been raised // in the Python code we just executed, it won't have the possibility of // trickling all the way up to the Python runtime. // Since it would interfere with subsequent Python code execution, we // want to report it right away. if ( PyErr_Occurred() != nullptr ) { PyErr_Print(); return false; } reschedule = py_result != nullptr && PyObject_IsTrue(py_result.o); // No rescheduling? Then advance to the next callable if ( !reschedule ) ++py_callable_idx; // Reschedule this C callback only if there are more callables return py_callable_idx < py_callables.size(); } // Walk the list and extract all callables bool init(PyObject *callable_list) { Py_ssize_t count = pyvar_walk_seq( callable_list, s_callable_list_walk_cb, this); return count > 0; } virtual idaapi ~py_ui_request_t() { PYW_GIL_GET; py_callables.clear(); } }; py_ui_request_t *req = new py_ui_request_t(); if ( !req->init(callable_list) ) { delete req; return false; } execute_ui_requests(req, nullptr); return true; } //------------------------------------------------------------------------ struct jobj_wrapper_t { private: const jobj_t *o; public: jobj_wrapper_t(const jobj_t *_o) : o(_o) {} PyObject *get_dict() { return PyW_from_jobj_t(*o); } static bool fill_jobj_from_dict(jobj_t *out, PyObject *dict) { return PyW_to_jobj_t(out, dict); } }; //--------------------------------------------------------------------------- // UI hooks //--------------------------------------------------------------------------- ssize_t idaapi UI_Callback(void *ud, int notification_code, va_list va); struct UI_Hooks : public hooks_base_t { // hookgenUI:methodsinfo_decl UI_Hooks(uint32 _flags=0, uint32 _hkcb_flags=HKCB_GLOBAL) : hooks_base_t("ida_kernwin.UI_Hooks", UI_Callback, HT_UI, _flags, _hkcb_flags) {} bool hook() { return hooks_base_t::hook(); } bool unhook() { return hooks_base_t::unhook(); } #ifdef TESTABLE_BUILD PyObject *dump_state(bool assert_all_reimplemented=false) { return hooks_base_t::dump_state(mappings, mappings_size, assert_all_reimplemented); } #endif // hookgenUI:methods ssize_t dispatch(int code, va_list va) { ssize_t ret = 0; switch ( code ) { // hookgenUI:notifications } return ret; } private: static ssize_t handle_get_ea_hint_output(PyObject *o, qstring *buf, ea_t) { ssize_t rc = 0; if ( o != nullptr && PyUnicode_Check(o) && PyUnicode_as_qstring(buf, o) ) rc = 1; Py_XDECREF(o); return rc; } static ssize_t handle_hint_output(PyObject *o, qstring *hint, int *important_lines) { if ( o != nullptr && PyTuple_Check(o) && PyTuple_Size(o) == 2 ) { borref_t el0(PyTuple_GetItem(o, 0)); qstring plug_hint; if ( el0 && PyUnicode_Check(el0.o) && PyUnicode_as_qstring(&plug_hint, el0.o) && !plug_hint.empty() ) { borref_t el1(PyTuple_GetItem(o, 1)); if ( el1 && PyLong_Check(el1.o) ) { long lns = PyLong_AsLong(el1.o); if ( lns > 0 ) { if ( !hint->empty() && hint->last() != '\n' ) hint->append('\n'); hint->append(plug_hint); *important_lines += lns; } } } } return 0; } static ssize_t handle_hint_output(PyObject *o, qstring *hint, ea_t, int, int *important_lines) { return handle_hint_output(o, hint, important_lines); } static ssize_t handle_hint_output(PyObject *o, qstring *hint, TWidget *, place_t *, int *important_lines) { return handle_hint_output(o, hint, important_lines); } static jobj_wrapper_t wrap_widget_cfg(const jobj_t *jobj) { return jobj_wrapper_t(jobj); } static ssize_t handle_create_desktop_widget_output(PyObject *o) { if ( o == Py_None ) return 0; TWidget *widget = nullptr; int cvt = SWIG_ConvertPtr(o, (void **) &widget, SWIGTYPE_p_TWidget, 0); if ( !SWIG_IsOK(cvt) || widget == nullptr ) return 0; return ssize_t(widget); } static ssize_t handle_widget_cfg_output(PyObject *o, const TWidget *, jobj_t *cfg) { return jobj_wrapper_t::fill_jobj_from_dict(cfg, o); } }; //------------------------------------------------------------------------- bool py_register_action(action_desc_t *desc) { bool ok = desc != nullptr; if ( ok ) { desc->flags |= ADF_OWN_HANDLER; ok = register_action(*desc); if ( ok ) { // Let's set this to nullptr, so when the wrapping Python action_desc_t // instance is deleted, it doesn't try to delete the handler (See // kernwin.i's action_desc_t::~action_desc_t()). desc->handler = nullptr; } } return ok; } //------------------------------------------------------------------------- PyObject *py_get_registered_actions() { PYW_GIL_CHECK_LOCKED_SCOPE(); qstrvec_t actions; get_registered_actions(&actions); return qstrvec2pylist(actions); } //------------------------------------------------------------------------- bool py_attach_dynamic_action_to_popup( TWidget *unused, TPopupMenu *popup_handle, action_desc_t *desc, const char *popuppath = nullptr, int flags = 0) { qnotused(unused); if ( popup_handle == nullptr || desc == nullptr ) return false; bool ok = attach_dynamic_action_to_popup( nullptr, popup_handle, *desc, popuppath, flags); // If attaching // * succeeded: the action (and its handler) will be deleted after // the popup is dismissed, // * fails: the action (and its handler) will be deleted right away // Therefore, we must always drop ownership. desc->handler = nullptr; return ok; } // This is similar to a twinline_t, with improved memory management: // twinline_t has a dummy destructor, that performs no cleanup. struct disasm_line_t { disasm_line_t() : at(nullptr) {} ~disasm_line_t() { qfree(at); } disasm_line_t(const disasm_line_t &other) { *this = other; } disasm_line_t &operator=(const disasm_line_t &other) { qfree(at); at = other.at == nullptr ? nullptr : other.at->clone(); return *this; } place_t *at; qstring line; color_t prefix_color; bgcolor_t bg_color; bool is_default; }; DECLARE_TYPE_AS_MOVABLE(disasm_line_t); typedef qvector disasm_text_t; //------------------------------------------------------------------------- // tuple(fields, icon, attr) static PyObject *py_chooser_base_t_get_row( const chooser_base_t *chobj, size_t n) { if ( chobj == nullptr ) { PyErr_SetString(PyExc_ValueError, "A valid chooser_base_t pointer is expected as first argument"); return nullptr; } qstrvec_t fields; fields.resize(chobj->columns); chooser_item_attrs_t *attrs = new chooser_item_attrs_t; int icon; chobj->get_row(&fields, &icon, attrs, n); PyObject *tuple = PyTuple_New(3); PyTuple_SetItem(tuple, 0, qstrvec2pylist(fields)); PyTuple_SetItem(tuple, 1, PyLong_FromLong(icon)); PyObject *py_attrs = SWIG_NewPointerObj( SWIG_as_voidptr(attrs), SWIGTYPE_p_chooser_item_attrs_t, SWIG_POINTER_OWN); PyTuple_SetItem(tuple, 2, py_attrs); return tuple; } //------------------------------------------------------------------------- void py_gen_disasm_text(disasm_text_t &text, ea_t ea1, ea_t ea2, bool truncate_lines) { text_t _text; gen_disasm_text(_text, ea1, ea2, truncate_lines); for ( size_t i = 0, n = _text.size(); i < n; ++i ) { twinline_t &tl = _text[i]; disasm_line_t &dl = text.push_back(); dl.at = tl.at; // Transfer ownership dl.line.swap(tl.line); // Transfer ownership } } //------------------------------------------------------------------------- PyObject *py_set_nav_colorizer(PyObject *callback) { struct ida_local lambda_t { static uint32 idaapi call_py_colorizer(ea_t ea, asize_t nbytes, void *) { PYW_GIL_GET; if ( !py_colorizer ) // Shouldn't happen. return 0; newref_t pyres(PyObject_CallFunction( py_colorizer.o, "KK", (unsigned long long) ea, (unsigned long long) nbytes)); PyW_ShowCbErr("nav_colorizer"); uint32 rc = 0; bool ok = pyres && PyLong_Check(pyres.o); if ( ok ) { int overflow = 0; const long l = PyLong_AsLongAndOverflow(pyres.o, &overflow); ok = PyErr_Occurred() == nullptr; if ( ok ) { if ( l == -1 && overflow != 0 ) ok = false; else rc = uint32(l); } else { PyErr_Print(); } } if ( !ok ) { static bool warned = false; if ( !warned ) { msg("WARNING: set_nav_colorizer() callback must return an " "unsigned 'long', that can be converted into a 32-bit " "unsigned integer.\n"); warned = true; } } return rc; } }; // Always perform the call to set_nav_colorizer(): that has side-effects // (e.g., updating the legend.) bool first_install = py_colorizer == nullptr; py_colorizer = borref_t(callback); nav_colorizer_t *was_fun = nullptr; void *was_ud = nullptr; set_nav_colorizer(&was_fun, &was_ud, lambda_t::call_py_colorizer, nullptr); if ( !first_install ) Py_RETURN_NONE; PyObject *was_fun_ptr = PyCapsule_New((void *) was_fun, VALID_CAPSULE_NAME, nullptr); PyObject *was_ud_ptr = PyCapsule_New(was_ud, VALID_CAPSULE_NAME, nullptr); PyObject *dict = PyDict_New(); PyDict_SetItemString(dict, "fun", was_fun_ptr); PyDict_SetItemString(dict, "ud", was_ud_ptr); return dict; } //------------------------------------------------------------------------- uint32 py_call_nav_colorizer( PyObject *colorizer, ea_t ea, asize_t nbytes) { if ( !PyDict_Check(colorizer) ) return 0; borref_t py_fun(PyDict_GetItemString(colorizer, "fun")); borref_t py_ud(PyDict_GetItemString(colorizer, "ud")); if ( !py_fun || !PyCapsule_IsValid(py_fun.o, VALID_CAPSULE_NAME) || !PyCapsule_IsValid(py_ud.o, VALID_CAPSULE_NAME) ) { return 0; } nav_colorizer_t *fun = (nav_colorizer_t *) PyCapsule_GetPointer(py_fun.o, VALID_CAPSULE_NAME); void *ud = PyCapsule_GetPointer(py_ud.o, VALID_CAPSULE_NAME); if ( fun == nullptr ) return 0; return fun(ea, nbytes, ud); } //------------------------------------------------------------------------- PyObject *py_msg_get_lines(int count=-1) { qstrvec_t lines; msg_get_lines(&lines, count); return qstrvec2pylist(lines); } //------------------------------------------------------------------------- static TWidget *TWidget__from_ptrval__(size_t ptrval) { return (TWidget *) ptrval; } // we limit the the number of spaces that can be added to 512k #define MAX_SPACES_ADDED 524288 //------------------------------------------------------------------------- static qstring py_add_spaces(const char *s, size_t len) { qstring qbuf(s); const size_t slen = tag_strlen(qbuf.c_str()); const size_t nlen = qbuf.length() - slen + len; if ( len > slen && nlen < MAX_SPACES_ADDED ) { qbuf.resize(nlen); } else { if ( s == nullptr ) qbuf.resize(1); } // we use the actual 'size' because we know that // 'add_spaces()' will add a terminating zero anyway add_spaces(qbuf.begin(), qbuf.size(), len); return qbuf; } //------------------------------------------------------------------------- THREAD_SAFE void py_show_wait_box(const char *message) { idapython_show_wait_box(/*internal=*/ false, message); } //------------------------------------------------------------------------- void py_hide_wait_box() { idapython_hide_wait_box(); } // //--------------------------------------------------------------------------- // // hookgenUI:methodsinfo_def //--------------------------------------------------------------------------- ssize_t idaapi UI_Callback(void *ud, int code, va_list va) { // hookgenUI:safecall=UI_Hooks } //------------------------------------------------------------------------ bool idaapi py_menu_item_callback(void *userdata) { PYW_GIL_GET; // userdata is a tuple of ( func, args ) // func and args are borrowed references from userdata PyObject *o = (PyObject *) userdata; if ( !PyTuple_Check(o) ) return false; PyObject *func = PyTuple_GetItem(o, 0); PyObject *args = PyTuple_GetItem(o, 1); // Call the python function newref_t result(PyObject_Call(func, args, nullptr)); // We cannot raise an exception in the callback, just print it. if ( result == nullptr ) { PyErr_Print(); return false; } return PyObject_IsTrue(result.o) != 0; } //------------------------------------------------------------------------- static void ida_kernwin_init(void) {} //------------------------------------------------------------------------- static void ida_kernwin_term(void) { py_colorizer = ref_t(); } //------------------------------------------------------------------------- static void ida_kernwin_closebase(void) {} //------------------------------------------------------------------------ static void py_snapshot_restore_callback(const char *err_msg, void *userdata) { PYW_GIL_GET; // userdata is a tuple of ( func, args ) // func and args are borrowed references from userdata PyObject *o = (PyObject *) userdata; if ( o == nullptr || !PyTuple_Check(o) ) return; PyObject *func = PyTuple_GetItem(o, 0); PyObject *args = PyTuple_GetItem(o, 1); // Create arguments tuple for python function PyObject *cb_args = Py_BuildValue("(sO)", err_msg, args); // Call the python function newref_t result(PyObject_Call(func, cb_args, nullptr)); // Free cb_args and userdata Py_DECREF(cb_args); Py_DECREF(o); // We cannot raise an exception in the callback, just print it. if ( !result ) PyErr_Print(); } // #endif