如何从外部访问容器类内生成的ITcl对象并解决调用报错问题
解决Itcl中OwningList外部调用对象方法出现"invalid command name"的问题
我编写了一个OwningList容器类用于管理对象,确保不再需要的对象能被销毁。该类可在内部生成、访问和销毁A类对象,但外部无法直接访问这些对象,直接调用对象方法时会出现“invalid command name 'a0'”错误,请问该如何修改代码解决此问题?
代码示例
#!/usr/bin/wish package require itcl namespace import itcl::* class A { private variable x public constructor { x_ } { set x $x_ puts "Create A" puts "this $this with value $x" } public method Do { } { puts "A::Do called" puts "Value x $x" } # 注意:即使参数非空,析构函数也不需要加括号 public destructor { puts "destruct insance of A, value of x $x" } # 销毁自身 public method delete { } { puts "delete object $this" # 这里需要加::调用全局的delete命令 ::delete object $this } } class OwningList { private variable objects public constructor { } { set objects {} ; # 创建空列表 } public method Generate { classname args } { set object [ $classname #auto $args ] lappend objects $object } # 注意:析构函数参数不需要加括号 destructor { foreach el $objects { ::delete object $el } } # 参数2使用默认值 public method EraseByIndex { start_ { end_ -1 } } { if { $end_ == -1 } { ::delete object [lindex $objects $start_] set objects [lreplace $objects $start_ $start_] } else { for { set s $start_ } { $s <= $end_ } { incr s } { ::delete object [lindex $objects $s] } set objects [lreplace $objects $start_ $end_] } } public method GetList { } { return [itcl::scope objects] } public method Do { } { foreach n $objects { puts "Call function from class scope" $n Do ; # 这里可以正常执行! } } public method CallAFunction { object function args } { puts "Execute: $object $function $args" eval $object $function $args } } OwningList ol; ol Generate A 0 ol Generate A 1 ol Generate A 2 ol Generate A 3 ol Generate A 4 ol Generate A 5 ol Generate A 6 puts "Erase 5" ol EraseByIndex 5 puts "erase 1 to 2" ol EraseByIndex 1 2 ol Do set x [ ol GetList ] upvar 0 $x y foreach n $y { puts "Got $n" puts "Want to do: $n Do" #$n Do ; # <<<<<<<<<<<<<<<<<<<<<<<<<<< 这里执行失败!错误信息见下方 ol CallAFunction $n Do } puts "Erase Container" delete object ol
错误信息
Error in startup script: invalid command name "a0" while executing "$n Do" ("foreach" body line 4) invoked from within "foreach n $y { puts "Got $n" puts "Want to do: $n Do" $n Do ol CallAFunction $n Do }" (file "./owning_list.tcl" line 100)
问题原因
Itcl中用#auto创建的对象默认是命名空间局部对象,仅在创建它的OwningList实例命名空间内可见,外部全局命名空间无法识别该对象的命令名称,因此直接调用会触发"invalid command name"错误。
解决方法
方法1:创建全局可见的对象
修改OwningList的Generate方法,生成全局命名空间下的对象,让外部可以直接访问:
public method Generate { classname args } { # 使用::指定全局命名空间,{*}展开参数列表(Tcl 8.5+支持) set object [ ::$classname #auto {*}$args ] lappend objects $object }
同时把GetList方法改成直接返回对象列表(无需返回命名空间引用):
public method GetList { } { return $objects }
这样外部拿到对象名后就能直接调用方法。
方法2:通过容器代理调用(推荐,符合封装原则)
既然OwningList是对象的管理者,外部不应直接持有对象引用,而是通过容器提供的接口操作对象。可以给OwningList添加代理方法:
# 按索引调用对象方法 public method CallByIndex { idx function args } { if { $idx < 0 || $idx >= [llength $objects] } { error "索引$idx超出范围" } set obj [lindex $objects $idx] $obj {*}$function {*}$args } # 按对象引用调用(先校验是否在容器内) public method CallObject { obj function args } { if { $obj ni $objects } { error "对象$obj不在容器管理范围内" } $obj {*}$function {*}$args }
外部调用示例:
# 按索引调用第一个对象的Do方法 ol CallByIndex 0 Do # 或者通过对象名调用(需先从GetList拿到列表) set objList [ol GetList] foreach obj $objList { ol CallObject $obj Do }
这种方式既保证了对象的封装性,又避免了外部直接操作对象的风险。
方法3:修正CallAFunction的参数传递
原代码中CallAFunction用eval传递参数存在安全隐患,改成用{*}展开参数更安全:
public method CallAFunction { object function args } { puts "Execute: $object $function $args" $object {*}$function {*}$args }
如果仍想保留通过容器代理调用的方式,这个修改能让参数传递更可靠。
内容的提问来源于stack exchange,提问作者Klaus
相关产品推荐
相关产品推荐

