51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

查看: 10334|回复: 23
打印 上一主题 下一主题

[讨论] re

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2009-12-9 17:24:13 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
问题8: 如何用TCL的内嵌脚本命令处理字符串(续2)


string命令具有强大的操作字符串的功能,其中的option选项多达20个,语法:string option arg ?arg...?

1. string compare ?-nocase? ?-length int? string1 string2
把字符串string1和string2进行比较,返回值为-1、0或1,分别对应string1小于、等于或大于string2。如果有 -length 参数,那么只比较前int个字符,如果int为负数,那么这个参数被忽略。如果有-nocase参数,那么比较时不区分大小写。


2. string equal ?-nocase? ?-length int? string1 string2
把字符串string1和string2进行比较,如果两者相同,返回值为1,否则返回0。其他参数与上同。


3. string first string1 string2 ?startindex?
在string2中从头查找与string1匹配的字符序列,如果找到,那么就返回匹配的第一个字母所在的位置(0-based)。如果没有找到,那么返回-1。如果给出了startindex变量,那么将从startindex处开始查找。例如:
% string first ab  defabc
3
% string first ab  defabc  4
-1


4. string index string charIndex
返回string中第charIndex个字符(0-based)。charIndex可以是下面的值:
整数n: 字符串中第n个字符(0-based)
end : 最后一个字符
end-整数n:倒数第n个字符。string index "abcd" end-1 返回字符'c'
如果charIndex小于0,或者大于字符串string的长度,那么返回空。
例如:
% string index abcdef  2
c
% string index abcdef  end-2
d


5. string last string1 string2 ?startindex?
参照3,唯一的区别是从后往前查找


6. string length string
返回字符串string的长度.


7. string match ?-nocase? pattern string
如果pattern匹配string,那么返回1,否则返回0.如果有-nocase参数,那么就不区分大小写.
在pattern 中可以使用通配符:
*        匹配string中的任意长的任意字符串,包括空字符串.
?        匹配string中任意单个字符
[chars]  匹配字符集合chars中给出的任意字符,其中可以使用 A-Z这种形式
\x       匹配单个字符x,使用'\'是为了让x可以为字符*,-,[,].
例子:
%  string match * abcdef
1
% string  match a*  abcdef
1
string  match  a?cdef  abcdef
1
% string match {a[b-f]cdef}  abcdef  //注意一定要用'{',否则TCL解释器会把b-f当作命令名
1                                    //从而导致错误
% string match {a[b-f]cdef}  accdef
1


8. string range string first last
返回字符串string中从第first个到第last个字符的子字符串(0-based)。如果first<0,那么first被看作0,如果last大于或等于字符串的长度,那么last被看作end,如果first比last大,那么返回空。



9. string repeat string count
返回值为:重复了string字符串count次的字符串。例如:
% string repeat "abc"  2
abcabc


10. string replace string first last ?newstring?
返回值为:从字符串string中删除了第first到第last个字符(0-based)的字符串,如果给出了newstring变量,那么就用newstring替换从第first到第last个字符。如果first<0,那么first被看作0,如果last大于或等于字符串的长度,那么last被看作end,如果first比last大或者大于字符串string的长度或者last小于0,那么原封不动的返回string。


11. string tolower string ?first? ?last?
返回值为:把字符串string转换成小写后的字符串,如果给出了first和last变量,就只转换first和last之间的字符。


12. string toupper string ?first? ?last?
同上,转换成大写。


13. string trim string ?chars?
返回值为:从string字符串的首尾删除掉了字符集合chars中的字符后的字符串。如果没有给出chars,那么将删除掉spaces、tabs、newlines、carriage returns这些字符。例如:
% string trim "abcde"  {a d e}
bc
% string trim  "        def
> "
def


14. string trimleft string ?chars?
同上,不过只删除左边的字符。


15. string trimright string ?chars?
同上,不过只删除右边的字符。


16. string map ?-nocase?  charMap string
这是个蛮有用的命令,把string按照charMap中设定的值进行替代,替代时优先级按照charMap中出现的顺序。如果有 -nocase参数,那么比较时不区分大小写。例如:
% string map {abc 1 ab 2 a 3 1 0} 1abcaababcabababc
01321221
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

该用户从未签到

2#
 楼主| 发表于 2009-12-9 17:26:01 | 只看该作者

re

问题9: 如何简化您的TCL测试脚本——定制过程


    TCL支持过程的定义和调用。在TCL中,过程可以看作是用TCL脚本实现的命令,效果与TCL的固有命令相似。我们可以在任何时候使用proc命令定义自己的过程,TCL中的过程类似于C中的函数。
    TCL中过程是由proc命令产生的,例如:
% proc abs {x} {
    if {$x >= 0} { return $x }
    return [expr -$x]
}
    proc命令的第一个参数是你要定义的过程的名字;第二个参数是过程的参数列表,参数之间用空格隔开;第三个参数是一个TCL脚本,代表过程体。proc生成一个新的命令,可以象固有命令一样调用,过程的返回值是过程体中最后执行的那条命令的返回值。


局部变量和全局变量
    对于在过程中定义的变量,因为它们只能在过程中被访问,并且当过程退出时会被自动删除,所以称为局部变量;在所有过程之外定义的变量我们称之为全局变量。TCL中,局部变量和全局变量可以同名,两者的作用域的交集为空:局部变量的作用域是它所在的过程的内部;全局变量的作用域则不包括所有过程的内部。这一点和C语言有很大的不同。如果我们想在过程内部引用一个全局变量的值,可以使用global命令。
% set a 4
4
% proc sample { x } {
         global  a
         incr a
         return [expr $a+$x]
}
% sample 3
8
%set a
5      
    全局变量a在过程中被访问,在过程中对a的改变会直接反映到全局上。如果去掉语句global a,TCL会出错,因为它不认识变量a。


缺省参数和可变参数个数
    TCL还提供三种特殊的参数形式:

    首先,你可以定义一个没有参数的过程,例如:
% proc  add {} { expr  2+3}

    其次,可以定义具有缺省参数值的过程,我们可以为过程的部分或全部参数提供缺省值,如果调用过程时未提供那些参数的值,那么过程会自动使用缺省值赋给相应的参数。和C\C++中具有缺省参数值的函数一样,有缺省值的参数只能位于参数列表的后部,即在第一个具有缺省值的参数后面的所有参数,都只能是具有缺省值的参数。
例如:
% proc  add {val1 {val2 2} {val3  3}}{
    expr $val1+$val2+$val3
}
则:
add  1          //值为6
add  2 20       //值为25
add  4 5 6      //值为15

    另外,TCL的过程定义还支持可变个数的参数,如果过程的最后一个参数是args,那么就表示这个过程支持可变个数的参数调用。调用时,位于args以前的参数象普通参数一样处理,但任何附加的参数都需要在过程体中作特殊处理,过程的局部变量args将会被设置为一个列表,其元素就是所有附加的变量。如果没有附加的变量,args就设置成一个空串,下面是一个例子:
% proc  add {  val1  args } {
    set  sum  $val1
    foreach    i   $args  {
        incr    sum   $i
    }
    return  $sum
}
则:
        add  2  //值为2


        add  2 3 4 5 6 //值为20



upvar命令

命令语法: upvar ?level? otherVar myVar ?otherVar myVar ...?
upvar命令使得用户可以在过程中对全局变量或其他过程中的局部变量进行访问,返回值为一个空字符串。upvar命令的第一个参数otherVar是我们希望以引用方式访问的参数的名字,第二个参数myVar是这个过程中的局部变量的名字,一旦使用了upvar命令把otherVar和myVar绑定,那么在过程中对局部变量myVar的读写就相当于对这个过程的调用者中otherVar所代表的局部变量的读写。下面是一个例子:
% proc  temp  { arg } {
         upvar $arg  b
         set  b   [expr  $b+2]
}
% proc  myexp   {  var  } {
         set  a  4
         temp a
         return  [expr $var+$a]
}
则:
% myexp 7
13              
这个例子中,upvar把$arg(实际上是过程myexp中的变量a)和过程temp中的变量b绑定,对b的读写就相当于对a的读写。

