c++tclc-api

Tcl convertion to double not working with large examples


I have a list of dicts and I want to retrieve some values of these dicts. Here is the code:

void Get_Dict_Value(Tcl_Interp *interp, Tcl_Obj *dict, const char* key, std::function<void(Tcl_Obj*)> data_handler) {
    Tcl_Obj* val_ptr;
    Tcl_Obj* key_ptr = Tcl_NewStringObj(key, -1);
    Tcl_IncrRefCount(key_ptr);
    Tcl_DictObjGet(interp, dict, key_ptr, &val_ptr);
    Tcl_IncrRefCount(val_ptr);
    data_handler(val_ptr);
    Tcl_DecrRefCount(val_ptr);
    Tcl_DecrRefCount(key_ptr);
}

void Write_Float_Dict(Tcl_Interp *interp, Tcl_Obj *dict, const char* key) {
    Get_Dict_Value(interp, dict, key, [&interp](Tcl_Obj *val_ptr) {
        double double_value;
        Tcl_GetDoubleFromObj(interp, val_ptr, &double_value); //crashing here
        std::cout << "the value: " << double_value << std::endl;
    });
}


static int Dict_Test(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
    const char* script = R"__(

    proc my_ns::bla {} {
        set my_list ""
        set my_dict [dict create]
        dict set my_dict my_key 123789
        dict set my_dict my_key_2 456
        lappend my_list $my_dict
        return $my_list;
    }

    )__";
    Tcl_Eval(interp, script);
    Tcl_Eval(interp, "my_ns::bla");

    Tcl_Obj* my_list = Tcl_GetObjResult(interp);
    Tcl_IncrRefCount(my_list);

    Tcl_Obj* my_dict;
    Tcl_ListObjIndex(interp, my_list, 0, &my_dict);
    Tcl_IncrRefCount(my_dict);

    Write_Float_Dict(interp, my_dict, "my_key"); //prints: the value: 123789
    Write_Float_Dict(interp, my_dict, "my_key_2"); //prints: the value: 456

    Tcl_DecrRefCount(my_dict);
    Tcl_DecrRefCount(my_list);
    return TCL_OK;
}

If I just run the example as is, this is going to work. But when I try with a specific big example, I got a crash in the first Tcl_GetDoubleFromObj occurrence. Am I missing something regarding reference counting? Also, I didn't find good examples regarding Tcl_DictObjGet usage, am I using it right?


Solution

  • You should (well, must really) test the result of Tcl_DictObjGet to see if it is TCL_OK or TCL_ERROR. It's a C API, so it returns result codes instead of throwing C++ exceptions.

    void Get_Dict_Value(Tcl_Interp *interp, Tcl_Obj *dict, const char* key, std::function<void(Tcl_Obj*)> data_handler) {
        Tcl_Obj* val_ptr = NULL;
        Tcl_Obj* key_ptr = Tcl_NewStringObj(key, -1);
        Tcl_IncrRefCount(key_ptr);
        if (Tcl_DictObjGet(interp, dict, key_ptr, &val_ptr) == TCL_OK) {
            Tcl_IncrRefCount(val_ptr);
            data_handler(val_ptr);
            Tcl_DecrRefCount(val_ptr);
        }
        Tcl_DecrRefCount(key_ptr);
    }
    

    (The other alternative is to throw an exception if the Tcl_DictObjGet returns TCL_ERROR.)

    Similarly with Tcl_GetDoubleFromObj; that uses the same pattern (but produces a double via its out argument on success, not a Tcl_Obj*).

    You don't need to increment the reference count of the value if the dict is holding the reference, but that should be fine; it's definitely legal to do what you've done there (and depending on what you do with it, it might be necessary; details matter).