我是Tcl C API的初学者,我正在努力了解如何使用它。我有这样的代码,它从get_my_list
proc获得Tcl列表,然后我对其进行迭代以发送一些信息,在本例中,是与get_attr_info
proc获得的属性A
、B
和C
相关的信息。当我在一个基本上只有这段代码的非常简单的例子中运行它时,一切都很完美,但当我在大型tcl项目中添加这个lib时,它最终会崩溃。我怀疑这是由于tcl对象的引用计数使用不当,我的意思是,它们的生存期。在下面的例子中,我可能做错了什么?我用的是Tcl 8.6。
Tcl_Obj* Get_Info(Tcl_Interp *interp, const char* info, const char* attr) {
char cmd[256];
sprintf(cmd, "get_attr_info %s %s", info, attr);
Tcl_Eval(interp, cmd);
return Tcl_GetObjResult(interp);
}
static int Copy_Info(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
Tcl_Eval(interp, "get_my_list");
Tcl_Obj *const my_list = Tcl_GetObjResult(interp);
int my_list_size;
Tcl_ListObjLength(interp, my_list, &my_list_size);
Tcl_IncrRefCount(my_list);
for (int i = 0; i < my_list_size; ++i) {
Tcl_Obj* current_info_obj;
Tcl_ListObjIndex(interp, my_list, i, ¤t_info_obj);
const char* current_info_ctr = Tcl_GetStringFromObj(current_info_obj, NULL);
/* getting info A */
Tcl_Obj* info_a_obj = Get_Info(current_info_ctr, "A");
const char* info_a = Tcl_GetStringFromObj(info_a_obj, NULL);
Copy_Info_A(info_a);
/* getting info B */
Tcl_Obj* info_b_obj = Get_Info(current_info_ctr, "B");
const char* info_b = Tcl_GetStringFromObj(info_b_obj, NULL);
Copy_Info_B(info_b);
/* getting info C */
Tcl_Obj* info_c_obj = Get_Info(current_info_ctr, "C");
const char* info_c = Tcl_GetStringFromObj(info_c_obj, NULL);
Copy_Info_C(info_c);
}
Tcl_DecrRefCount(my_list);
Tcl_FreeResult(interp);
return TCL_OK;
}
棘手的一点是,您在Get_Info()
中调用Tcl_Eval()
,它可以对您没有自己引用的值进行引用计数管理,包括闪烁掉my_list
中引用的值的列表表示,这可以从其元素下拉开地毯。特别是,current_info_obj
中引用的对象必须使其引用计数从Tcl_ListObjIndex()
之后增加到循环体的末尾。
// ...
for (int i = 0; i < my_list_size; ++i) {
Tcl_Obj* current_info_obj;
Tcl_ListObjIndex(interp, my_list, i, ¤t_info_obj);
// HOLD THE REFERENCE; IT OWNS THE current_info_ctr STRING FOR US
Tcl_IncrRefCount(current_info_obj);
const char* current_info_ctr = Tcl_GetStringFromObj(current_info_obj, NULL);
/* getting info A */
Tcl_Obj* info_a_obj = Get_Info(current_info_ctr, "A");
const char* info_a = Tcl_GetStringFromObj(info_a_obj, NULL);
Copy_Info_A(info_a);
/* getting info B */
Tcl_Obj* info_b_obj = Get_Info(current_info_ctr, "B");
const char* info_b = Tcl_GetStringFromObj(info_b_obj, NULL);
Copy_Info_B(info_b);
/* getting info C */
Tcl_Obj* info_c_obj = Get_Info(current_info_ctr, "C");
const char* info_c = Tcl_GetStringFromObj(info_c_obj, NULL);
Copy_Info_C(info_c);
// RELEASE THE REFERENCE
Tcl_DecrRefCount(current_info_obj);
}
// ...
改进代码
你也可以做一个捷径,用Tcl_GetString(objPtr)
代替Tcl_GetStringFromObj(objPtr, NULL)
。你可以把它放在Get_Info
里面。您可能还应该考虑改用Tcl_EvalObjv()
,因为这是一个更快的API(它避免了一定程度的解析(,尽管使用起来更复杂。
const char *Get_Info(Tcl_Interp *interp, Tcl_Obj* info, const char* attr) {
Tcl_Obj *argv[3], *result;
argv[0] = Tcl_NewStringObj("get_attr_info", -1); // cacheable
argv[1] = info;
argv[2] = Tcl_NewStringObj(attr, -1);
Tcl_IncrRefCount(argv[0]);
Tcl_IncrRefCount(argv[1]);
Tcl_IncrRefCount(argv[2]);
// It's very bad form to omit error handling
if (Tcl_EvalObjv(interp, 3, argv, 0) != TCL_OK) {
Tcl_Panic("problem: %s", Tcl_GetString(Tcl_GetObjResult(interp)));
}
result = Tcl_GetObjResult(interp);
Tcl_DecrRefCount(argv[0]);
Tcl_DecrRefCount(argv[1]);
Tcl_DecrRefCount(argv[2]);
return Tcl_GetString(result);
}
在某些情况下,可以减少引用计数处理的数量。一开始不要担心这样做!最好绝对避免撞车事故!
Tcl_EvalObjv
(实际上(是Tcl_Eval
在交互模式下解析后实际调度命令所调用的,也是字节码引擎用来评估任何非内联命令的调用。它比将字符串解析为单词要高效得多。