Pybind11 and PT()

I am developing an exporter for blender, I came up with the idea to abandon Interrogate in favor of pybind11, since the Interrogate features are redundant, I need to call one function once and that’s it. However, Panda3D has smart pointers, which I believe are not compatible outside of the Panda3D game loop. Because using them causes the add-on to crash. Here is a simple code that causes the program to crash:

#include <iostream>
#include <pybind11/pybind11.h>

#include "pandaNode.h"

#include "camera.h"
#include "perspectiveLens.h"
#include "pointerTo.h"

namespace {
    
    void make_camera(PandaNode* node) {
        PT(PerspectiveLens) lens = new PerspectiveLens();
    }

    void export_object(pybind11::dict& input_dict){
        PT(PandaNode) node = new PandaNode("RootNode");
        make_camera(node);
    }
}
# Blender 5.0.0, Commit date: 2025-11-18 09:54, Hash a37564c4df7a

# backtrace
Exception Record:

ExceptionCode         : EXCEPTION_ACCESS_VIOLATION (0xc0000005)
Exception Address     : 0x00007FF9EB636ADD
Exception Module      : fast_bam_bridge.pyd
Exception Flags       : 0x00000000
Exception Parameters  : 0x2
	Parameters[0] (action)  : 0x0000000000000000 (read)
	Parameters[1] (address) : 0x0000000000000118


Stack trace:
fast_bam_bridge.pyd :0x00007FF9EB636830  pybind11::detail::error_fetch_and_normalize::error_string
fast_bam_bridge.pyd :0x00007FF9EB62D790  <lambda_b288df5d5539799df0eaa53ffb349dc6>::operator()
fast_bam_bridge.pyd :0x00007FF9EB621680  <lambda_b288df5d5539799df0eaa53ffb349dc6>::<lambda_invoker_cdecl>
fast_bam_bridge.pyd :0x00007FF9EB6332C0  pybind11::cpp_function::dispatcher
python311.dll       :0x00007FF9EBD5B430  PyCFunction_GetFlags
python311.dll       :0x00007FF9EBD15370  PyObject_MakeTpCall
python311.dll       :0x00007FF9EBD156D0  PyObject_Vectorcall
python311.dll       :0x00007FF9EBE3B130  PyEval_EvalFrameDefault
python311.dll       :0x00007FF9EBE3B130  PyEval_EvalFrameDefault
python311.dll       :0x00007FF9EBD158E0  PyFunction_Vectorcall
python311.dll       :0x00007FF9EBD154E0  PyVectorcall_Function
python311.dll       :0x00007FF9EBD15730  PyObject_Call
blender.exe         :0x00007FF6EEE928F0  bpy_class_call
blender.exe         :0x00007FF6EECEEA40  rna_operator_exec_cb
blender.exe         :0x00007FF6EE7C87A0  wm_operator_invoke
blender.exe         :0x00007FF6EE7C7B70  wm_operator_call_internal
blender.exe         :0x00007FF6EE7BE9B0  WM_operator_name_call_ptr_with_depends_on_cursor
blender.exe         :0x00007FF6EF748FB0  ui_apply_but_funcs_after
blender.exe         :0x00007FF6EF754470  ui_handler_region_menu
blender.exe         :0x00007FF6EE7C6C20  wm_handlers_do_intern
blender.exe         :0x00007FF6EE7C5DC0  wm_handlers_do
blender.exe         :0x00007FF6EE7C20F0  wm_event_do_handlers
blender.exe         :0x00007FF6EE7AC5A0  WM_main
blender.exe         :0x00007FF6EDCFD280  main
blender.exe         :0x00007FF6F0DB8784  __scrt_common_main_seh
KERNEL32.DLL        :0x00007FFA588F7360  BaseThreadInitThunk
ntdll.dll           :0x00007FFA596BCC70  RtlUserThreadStart

The log was edited, the loaded modules were excluded so that the number of characters in the message would not exceed the permissible limit.

If you abandon smart pointers:

#include <iostream>
#include <pybind11/pybind11.h>

#include "pandaNode.h"

#include "camera.h"
#include "perspectiveLens.h"
#include "pointerTo.h"

namespace {
    
    void make_camera(PandaNode* node) {
        PerspectiveLens* lens = new PerspectiveLens();
    }

    void export_object(pybind11::dict& input_dict){
        PT(PandaNode) node = new PandaNode("RootNode");
        make_camera(node);
    }
}

The crash does not happen, the most surprising thing is that the pointer to GeomNode does not cause problems, the problem is caused by PerspectiveLens and Camera, I did not check the other classes.

I don’t think this has anything to do with the use of PT() per se, because the code you showed should not have any problems. The only thing that is different is that your second piece of code creates a memory leak: the PerspectiveLens can never be destroyed.

According to your stack trace, the crash seems to happen in code that doesn’t have anything to do with Panda3D. Either the symbolization is incorrect (is there a .pdb generated?) or the crash really is happening in pybind11 error handling for an unrelated reason, ie. an error was generated elsewhere already.

I have the .pdb files, but adding them next to the dll and pyd doesn’t reveal anything. Maybe I just don’t know where to look…

I also remembered that I tested the code outside of the Blender environment, I used python 3.7 and no crashes occurred. This problem only surfaced when I built Panda3D without python support. Perhaps the problem lies in optimization level 4?

@rdb A quick check showed that the --optimize=4 flag for makepanda is a problem. The solution is to delete it, but if it’s a real solution, I’ll temporarily set the flag anyway.

Ah, so it’s an ABI / build mismatch. Look at whether the _DEBUG / NDEBUG flags are set the same between the Panda build and the module you’re building.

I recommend against --optimize=4 anyway except for release, it makes debugging harder.

The bottom line is that I use cmake to create a module. I’m starting the construction of the project, with the --config Release flag. However, makepanda uses something of its own that is not universally recognized, so now I need to check the flags passed to the compiler to understand how it relates.

I think a reliable fix would be to use

target_compile_options(fast_bam_bridge PRIVATE
    #$<$<CXX_COMPILER_ID:GNU,Clang>:-O3 -Wall>
    $<$<CXX_COMPILER_ID:MSVC>:/MD /Zi /GS- /O2 /Ob2 /Oi /Ot /fp:fast>
)

This information is very useful because it allows you to determine how makepanda combines optimization flags when using a specific level.

I am publishing this only in order to understand which set of flags are generally accepted. I don’t know if Cmake follows this rule or not.