upvar命令语法中的level参数表示:调用upvar命令的过程相对于我们希望引用的变量myVar在调用栈中相对位置。例如:
upvar  2   other  x
这个命令使得当前过程的调用者的调用者中的变量other,可以在当前过程中利用x访问。缺省情况下,level的值为1,即当前过程(上例中的temp)的调用者(上例中的myexp)中的变量(上例中myexp的a)可以在当前过程中利用局部变量(上例中temp的b)访问。
如果要访问全局变量可以这样写:upvar #0  other  x
那么,不管当前过程处于调用栈中的什么位置,都可以在当前过程中利用x访问全局变量other。
回复 支持 反对

使用道具 举报

该用户从未签到

3#
 楼主| 发表于 2009-12-9 17:26:41 | 只看该作者

fd

问题10: 如何将一个十进制数转换为十六进制表示


Format命令
% set a 16
16
% set a [format "%#x"   $a]
0x10


【Format样例】

#1 案例1

% set labels [format "%-20s %+10s " "Item" "Cost"]
Item                       Cost
% set price1 [format "%-20s %10d Cents Each" "Tomatoes" "30"]
Tomatoes                     30 Cents Each
% set price4 [format "%-20s %10.2f per Lb." "Steak" "3.59997"]
Steak                      3.60 per Lb.

#2 案例2

% set a 9
9
% set a [format "%04d"  $a]
0009
% set a [format "%+4d"  $a]
  +9
% set a [format "%-+4d" $a]
+9  
% set a [format "%#x"   $a]  
0x9
% set a [format "%#X"   $a]
0X9
% set a 9.9999
9.9999
% set a [format "%4.3f"  $a]
10.000
% set a [format "%+6.4f" $a]
+9.9990
% set a [format "%6.4e"  $a]
9.9990e+00
% set a [format "%6.4f%%" $a]
9.9990%
% set a 9999999.4321
9999999.4321
% set a [format "%g" $a]
1e+07
% set a [format "%c" 97]
a

#3 案例3

% set a 99999
99999
% set a [format "%04d" $a]
99999

#4 案例4

% set a "alpha"
alpha
% set b a
a
% puts "format with no subst [format {$%s} $b]"
format with no subst $a
% puts "format with subst: [subst [format {$%s} $b]]"
format with subst: alpha
% eval "puts \"eval after format: [format {$%s} $b]\""
eval after format: alpha

#5 案例5

% set a arrayname
% set b index
% set c newvalue
% eval [format "set %s(%s) %s" $a $b $c]
% puts "Index: $b of $a was set to: $arrayname(index)"
Index: index of arrayname was set to: newvalue

#6 案例6

