#tcl
Вопрос:
У меня есть два вопроса о следующем коде.
- Можно ли его использовать
upvar
,ReadLine
когда он вызывается событием канала? Экспериментируяupvar
, я использовал точно такой же сценарий, за исключениемReadLine
того, что он был вызван напрямую, а не через событие; и это сработало нормально. Он терпит неудачу как есть, потомуstate($sock)
что не распознается. Нужна ли другая#n
ценность или она должна быть глобальной? Он попытался#2
, и это выдало ошибку, которая была плохим значением. - В процедуре
ReadLine
я попытался сделать указатель$state($sock)
таким, чтобы он указывал на сам список. В экспериментах, например , это хорошо работалоlappend $sptr $op
, но не сработалоset row [lindex $sptr 0]
, потому$sptr
что естьstate(1)
. Тем не менее, это работает , если использоватьset
, напримерset row [lindex [set $sptr] 0]
. Почему это происходит? Существует ли другой метод получения значения, на которое указывает указатель? Является ли попытка использовать указатели в Tcl плохой идеей? Означает ли тот факт, что тело процедур преобразуется в представление байт-кода, что использование указателей не имеет большого значения?
Спасибо.
proc ClientConnect {sock client_ip client_port} {
if {![info exists state($sock)]} {set state($sock) {1}};
chan configure $sock -buffering line -blocking 0 -translation crlf
chan event $sock readable [list ReadLine $sock]
}
proc ReadLine {sock} {
# The #1 in upvar is to first caller, which is ClientConnect.
# Make a ptr to the array list rather than retrieving it each reference,
# such that $sptr is state($sock)'s list.
upvar #1 state r
set sptr r($sock)
puts stdout "x: [lindex $r($sock) 0]"; #Fails to recognize state.
set row [lindex [set $sptr] 0]
}
Экспериментальный код, который, по-видимому, работает правильно:
proc ReadLine {sock} {
upvar #1 row rtemp; #Recognizes row.
set r rtemp($sock)
lappend $r "new"
set line "Content-length: 247899"
if { [string first "Content-length:" $line] == 0 } { lappend $r [string trim [string range $line 16 end]] }
lappend $r "help"
puts [lindex [set $r] 3]; #Output is: 247899.
puts $rtemp($sock); #Output is: 1 GET new 247899 help.
}
proc do {sock} {
set row($sock) {1 GET}
ReadLine $sock
}
set sock 1
do $sock
Комментарии:
1. IIRC, обработчик событий канала не имеет стека вызовов (или, скорее, это первый уровень в новом), поэтому
upvar
с чем угодно, но#0
вряд ли будет полезен.2. @Шон, спасибо тебе за объяснение.
Ответ №1:
Вы можете использовать upvar
, если хотите, но вы обнаружите, что единственный кадр стека, видимый над обратным вызовом, — это глобальный кадр стека. Это как если бы его вызывали с uplevel #0
помощью . Он не может видеть внутренние процедуры, и если вы немного подумаете об этом, причина очевидна: нет никакой гарантии, что какая-либо конкретная процедура будет находиться в стеке, когда произойдет вызов.
Если вам нужно передать состояние из места, где вы устанавливаете обратный вызов, туда, где он вызывается, самые простые способы включают либо использование сопрограммы (которую можно возобновить из фактического глобального обратного вызова), либо использование объекта TclOO (который, конечно, может инкапсулировать все виды сложного состояния). В 8.5 и ранее вы более ограничены и должны использовать глобальные переменные/пространство имен, что может привести к беспорядку.
Чтобы ваш доступ к переменной работал, используйте upvar
для создания псевдонима переменной:
# upvar 0 is special in that it aliases a currently-visible variable
upvar 0 row($sock) r
puts [lindex $r 0]
Единственное реальное ограничение заключается в том, что вы не можете помещать массивы внутри массивов. Это было удалено давным-давно, потому что оно было сложным в использовании и приводило к сбоям.
Комментарии:
1. Спасибо вам за объяснение. Я не понимал, что
#0
можно использовать без использованияglobal
. Поскольку я больше работаю, используя множество небольших процедур, их кажется трудным использоватьupvar
в качестве средства изменения одной и той же переменной. Например, если использовать его для ссылкиstate
ReadLine
и хотите позвонитьCloseSock
оттуда, что выполняетсяunset state($sock)
, он, по-видимомуupvar
, больше не может использоваться для ссылкиstate
. Глобальная переменная кажется проще, но я читал, что глобальные переменные не следует использовать. или используется минимально. Это правда?