% set cmd "NOT OK"
% eval [format {%s "%s"} puts "Even This Works"]
Even This Works
% set cmd "And even this can be made to work"
% eval [format {%s "%s"} puts $cmd ]
And even this can be made to work
% set cmd {puts "This is Cool!}
% if {[info complete $cmd]} {
    eval $cmd
} else {
    puts "INCOMPLETE COMMAND: $cmd"
}
INCOMPLETE COMMAND: puts "This is Cool!

#7 案例7

% set num 0
% set cmd "proc temp {} "
% set cmd [format "%s {global num; incr num;" $cmd]
% set cmd [format {%s return "/tmp/TMP.%s.$num"} $cmd 45678]
% set cmd [format "%s }" $cmd ]
% eval $cmd
% puts "[info body temp]"
global num; incr num; return "/tmp/TMP.45678.$num" after#1 after#0


【解析】

[1]     案例1中被转化前的字符串在format中可以不使用引号;
[2]     这是基本的几个转换修饰符(-、+、0、空格、#)与转换格式(0、x或X、c、f、e或E、g或G、%)的使用举例;
[3]     由案例3可以看出,当被转换的参数的位数多于转换格式规定的位数时,转换格式限定条件失效,按照现实参数的情况转换,对于整形、字符串等类型参数的转换也如此; 但对于浮点型数字则不是如此;
[4]     由案例4、5、6可以看出该命令与置换命令subst配合使用的方法;
[5]     由案例7可以看出该命令可用于构建一个进程。
回复 支持 反对

使用道具 举报

该用户从未签到

4#
 楼主| 发表于 2009-12-9 17:27:30 | 只看该作者

re

问题11:请问TCL中有无类似于C中的SLEEP之类的休眠的函数?


回答:
1. 使用TCL内嵌脚本命令after、vwait;
2. 编写TCL扩展命令,将C中的sleep之类的函数进行封装。


after命令

【功能】

该命令用于延迟执行某程序或者在后续的某个时间段上执行某条命令。它有如下几种形式,取决于该命令后的第一个参数:

after ms?
ms应该是一个以毫秒为单位的整数。该命令在睡眠ms 后返回,当这个命令进入睡眠状态,TCL应用将不会响应事件。

after ms? script script script ...?
该模式下命令将立即返回,但是它会促使一个TCL命令在ms 毫秒后作为一个事件句柄来执行。在给定时间后该命令只执行一次。
延迟命令是通过链接所有脚本的语体而形成的,与concat命令形式类同。该命令执行级别是全局级(在TCL进程文本之外)。如果
执行一个延迟命令时发生错误,将启用bgerror机制来报错。after命令返回一个标识,通过after cancel 命令该标识可取消延
迟命令。

after cancel id
取消以前预定的延迟命令的执行。id代表要取消哪一个命令,必须是由前面的after 命令返回的数值。如果给出id的after命令
已经执行完毕,after cancel命令将失效。

after cancel script script ...
这个命令也是用于取消一个延迟命令的执行。几个脚本参数链接在一起,相互之间空格间隔开(如同concat命令中的样式)。如果
有一未决命令匹配于该字符串,这个命令将被取消并且不再被执行;如果当前没有这样的未决命令,after cancel 命令将会失效。

after info? id?
这个命令返回一些现存的事件句柄的信息。如果没带id这个参数,该命令返回所有由命令解释器下after命令创建的所有现存事件
句柄的标识符列表。如果提供了id,id 应明确表示了一个现存的句柄,并且必定是以前调用after 时的返回值,该id 还没有被
触发或者还没有被取消。在这种情况下,命令返回一个由两个表项构成的列表。列表的第一个表项是与id相关联的脚本,而第二个
表项则是事件句柄类型的空闲状态机或者定时器。

说明:after ms 和after idle命令假定了应用是由事件驱动的;除非应用进入了事件循环之中,否则延迟命令将不会被执行。在
异常事件驱动的应用中,比如tclsh,可以通过vwait 和update命令进入事件循环中。


【样例】

#1 延迟功能

% proc temp {x} {
        after $x
        puts "After $x ms, output this sentence."
}
% temp 1
% temp 1000
% temp 4000

从时间上可以明显感觉到快慢


#2 定时器功能功能

% proc temp {} {
        puts "Test 2 with after."
}

% after 1000 temp
after#0
% after 4000 temp
after#1
% after 8000 temp
after#2
% after info after#0
temp timer
% after info after#1
temp timer
% after info after#2
temp timer
% after info
after#2 after#1 after#0
% after cancel after#0
% after info after#0
event "after#0" doesn't exist
% after cancel temp
% after info after#2
event "after#2" doesn't exist
% after info after#1
temp timer
% after cancel temp
% after info after#1
event "after#1" doesn't exist
% after idle temp
after#4
% after info
after#4
% after info after#4
temp idle


#3 在进程中的其他应用实例

% proc temp1 {} {
        after 1000 temp
        puts "test 2 with after."
}

% temp1
test 2 with after.
% temp1
test 2 with after.
% after info
after#1 after#0


【解析】

[1] 从案例2可以看出,当执行after cancel script? 或after cancel id?时,会将最近一次设置的after ?ms script?
命令取消;
[2] 从案例3可以看出,在执行temp1中的after语句时,是一下就跳过去了,但会生成一个关于该次调用的id。

[ 本帖最后由 ameg3 于 2009-12-9 17:29 编辑 ]
回复 支持 反对

使用道具 举报

该用户从未签到

5#
 楼主| 发表于 2009-12-9 17:29:51 | 只看该作者

re

问题12: 如何在TCL中将数组作为参数传入过程中


案例:

要测试如下函数CTFCToTFI
typedef unsigned int UINT ;
typedef unsigned short USHORT;
typedef unsigned char UCHAR;
void CTFCToTFI (UINT uwCTFC,UCHAR ucTrCHNum,UINT uwP[],USHORT uhwInterTFI[])
{//根据uwCTFC,ucTrCHNum,uwP[]计算uhwInterTFI[].  
//uwP[],uhwInterTFI[]是数组,且长度由ucTrCHNum确定。如何传递参数uwp, uhwIterTFI?
        
        UINT m;
        UINT uwTemp;
        int i;

        m = uwCTFC;
        i = ucTrCHNum;

        for(i=ucTrCHNum-1; i>=0; i--)
        {
                uwTemp = m / uwP;
                /*因为m,uwP[i-1]均为正值,所以不需检验负数情况*/
                uhwInterTFI = uwTemp;/*(USHORT)(floor(m/uwP));*/
                m = m % uwP;
        }
        return;
}


解决方案:

#include "tcl.h"
int Tcl_InitApp(Tcl_Interp* interp);
int TestCmd(ClientData clientdata,Tcl_Interp* interp,int argc,char *  argv[]);

typedef unsigned int UINT ;
typedef unsigned short USHORT;
typedef unsigned char UCHAR;

USHORT pTFI[128];
void CTFCToTFI (UINT uwCTFC,UINT ucTrCHNum,UINT uwP[],USHORT uhwInterTFI[])     //被测函数
{
   ...
}

void main(int argc,char* argv[])           //主函数
{
        Tcl_Main(argc,argv,Tcl_InitApp);
        return;
}
int Tcl_InitApp(Tcl_Interp* interp)
{
        Tcl_Init(interp);      
        Tcl_CreateCommand(interp,"test",TestCmd,NULL,NULL);    //注册扩展命令test
        return TCL_OK;
}

int TestCmd(ClientData clientdata,Tcl_Interp* interp,int argc,char *  argv[])   
                                                              //扩展命令test
{
        UINT i,CTFC,Num=0;
        UINT pwP[128];
        UCHAR temp[80];
        
        //判断参数类型;
        //获取需传入被测函数的参数值;
        if(TCL_OK!=Tcl_GetInt(interp,argv[1],&CTFC))
        {
                sprintf(interp->result,"Expect integer but got %s",argv[1]);
                return TCL_ERROR;
        }
        if(TCL_OK!=Tcl_GetInt(interp,argv[2],&Num))
        {
                sprintf(interp->result,"Expect integer but got %s",argv[2]);
                return TCL_ERROR;
        }
        for (i=0;i<Num;i++)
        {
                if(TCL_OK!=Tcl_GetInt(interp

,argv[i+3],&pwP))
                {
                        sprintf(interp->result,"Expect integer but got %s",argv[i+3]);
                        return TCL_ERROR;
                }
        }
        CTFCToTFI(CTFC,Num,pwP,pTFI);       //向被测函数传入参数
        for (i=0;i<Num;i++)
        {               
                sprintf(temp, "%d\n", pTFI);
                strcat(interp->result, temp);       //输出结果
        }
        return TCL_OK;
}

测试脚本和结果如下:
% test 25 2 1 2    //其中uwCTFC=25,ucTrCHNum=2,uwP[0]=1,uwP[1]=2
1
12                 //uhwInterTFI[0]=1,uhwInterTFI[1]=12
回复 支持 反对

使用道具 举报

该用户从未签到

6#
 楼主| 发表于 2009-12-9 17:30:41 | 只看该作者

re

问题13:怎样用TCL来实现如下功能——从前台输入参数,并提示输入参数的名称


问题描述:

现做一APC驱动程序测试接口,其中流量类型的配置需一次输入22个参数,并且不同的PVC可能属于不同的流量类型。为方便计,想做一从TCL前台逐个输入参数值的配置方式,需调用TCL前台读入参数的函数接口,但没有找到类似的应用方法,请指教。


问题回答:

方法一:
puts stdout "format string"             ;#输出输入格式
get stdin var                           ;#表示从控制台读入字符串
scan $var "format string" varlist       ;#表示对字符串进行分析
如果不一次输入,可以用几个这样的组合完成输入过程。

方法二:
你可以先用list生成提示串outputstring
同时生成另一个列表变量存放输入值inputstring
然后用
foreach var $outputstring {
   puts $var
   get stdin inputstring($var)         ;#获取输入
}
回复 支持 反对

使用道具 举报

该用户从未签到

7#
 楼主| 发表于 2009-12-9 17:31:28 | 只看该作者

re

问题14:在开发TCL扩展命令时:如何保存TCL扩展命令的多次执行结果?


问题描述:
摘自《研发IT支撑体系》

扩展命令openchIP的功能是打开某IP地址的一个端口:
执行如下命令后:
set pco_id1 [openchIP 10.11.25.634:1762]
set pco_id2 [openchIP 10.11.25.634:1762]
set pco_id3 [openchIP 10.11.25.634:1762]

系统如何保存openchIP命令的3次执行结果,以备后面使用。



问题回答:

事实上执行脚本set pco_id1 [openchIP 10.11.25.634:1762]后,你已经在变量pco_id1中保存了openchIP的结果,并随时可以通过变量置换使用它,即$pco_id1。
如果你还需要保存别的东西,可以在命令openchIP对应的c/c++过程中把那些内容存到一个文件中,也可以存到一个TCL变量中。

如果要把结果存到TCL变量中,可以在openchIP命令的后面添加一个参数,作为保存结果的变量。形式如下:
openchIP port var

这时可以这样扩展openchIP:
int OpenchIPCmd(ClientData clientdata,Tcl_Interp* interp,int argc,char* argv[])
{
...
/*只要在你的代码中加下面几行,其中result是你想保存的内容,类型为char*,Tcl_SetVar在TCL解释器中生成一个变量,并把result的内容赋给它*/
if(NULL==Tcl_SetVar(interp,argv[2],result,0))
{      
        return TCL_ERROR;
}
...
}

你的TCL脚本就可以这样写了
set pco_id1 [openchIP 10.11.25.634:1762 var1]
puts $var1
set pco_id2 [openchIP 10.11.25.634:1762 var2]
puts $var2
set pco_id3 [openchIP 10.11.25.634:1762 var3]
puts $var3
你可以任意使用var1-3中的内容
回复 支持 反对

使用道具 举报

该用户从未签到

8#
 楼主| 发表于 2009-12-9 17:32:06 | 只看该作者

re

问题14:在开发TCL扩展命令时:如何保存TCL扩展命令的多次执行结果?


问题描述:
摘自《研发IT支撑体系》

扩展命令openchIP的功能是打开某IP地址的一个端口:
执行如下命令后:
set pco_id1 [openchIP 10.11.25.634:1762]
set pco_id2 [openchIP 10.11.25.634:1762]
set pco_id3 [openchIP 10.11.25.634:1762]

系统如何保存openchIP命令的3次执行结果,以备后面使用。



问题回答:

事实上执行脚本set pco_id1 [openchIP 10.11.25.634:1762]后,你已经在变量pco_id1中保存了openchIP的结果,并随时可以通过变量置换使用它,即$pco_id1。
如果你还需要保存别的东西,可以在命令openchIP对应的c/c++过程中把那些内容存到一个文件中,也可以存到一个TCL变量中。

如果要把结果存到TCL变量中,可以在openchIP命令的后面添加一个参数,作为保存结果的变量。形式如下:
openchIP port var

这时可以这样扩展openchIP:
int OpenchIPCmd(ClientData clientdata,Tcl_Interp* interp,int argc,char* argv[])
{
...
/*只要在你的代码中加下面几行,其中result是你想保存的内容,类型为char*,Tcl_SetVar在TCL解释器中生成一个变量,并把result的内容赋给它*/
if(NULL==Tcl_SetVar(interp,argv[2],result,0))
{      
        return TCL_ERROR;
}
...
}

你的TCL脚本就可以这样写了
set pco_id1 [openchIP 10.11.25.634:1762 var1]
puts $var1
set pco_id2 [openchIP 10.11.25.634:1762 var2]
puts $var2
set pco_id3 [openchIP 10.11.25.634:1762 var3]
puts $var3
你可以任意使用var1-3中的内容
回复 支持 反对

使用道具 举报

该用户从未签到

9#
 楼主| 发表于 2009-12-9 17:34:20 | 只看该作者

re

问题15:在TCL中如何触发事件?


问题描述:

设想为:在TCL脚本中先写一段TCL命令(或过程),然后用TCL命令登记一个事件(在登记的事件发生后,将自动执行前面的TCL命令段或过程),后面是其他的TCL命令。并且在TCL脚本中可以登记多个不同的触发事件,每个事件能随时触发,随时执行对应的TCLTCL命令段或过程。请问采用何种方式实现?


问题回答:

    使用变量跟踪trace命令
    TCL提供了trace命令来跟踪一个或多个变量。如果已经建立对一个变量的跟踪,则不论什么时候对该变量进行了读、写、或删除操作,就会激活一个对应的Tcl命令,跟踪可以有很多的用途:
    1.监视变量的用法(例如打印每一个读或写的操作)。
    2.把变量的变化传递给系统的其他部分(例如一个TK程序中,在一个小图标上始终显示某个变量的当前值)。
    3.限制对变量的某些操作(例如对任何试图用非十进制数的参数来改变变量的值的行为产生一个错误)或重载某些操作(例如每次删除某个变量时,又重新创建它)。

    trace命令的语法为:trace option ?arg arg...?
    其中option有以下几种形式:

1. trace variable name ops command
    这个命令设置对变量name的一个跟踪:每次当对变量name作ops操作时,就会执行command命令。name可以是一个简单变量,也可以是一个数组的元素或者整个数组。
    ops可以是以下几种操作的一个或几个的组合:
    r   当变量被读时激活command命令;
    w   当变量被写时激活command命令;
    u  当变量被删除时激活command命令,通过用unset命令可以显式的删除一个变量,一个过程调用结束则会隐式的删除所有局部变量。当删除解释器时也会删除变量,不过这时跟踪已经不起作用了。

    当对一个变量的跟踪被触发时,TCL解释器会自动把三个参数添加到命令command的参数列表中。这样command实际上变成了  command  name1 name2 op
    其中op指明对变量作的什么操作。name1和name2用于指明被操作的变量: 如果变量是一个标量,那么name1给出了变量的名字,而name2是一个空字符串;如果变量是一个数组的一个元素,那么name1给出数组的名字,而name2给出元素的名字;如果变量是整个数组,那么name1给出数组的名字而name2是一个空字符串。为了让你很好的理解上面的叙述,下面举一个例子:
% trace  variable   color    w   pvar
% trace  variable   a(length)   w   pvar
% proc pvar {name element op} {
       if {$element !=""} {
           set name ${name}($element)
       }


       upvar $name  x
       puts "Variable $name set to $x"
}
    上面的例子中,对标量变量color和数组元素a(length)的写操作都会激活跟踪操作pvar。我们看到过程pvar有三个参数,这三个参数TCL解释器会在跟踪操作被触发时自动传递给pvar。比如如果我们对color的值作了改变,那么激活的就是pvar color "" w。我们敲入:
% set color green
Variable color set to green
green
    command将在和触发跟踪操作的代码同样的上下文中执行:如果对被跟踪变量的访问是在一个过程中,那么command就可以访问这个过程的局部变量。比如:
% proc Hello { } {
set a 2
trace  variable   b  w    { puts $a  ;list }
set b 3
}
% Hello
2
3
    对于被跟踪变量的读写操作,command是在变量被读之后,而返回变量的值之前被执行的。因此,我们可以在command对变量的值进行改变,把新值作为读写的返回值。而且因为在执行command时,跟踪机制会临时失效,所以在command中对变量进行读写不会导致command被递归激活。例如:
% trace  variable  b  r  tmp
% proc tmp {var1 var2 var3 } {
upvar $var1  t
incr t 1
}
% set b 2
2
% puts $b


3
% puts $b
4
    如果对读写操作的跟踪失败,即command失败,那么被跟踪的读写操作也会失败,并且返回和command同样的失败信息。利用这个机制可以实现只读变量。下面这个例子实现了一个值只能为正整数的变量:
% trace variable size w forceInt
% proc  forceInt {name element op} {
   upvar $name x
   if ![regexp {^[0-9]*$} $x] {        
       error "value must b a postive integer"
    }
}
    如果一个变量有多个跟踪信息,那么各个跟踪被触发的先后原则是:最近添加的跟踪最先被触发,如果有一个跟踪发生错误,后面的跟踪就不会被触发。

2. trace vdelete name ops command
删除对变量name的ops操作的跟踪。返回值为一个空字符串。

3. trace vinfo name
    这个命令返回对变量的跟踪信息。返回值是一个list,list的每个元素是一个子串,每个子串包括两个元素:跟踪的操作和与操作关联的命令。如果变量name不存在或没有跟踪信息,返回一个空字符串。
回复 支持 反对

使用道具 举报

该用户从未签到

10#
 楼主| 发表于 2009-12-9 17:35:07 | 只看该作者

re

问题16:在TCL中如何获取程序执行效率的数据?


方法一:
    采用clock命令,在程序开始和结束时调用,然后减。
    这是个笨办法,其实TCL提供了实现该功能的内嵌命令,请看方法二。

    clock命令的语法:
        clock option ?arg arg ...?
    常用的clock命令有:
        clock seconds
        clock format clockValue ?-format string? ?-gmt boolean?
    例如:
    % set systemTime [clock seconds]
    987212535
    % puts "[clock format $systemTime -format %D] [clock format $systemTime -format %H:%M:%S]"
    04/14/01 09:42:15
    其中,
        %D      Date as %m/%d/%y.
        %H      Hour in 24-hour format (00 - 23).
        %M      Minute (00 - 59).
        %S      Seconds (00 - 59).


方法二:
    TCL提供time命令来衡量TCL脚本的性能。
    time命令的语法:
        time   script  ?count?
    这个命令重复执行script脚本count次,再把花费的总时间用count除,返回执行一次的平均执行时间,单位为微秒。如果没有count参数,就取执行一次的时间。其中script用{}或""括起。
   例如:
    % time { sendmsg pco_id msg1 } 10      
                        //sendmsg是ITT中的扩展命令,向指定测试通道pro_id中发送一条消息msg1
    219667 microseconds per iteration
    该命令还有一个妙用:就是如果要直接把某个命令循环多少次的话...
题17:在TCL中如何执行操作系统的命令?


使用exec命令

     例如,在脚本中执行ping 命令:

     % exec ping 10.121.13.100


        Pinging 10.121.13.100 with 32 bytes of data:



        Reply from 10.121.13.100: bytes=32 time<10ms TTL=128

        Reply from 10.121.13.100: bytes=32 time<10ms TTL=128

        Reply from 10.121.13.100: bytes=32 time<10ms TTL=128

        Reply from 10.121.13.100: bytes=32 time<10ms TTL=128



        Ping statistics for 10.121.13.100:

            Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),

        Approximate round trip times in milli-seconds:

            Minimum = 0ms, Maximum =  0ms, Average =  0ms


      这样在测试中就可以很好的利用其它的程序功能来实现复杂的测试了。

[ 本帖最后由 ameg3 于 2009-12-9 17:37 编辑 ]
回复 支持 反对

使用道具 举报

该用户从未签到

11#
 楼主| 发表于 2009-12-9 17:37:57 | 只看该作者

re

问题18:
    在TCL库函数中,有没有一个类似Tcl_GetInt()的函数从输入的参数中获取字符串的?
      摘自《研发IT支撑体系》


问题分析:

    每一个定义TCL扩展命令的C\C++函数或过程都有参数argc和argv,TCL解释器在解释执行TCL命令时,会把命令的参数个数和参数的字符串形式传给定义这个命令的C\C++函数的argc和argv参数,并执行这些函数或过程。
    因此,参数argv[n]就是那个你想要的字符串,不用再使用库函数Tcl_GetString()去Get了。


题外话:

    由于所有参数都以字符串形式传递,所以我们需要对它们进行分析,以下是一些常用的函数:

1. Tcl_GetIntFromObj 函数
原型:int Tcl_GetIntFromObj(Tcl_Interp *interp, register Tcl_Obj *objPtr, register int *intPtr )
说明:这个函数把objPtr作为一个整数分析,值存在*intPtr中。成功返回TCL_OK ,失败返回TCL_ERROR,并把错误信息存在
interp->result中。

2. Tcl_GetLongFromObj 函数
原型:int TclGetLongFromObj(Tcl_Interp *interp,register Tcl_Obj *objPtr, register long*intPtr )
说明:这个函数把objPtr作为一个长整型分析,值存在*longPtr中。成功返回TCL_OK ,失败返回TCL_ERROR,并把错误信息
存在interp->result中。

3. Tcl_GetDoubleFromObj 函数
原型:int Tcl_GetDoubleFromObj(Tcl_Interp *interp, register Tcl_Obj *objPtr, register double*intPtr )
说明:这个函数把objPtr 作为一个浮点数分析,值存在*doublePtr中。成功返回TCL_OK ,失败返回TCL_ERROR,并把错误信
息存在interp->result中。

4. Tcl_GetBooleanFromObj 函数
原型:int Tcl_GetBooleanFromObj(Tcl_Interp *interp, register Tcl_Obj *objPtr, register int *intPtr )
说明:这个函数把objPtr 作为一个BOOL值分析,并把0\1存在*intPtr中。成功返回TCL_OK ,失败返回TCL_ERROR,并把错误
信息存在interp->result中。

5. Tcl_GetStringFromObj 函数
原型:int Tcl_GetStringFromObj(register Tcl_Obj *objPtr, register int *lengthPtr )
说明:这个函数把objPtr 作为一个字符串分析,并把字符串长度存在*lengthPtr中,同时返回指向objPtr中字符串的指针。
问题19:在TCL中如何实现类似C++中的new操作?


问题回答:

没有标准的TCL命令完成此功能,必须自己扩展,可参考如下代码:

/*format: new size (new _UC[size])*/
int Tcl_NewCmd(ClientData dummy, Tcl_Interp* interp, int objc, Tcl_Obj *CONST objv[])
{
        _UI size;
        _UC buf[32];
        _UC *p;

        if(objc != 2)
        {
                Tcl_AppendResult(interp,"must be: new size",NULL);
                return TCL_ERROR;
        }

        if(Tcl_GetIntFromObj(interp, objv[1], &size) != TCL_OK)
        {
                Tcl_SetResult(interp, "memory size must be number" ,TCL_STATIC);
                return TCL_ERROR;
        }

        p = (_UC*)MAlloc(size);
        sprintf(buf, "%d", (_UI)p);
        Tcl_SetResult(interp,buf,TCL_VOLATILE);

        return TCL_OK;
}
回复 支持 反对

使用道具 举报

该用户从未签到

12#
 楼主| 发表于 2009-12-9 17:39:43 | 只看该作者

re

问题20:在TCL中如何实现如下功能:在一个字符串中,得出含有某个单词的个数


问题回答:

没有标准的TCL命令完成此功能,必须自己扩展,可参考如下代码:

# Process name:         temp
# Parameters  :         arg1  ----- the string be searched in arg2
#                       arg2  ----- the string in which you search arg1

proc temp {arg1 arg2} {
        set temp1 [string length $arg1]         
        set temp2 0
        set temp3 [string first $arg1 $arg2 0]

        if {$temp3 == -1} {
                puts "There is no matched $arg1 in $arg2"
                return -1
        }

        while {$temp3 >= 0} {
                incr temp2
                set temp3 [string first $arg1 $arg2 [expr $temp3 + $temp1]]
        }

        puts "Find $arg1 $temp2 times in $arg2..."
        return $temp2
}


string命令



问题21:在TCL脚本中如何产生随机数


方法一:编写proc
摘自《研发IT支撑体系》

% set a 4
% proc srand {seed} {
global a
set a $seed
}
% proc rand {} {
global a
set a [expr 1664525 * $a + 1013904223]
}
% srand [clock clicks -milliseconds]
% rand

注意事项:随机种子的初始值建议采用另外一个函数来生成,这个种子还是不太优良。


方法二:使用系统的rand()函数

% expr rand()
0.893715903579
% expr rand()
0.683191454356

    TCL实际上也是支持一些常用接口函数、包括数学表达式的,例如sin、cos等,rand也在范围内。
    在实际使用中还需注意的是,不是所有的单板软件中都把数学库包含进去了,所以如上的这些命令在使用前可以尝试一下,看看支持不支持。

[ 本帖最后由 ameg3 于 2009-12-9 17:41 编辑 ]
回复 支持 反对

使用道具 举报

该用户从未签到

13#
 楼主| 发表于 2009-12-9 17:42:22 | 只看该作者

re

问题23:如何查看现有的TCL变量


问题回答:
    使用info命令


info命令:
    info命令提供了查看TCL解释器信息的手段

1. 查看变量信息

info exists varName
如果名为varName的变量在当前上下文(作为全局或局部变量)存在,返回1,否则返回0。

info globals ?pattern?
如果没有pattern参数,那么返回包含所有全局变量名字的一个list。如果有pattern参数,就只返回那些和pattern匹配的全局变量(匹配的方式和string match相同)。

info locals ?pattern?
如果没有pattern参数,那么返回包含所有局部变量(包括当前过程的参数)名字的一个list,global和upvar命令定义的变量将不返回。如果有pattern参数,就只返回那些和pattern匹配的局部变量。

info vars ?pattern?
如果没有pattern参数,那么返回包括局部变量和可见的全局变量的名字的一个list。如果有pattern参数,就只返回和模式pattern匹配的局部变量和可见全局变量。模式中可以用namespace来限定范围,如:foo:ption*,就只返回namespace中和option*匹配的局部和全局变量。(注:tcl80以后引入了namespace概念,不过我们一般编写较小的TCL程序,可以对namespace不予理睬,用兴趣的话可以查找相关资料。)

下面针对上述命令举例,假设存在全局变量global1和global2,并且有下列的过程存在:
proc test {arg1 arg2} {
   global global1
   set local1 1
   set local2 2
   ...
}
然后在过程中执行下列命令:
% info vars
global1 arg1 arg2 local2 local1        //global2不可见
%  info globals
global2 global1
%  info locals
arg1 arg2 local2 local1
%  info vars *al*
global1 local2 local1

2. 查看过程信息

info procs ?pattern?
如果没有pattern参数,命令返回当前namespace中定义的所有过程的名字。如果有pattern参数,就只返回那些和pattern匹配的过程的名字。

info body procname
返回过程procname的过程体,procname必须是一个TCL过程。

info args procname
返回包含过程procname的所有参数的名字的一个list, procname必须是一个TCL过程。

info default procname arg varname
procname必须是一个TCL过程,arg必须是这个过程的一个变量。如果arg没有缺省值,命令返回0;否则返回1,并且把arg的缺省值赋给变量varname。

info level ?number?
如果没有number参数,这个命令返回当前过程在调用栈的位置。如果有number参数,那么返回的是包含在调用栈的位置为number的过程的过程名及其参数的一个list。

下面针对上述命令举例:
  proc maybeprint {a b {c 24}} {
    if {$a<$b} {
       puts stdout "c is $c"
    }
  }
% info body maybeprint
    if {$a<$b} {
       puts stdout "c is $c"
    }
% info args maybeprint
a b c
% info default maybeprint a x
0
% info default maybeprint c a
1

3. 查看命令信息

info commands ?pattern?
如果没有参数pattern,这个命令返回包含当前namspace中所有固有、扩展命令以及以proc命令定义的过程在内的所有命令的名字的一个list。pattern参数的含义和info procs一样。

info cmdcount
返回了一个十进制字符串,表明多少个命令曾在解释器中执行过。

info complete command
如果命令是command完整的,那么返回1,否则返回0。这里判断命令是否完整仅判断引号,括号和花括号是否配套。

info script
当前有脚本文件正在Tcl解释器中执行,则返回最内层处于激活状态的脚本文件名;否则将返回一个空的字符串。

4. 查看TCL的版本和库

info tclversion
返回为Tcl解释器返回的版本号,形式为major.minor,例如8.3。

info libraray
返回Tcl库目录的完全路径。这个目录用于保存Tcl所使用的标准脚本,TCL在初始化时会执行这个目录下的脚本。
回复 支持 反对

使用道具 举报

该用户从未签到

14#
 楼主| 发表于 2009-12-9 17:42:46 | 只看该作者

re

问题24:什么是namespace


【概念】
   名字作用域(namespace)是变量和命令的集合,作用域内的变量和命令不受其他作用域的变量或命令影响。Tcl也有这样的一个域,我们通常所指的是全局作用域。全局作用域包含所有的变量和命令,并提供命令让你创建新的作用域。
        namespace eval namespacename arg ?arg ...?
如:    namespace eval Counter {
        variable num 0
        proc bump {} {
        variable num
        incr num
        }
        }
    上面这段脚本创建了一个新的作用域Counter,包括变量num和程序bump,在同一个程序中,作用域内的变量和命令和其它的命令和变量是分开的。如在全局作用域有一命令bump,它与作用域Counter的bump不同。
    作用域是动态的,在任何时候可增加或删除。因此,可利用一系列作用域命令建立一个作用域目录,效果与上面显示的namespace定义相同:
        namespace eval Counter {
        proc test {args} {
        return $args
        }
        }
        namespace eval Counter {
        rename test ""
        }
    注意,这测试程序test被加入Counter作用域,而后,由重命名命令rename删了。
    作用域可嵌套。

【命名】
    每个作用域都有名字,如history或::safe::interp。有效的命名类似与Unix文件的继承路径名,除了一点,就是这里使用"::"当作分隔符,而不是"/"或".."。最高作用域名是""(空字串)。例如::safe::interp::create指的是create是作用域interp中的命令,而interp又是作用域safe的子作用域,safe又是全局作用域::的子作用域。
    如果你想从另外的作用域访问命令和变量,那么该命令或变量的名字必须有效的被作用域包含。从全局作用域访问Counter我们可以象这样:
        Counter::bump                   //调用Counter中的命令bump
        Counter::test 5                 //调用Counter中的命令test,并传递参数
        puts "count = $Counter::num"    //获取Counter中的变量num的值
    当你创建或重命名作用域时,你可以这样来操作:
        proc Foo::Test {args} {return $args}
        rename Foo::Test Bar::Test
    注意:作用域的名字必须是非空的;二个或二个以上的":s"被看作是分隔符,故单个":s"被忽略;在描述变量和命令的名称时一串"::"表示该变量或命令命名为{},在描述作用域的名称时一串"::"将被忽略。

【应用】
    namespace 经常用于表示库。一些库命令经常会用到,以至于敲入它们完整的名称时会变得令人厌烦。比如,假设类似于BLT的包中所有的命令包含于一个名为Blt的namespace中,那么你就可以这样来访问这些命令:
        Blt::graph .g-background red
        Blt::table . .g 0,0
    如果你经常使用图形和表格命令,你可能想不使用Blt::prefix就来访问它们。你可以把这些命令输入到当前namespace中,如下所示:
        namespace import Blt::*
这会把所有的从名字为Blt的namespace中输出的命令添加到当前的namespace的内容中,所以你可以这样写代码:
        graph .g-background red
        table . .g 0,0
    namespace import命令输入的命令仅来源于这样的namespace中:该namespace使用一个namespace export命令作为输出。既然你不知道你将会得到什么,通常从namespace中输入每一个命令是一个危险的举措。最好你只输入一些你想要的确定的命令,例如下面的命令仅将图形和表格输入到当前文本中:
        namespace import Blt::graph Blt::table
    如果你试图输入一个已存在的命令,就会产生错误。这会防止你从两个不同的包中输入相同的命令。但是经过反复操作(可能是调试时)后,你可能想避开这种限制。你可能想重新启用这个namespace import命令,以获取新的、在namespace中已出现的命令。这时你就可以使用-force 选项,现有的命令就会悄然地重写:
        namespace import-force Blt::graph Blt::table
  

    如果由于某些原因,你想停止使用输入命令,你可以借助于namespace forget命令将其移走,如下所示:
        namespace forget Blt::*
    这就会在当前namespace中查找任何从Blt中输入的命令,如果发现了任意一个,将会把它们移走。否则,它什么也不做。这一步操作后,必须使用Blt::prefix访问Blt命令。当你把一个命令从输出namespace中删除时,如下所示:
        rename Blt::graph ""
    该命令从所有的将其输入的namespace中自动移走。
回复 支持 反对

使用道具 举报

该用户从未签到

15#
 楼主| 发表于 2009-12-9 17:43:32 | 只看该作者

re

问题25:如何调用自扩展的Tcl过程库


问题描述:
    定制过程(proc)可以大大简化您的测试脚本,可是怎样才能方便地调用自扩展的过程库中的过程呢?

问题解答:
    使用TCL的自动加载功能。
    自动加载提供了两个好处:首先,你可以把有用的过程建立为过程库,而你无需精确知道过程的定义到底在哪个源文件中,自动加载机制会自动替你寻找;第二个好处在于自动加载是非常有效率的,如果没有自动加载机制你将不得不在TCL应用的开头使用source命令来加载所有可能用到的库文件,而应用自动加载机制,应用启动时无需加载任何库文件,而且有些用不到的库文件永远都不会被加载,既缩短了启动时间又节省了内存。

    使用自动加载只需简单的按下面三步来做:
    第一,在一个目录下创建一组脚本文件作为库,一般这些文件都以".tcl"结尾。每个文件可以包含任意数量的过程定义。建议尽量减少各脚本文件之间的关联,让相互关联的过程位于同一个文件中。为了能够让自动加载功能正确运行,proc命令定义一定要顶到最左边,并且与函数名用空格分开,过程名保持与proc在同一行上。
    第二步,为自动加载建立索引。启动Tcl应用比如tclsh,调用命令auto_mkindex dir  pattern , 第一个参数是目录名,第二个参数是一个模式。auto_mkindex在目录dir中扫描文件名和模式pattern匹配的文件,并建立索引以指出哪些过程定义在哪些文件中,并把索引保存到目录dir下一个叫tclindex的文件中。如果修改了文件或者增减过程,需要重新生成索引。
    第三步是在应用中设置变量auto_path,把存放了希望使用到的库所在的目录赋给它。auto_path变量包含了一个目录的列表,当自动加载被启动的时候,会搜索auto_path中所指的目录,检查各目录下的tclindex文件来确认过程被定义在哪个文件中。如果一个函数被定义在几个库中,则自动加载使用在auto_path中靠前的那个库。
    例如,若一个应用使用目录/usr/local/tcl/lib/shapes下的库,则在启动描述中应增加:
   set auto_path     [linsert $auto_path 0 /usr/local/tcl/lib/shapes]
    这将把/usr/local/tcl/lib/shapes作为起始搜索库的路径,同时保持所有的Tcl/Tk库不变,但是在/usr/local/tcl/lib/shapes中定义的过程具有更高的优先级,一旦一个含有索引的目录加到了auto_path中,里面所有的过程都可以通过自动加载使用了。
回复 支持 反对

使用道具 举报

该用户从未签到

16#
 楼主| 发表于 2009-12-9 17:44:08 | 只看该作者

re

问题26:什么是包(package)
    对于一些经常用到的TCL过程或命令(这些命令和过程我们可能纯粹用TCL脚本编写,也可能在一个C的动态链接库中作为TCL的扩展实现),我们通常希望能把他们做成类似于标准C库函数的形式,可以方便的被各个应用程序共享。TCL提供了包(package)的功能,来帮我们达成上述目标。TCL中,包是一些命令和过程的集合,可以提供版本控制功能。
    TCL的包可以有两种形式:纯粹的TCL脚本和二进制文件(在windows中是DLL)。
用TCL脚本实现包
    TCL提供了package命令来实现包的功能。package命令有多个选项,但我们需要用到的只是其中的两个:package provide 和package require。
    package provide  package  version
    这个命令用于指明脚本文件提供的包的名字和版本。 package和version分别是你要提供的包的名字和版本。
    package require ?-exact? package ?version?  
    这个命令用于指明你要使用的包的名字和版本,TCL会启动自动加载机制把你需要的包加载进来。其中,-exact开关是针对版本而言。如果没有指明-exact和version,TCL会自动加载它能找到的包package的最新版本。如果给出了version,但没有指明-exact,那么TCL解释器会加载它能找到的高于version的版本中的最新版本,否则报错。如果同时指明了-exact和version,那么必须加载version版本。如果TCL解释器找不到符合条件的版本,就会报错。
    使用TCL脚本实现包很简单,你只需要把你想包含在某个包中的所有自定义过程放到一个TCL脚本文件中,并使用package provide命令指明这个包的名字和版本即可。下面的文件add.tcl就实现了一个名为add的包:
    package provide add 1.0
    proc add { left right } {
    return [expr $left+right]
    }
    一个包可以在多个文件中实现,你只需要在调用package provide命令时提供相同的包名和版本号即可。但是一个文件中只能实现一个包。

[ 本帖最后由 ameg3 于 2009-12-9 17:45 编辑 ]
回复 支持 反对

使用道具 举报

该用户从未签到

17#
 楼主| 发表于 2009-12-9 17:46:02 | 只看该作者

re

用DLL实现包

    利用DLL实现包和我们平常利用DLL来扩展命令只有稍微的差别:1,必须引出一个和DLL的名字关联的初始化函数;2,在该初始化函数中调用TCL库函数Tcl_PkgProvide。
    下面我们利用一个win32 的non-MFC DLL来实现一个名为add的package,其中也实现了add扩展命令,为了和上面的add包区别,我们把这里的实现作为版本2.0。
    启动VC6,点击菜单File/New,在弹出的对话框中选择projects属性页,在这个属性页中选择Win32 Dynamic-Link Library,然后在右边的“Project name”栏输入工程名Add,在"Location"栏选择你的工程想保存的目录,然后点击“OK”按钮,在接下来的对话框中,会有三个选项,为了方便,可以选择选项“A DLL that exports some symbols”,最后点击"finish"按钮,工程自动生成。
    打开工程中的文件add.h,add.cpp,我们会发现,MFC已经自动给我们添加了一个变量,一个类和一个函数的引出示例。在add.h和add.cpp中把这些变量、类和函数的声明和实现删除。然后加上我们自己的代码。
add.h如下:
#ifdef ADD_EXPORTS
#define ADD_API __declspec(dllexport)
#else
#define ADD_API __declspec(dllimport)
#endif
#include "tcl.h"
extern "C" ADD_API int Add_Init(Tcl_Interp *interp);

add.cpp中的代码如下:
#include "stdafx.h"
#include "Add.h"
int AddCmd(ClientData clientData, Tcl_Interp *interp,int argc, char *argv[]);

BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
{
    switch (ul_reason_for_call)
        {
                case DLL_PROCESS_ATTACH:
                case DLL_THREAD_ATTACH:
                case DLL_THREAD_DETACH:
                case DLL_PROCESS_DETACH:
                        break;
    }
    return TRUE;
}

ADD_API int Add_Init(Tcl_Interp *interp)
{
        Tcl_CreateCommand(interp, "add", AddCmd,
                        (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
        
        Tcl_PkgProvide(interp, "Add", "2.0");
        return TCL_OK;
}


int AddCmd(ClientData clientData, Tcl_Interp *interp,
                int argc, char *argv[])
{      
        int n1,n2;
        if (argc != 3) {
                interp->result = "Usage error: add int1 int2";
                return TCL_ERROR;
        }
        
        if(TCL_OK != Tcl_GetInt(interp, argv[1], &n1))
        {
                sprintf(interp->result,"Expect int but get %s",argv[1]);
                return TCL_ERROR;
        }
        if(TCL_OK != Tcl_GetInt(interp, argv[2], &n2))
        {
                sprintf(interp->result,"Expect int but get %s",argv[2]);
                return TCL_ERROR;
        }        
        sprintf(interp->result, "%d", n1+n2);
        return TCL_OK;
}

    自己设置好包含tcl头文件的目录,并把tcl的库文件加入工程中,编译就可以得到Add.dll动态链接库了。
    TCL要求实现包的DLL必须导出一个初始化函数,在这个函数中注册包中扩展的所有TCL命令,比如上述例子中的导出函数是Add_Init。TCL为了能方便的使用自动加载功能,对DLL的名字、包的名字、导出函数的名字有严格规定。DLL的名字必须和包的名字一样,导出函数的名字必须是DLL的名字+下划线+Init。例如上述例子中,DLL的名字是Add.dll,那么包的名字就必须是Add,导出函数的名字就必须是Add_Init,当然可以不区分大小写。注意导出函数声明时必须加上extern "C",以避免编译器对它进行名字修饰,不然,加载Add.dll时TCL会报告找不到Add_Init函数。
回复 支持 反对

使用道具 举报

该用户从未签到

18#
 楼主| 发表于 2009-12-9 17:46:25 | 只看该作者

re

问题27:如何使用包(package)


    包的使用和TCL的自动加载功能类似,不过自动加载不能提供版本控制。

方法一(适用与.tcl和.dll形式的库文件):

    当我们想使用某一个包时,可以调用package require命令。比如,
        package require add 2.0
    当启动package require命令时,TCL是按照如下机制来载入包的:首先,package机制检查是否已经有所要加载的包的信息,如果有,就生成在这个包中实现的TCL命令的索引,这样当用户调用这些命令时可以被TCL自动加载进来;如果没有所要加载的包的信息,tclPkgUnknown命令会被调用,来寻找这个包的信息; tclPkgUnknown命令查找auto_path中包含的目录及其子目录下的PkgIndex.tcl索引文件,通过这些索引文件来生成一个包含包及其版本信息的内部数据库,并调用tclPkgSetup来建立各个package中包含的命令的索引。对于tclPkgUnknown、tclPkgSetup等命令,只在TCL内部使用,我们可以不必深究。
    package require命令并不直接把你的包中实现的命令或过程直接加载进来,只是当你的包中实现的命令第一次被使用时,TCL才把它加载进来。这个自动加载的机制,依赖于一个记录你的包中实现的命令的索引文件PkgIndex.tcl,这个索引文件可以借助TCL的一个命令pkg_mkIndex来生成。我们可以把实现包的TCL脚本或DLL文件作为pkg_mkIndex的输入,输出文件是pkgIndex.tcl。每个目录中可以包含很多实现了包的TCL脚本文件和DLL文件,但只能有一个pkgIndex.tcl文件。  
    pkg_mkIndex对TCL文件和DLL文件同样适用,比如如果我们把所有的TCL脚本文件和DLL文件放到目录 C:\MyLib目录下,我们可以这样生成索引文件:
        pkg_mkIndex c:/MyLib  *.tcl  *.dll
    注意,上面的c:/MyLib中使用的不是'\',而是UNIX系统风格的'/'。上面的命令生成包含所有这些文件中定义的包及其中定义的命令信息的索引文件pkgIndex.tcl。不过因为tclPkgUnknown命令只会在auto_path中包含的目录及其子目录下查找pkgIndex.tcl文件,所以如果c:/MyLib不在auto_path包含的目录下中,需要把它加到auto_path中:
    lappend auto_path c:/MyLib
    作了以上工作之后,就可以使用:
    package require add 2.0
    然后就可以使用包中的add命令了。


方法二(仅适用于.dll形式的库文件)

    使用load命令。假设你的dll文件的目录是local/usr/lib/Add.dll,那么如下即可:
        % load local/usr/lib/Add.dll
        %
    如果单纯只想使用Load命令来加载DLL,其实并不需要提供package功能,我们可以不必在Add_Init中调用Tcl_PkgProvde函数。不过还是得导出初始化函数,并且函数名还必须是DLL的名字+下划线+Init。
回复 支持 反对

使用道具 举报

该用户从未签到

19#
 楼主| 发表于 2009-12-9 17:47:09 | 只看该作者

re

问题28:如何通过TCL访问数据库


操作步骤:

第一步:
    将tclodbc83.dll拷到\Tcl\bin目录下;

第二步:
    在控制面板的ODBC数据源中进行数据源(datasource)的设置;
这里我增加了一个绑定Access数据库的用户数据源tcl,用户名tcl,密码tcl

第三步:
    通过TCL脚本来访问数据库内的内容:

% load tclodbc83
% database
Usage:
  database [connect] id datasourcename userid password   //数据库连接方法一
  database [connect] id connectionstring                 //数据库连接方法二
  database configure operation driver attributelist
  database datasources
  database drivers
  database version

//连接数据库:database [connect] id datasourcename userid password
% database db tcl tcl tcl      
db

//访问数据库:id "sql语句"
% set data [db "select * from table1"]                  //获取表table1中所有字段的内容   
{1 a 88} {2 b 100}                                 
% set data [db "select name from table1"]               //获取表table1中name字段的内容
a b

其中表table1中有三个字段:ID, name, score,只有两条记录{1 a 88} {2 b 100}


第四步:
    对数据库访问到的内容进行操作:

% set data [db "select * from table1 where .."]
{1 a 88} {2 b 100}

变量data实际上保存的就是一个结果集,在TCL中称为list,上面这个list中又嵌套了list

通过循环可以将list中的项一一取出,比如下面的脚本可取出{1 a 88}和{2 b 100},如果需要再精确到某一具体字段,可再嵌套一个循环

set length [llength $data]
for {set i 0} {$i<=$length} {incr i 1} {
    set value($i) [lindex $data $i]
    puts values($i)
    ....
}
回复 支持 反对

使用道具 举报

该用户从未签到

20#
 楼主| 发表于 2009-12-9 17:47:51 | 只看该作者

re

问题29:如何在TCL中捕获错误


问题回答:
    使用catch命令

catch命令:
    错误通常导致所有活动的TCL命令被终止,但是有些情况下,在错误发生后继续执行脚本是有用的。例如,你用unset取消变量x的定义,但执行unset时,x可能不存在。如果你用unset取消不存在的变量,会产生一个错误:
      % unset x
      can't unset "x": no such variable
    此时,你可以用catch命令忽略这个错误:
      % catch {unset x}
      1
    catch的参数是TCL脚本。如果脚本正常完成,catch返回0。如果脚本中发生错误,catch会俘获错误(这样保证catch本身不被终止掉)然后返回1表示发生了错误。上面的例子忽略unset的任何错误,这样如果x存在则被取消,即使x以前不存在也对脚本没有任何影响。
    catch命令可以有第二个参数。如果提供这个参数,它应该是一个变量名,catch把脚本的返回值或者是出错信息存入这个变量。
      %catch {unset x} msg
      1
      %set msg
      can't unset "x": no such variable
    在这种情况下,unset命令产生错误,所以msg被设置成包含了出错信息。如果变量x存在,那么unset会成功返回,这样catch的返回值为0,msg存放unset命令的返回值,这里是个空串。如果在命令正常返回时,你想访问脚本的返回值,这种形式很有用;如果你想在出错时利用错误信息做些什么,如产生log文件,这种形式也很有用
问题30:为什么使用puts命令后没有将数据写入文件中?


问题描述:

    set filehandle [open testfile.txt a+]    //open命令返回一个字符串fileId 用于表识打开的文件
    set info "This is test info..."
    puts $filehandle $info
    ......
    close #filehandle
   
    当脚本执行完puts语句后,为什么testfile.txt中没有写进去任何的内容?


问题回答:

    puts命令使用C的标准I/O库的缓冲区方案,这就意味着使用puts产生的信息不会立即出现在目标文件中,当缓冲区满时才会向文件写入。不过你可以用flush随时强制写入,当文件关闭时缓冲区数据会自动flush。
flush fileId

    flush命令迫使缓冲区数据写到fileId 标识的文件中,flush直到数据被写完才返回,命令返回值为空字符串。


附:文件的读命令

gets fileId ?varName?
读fileId 标识的文件的下一行,忽略换行符。如果命令中有varName就把该行赋给它,并返回该行的字符数(文件尾返回-1),如果没有varName参数,返回文件的下一行作为命令结果(如果到了文件尾,就返回空字符串)。

和gets类似的命令是read,不过read不是以行为单位的,它有两种形式:
1. read ?-nonewline? fileId
读并返回fileId 标识的文件中所有剩下的字节。如果没有nonewline开关,则在换行符处停止。
2. read fileId numBytes
在fileId标识的文件中读并返回下一个numbytes字节。
回复 支持 反对

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-4-25 01:38 , Processed in 0.085959 second(s), 27 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表