<?xml version="1.0" encoding="GBK"?>
<?xml-stylesheet href="/style/rss.css" type="text/css"?>
<rss version="2.0" xmlns:eb="http://blog.tom.com/">
<channel>
  <title>一粒阳光</title>
  <link>http://blog.tom.com/teresa_cj</link>
  <description><![CDATA[宠辱不惊，闲看厅前花开花落；去留无意，漫随天外云卷云舒；有缘的朋友加我qq吧:498793700 ]]></description>
  <language>zh</language>
  <generator>newblog.tom.com RSS</generator>
  <pubDate></pubDate>    <item>
		<title><![CDATA[ Dreamweaver实现鼠标移到上面更改图片 ]]></title>
		<link>http://blog.tom.com/teresa_cj/article/315.html</link>
		<description><![CDATA[ insert-&gt;interactive &nbsp; images-&gt;rollover &nbsp; images &nbsp; <br />&nbsp; 插入-&gt;交互式图像-&gt;鼠标经过图像&nbsp;&nbsp; <br /> ]]></description>
		<eb:creationDate>2006-11-22 10:35:13</eb:creationDate>
		<eb:modificationDate></eb:modificationDate>
    </item>
    <item>
		<title><![CDATA[ 自制面模大杂烩 ]]></title>
		<link>http://blog.tom.com/teresa_cj/article/314.html</link>
		<description><![CDATA[ <BR><font color="#ff0000">香蕉面膜</font>：将香蕉去皮捣烂成糊状后敷面，15－20分钟后洗去，长期坚持可使脸部皮肤细嫩、清爽，特别适用于干性或敏感性皮肤的面部美容，效果良好。此方法可使皮肤清爽滑润，并可去除脸部痤疮及雀斑。这种方法适合任何一种皮肤，一周一次，可软化角质净白皮肤。<BR>&nbsp;<BR><font color="#ff0000">牛奶美白</font>：准备一小杯鲜奶，（夏天的时候,也可以把鲜奶到放到冰箱里,敷上凉凉的,会更舒服）。用蒸气蒸脸,将化妆棉吸满鲜奶，敷在脸上十五分钟左右，取下，用清水将脸上的牛奶洗净。长期坚持，可以使肤色白净均匀。&nbsp;<BR>&nbsp;<BR><font color="#ff0000">番茄净肤去油面膜&nbsp;</font>: 番茄1个（中型）、奶粉2大匙、蜂蜜2茶匙&nbsp;<BR>1将熟透的红番茄用汤匙捣烂。<BR>2然后将奶粉和蜂蜜加入捣烂的番茄泥中，均匀搅拌成糊状。<BR>3洗脸后，均匀涂于面上，然后于T字部位敷厚一点并稍加按摩，10分钟后用温水清洗干净即可。（涂抹在脸上后，以湿的热毛巾或用市售的纸面膜盖上，可以更帮助吸收哦！）<BR>这个番茄净肤去油面膜，有很不错的平衡油脂功效，还有清洁、美白与镇静效果，非常适合油性肌肤的美眉哦！因为脸上有许多没有用的角质，所以可以趁敷脸时去除，加上茄红素和奶粉的的滋养，可以让肌肤柔嫩有弹性。<BR>番茄可以改善平衡油脂分泌，让肌肤在任何时候不油不腻，更有弹性。如果觉得麻烦，也可以将番茄切成薄片，直接贴在脸上，约20分钟就可以达到美肤效果。还有呀，也可以拿这些番茄切片在脸上搓揉按摩，可以去角质兼柔嫩肤质。番茄汁也可以好好利用哦！可以拿来擦脸，有不错的美白效果，长痘痘的美眉也可以试试以化妆棉沾一些番茄汁擦脸，可以去油消炎，舒缓痘痘的不适呢！总之，我真的很喜欢番茄，除了吃番茄、喝番茄汁外，番茄面膜是我保持白皙水嫩的秘密武器，来，和我一样来当番茄小美人！<BR>&nbsp;<BR><font color="#ff0000">木瓜嫩肤青春面膜</font>:木瓜1个、鲜奶2汤匙、蜂蜜2茶匙<BR>1把木瓜果肉用汤匙挖出，放在碗中，捣成泥状。<BR>2再慢慢地加入鲜奶和蜂蜜，并均匀搅拌，直至成糊状。<BR>3洗完脸后，涂于脸上，敷约10至15分钟。（涂抹在脸上后，以湿的热毛巾或用市售的纸面膜盖上，可以更帮助吸收哦！） <BR>4再用清水洗净即可。<BR>木瓜含有很珍贵天然的水果酵素，可以深层清洁躲藏在毛孔内的污垢，还可以收敛毛孔。木瓜的维生素B群还能复活疲倦的肌肤，给肌肤满满的活力。<BR>我每天早上都喝木瓜炖奶酪，它可是我一天活力的来源。木瓜是美容水果的排行前几名，它的功效数都数不清，除了能抗氧化和促进肌肤的正常代谢外，还可以迅速地白嫩肌肤、活氧、淡化斑点和丰胸美白。用木瓜肉来敷脸，效果是一下子就能感受到，白白嫩嫩的滑溜感，敷几次就可以摸得到了。想要美白水嫩，木瓜白嫩面膜最合我意啦！<BR>&nbsp;<BR>&nbsp; ]]></description>
		<eb:creationDate>2006-11-17 09:13:03</eb:creationDate>
		<eb:modificationDate></eb:modificationDate>
    </item>
    <item>
		<title><![CDATA[ 欧蕾橄榄油 ]]></title>
		<link>http://blog.tom.com/teresa_cj/article/313.html</link>
		<description><![CDATA[ <font face="Arial" color="#993300" size="2">●●再来看看橄榄油的18大功效：★★ <br />1、&nbsp;在洗完脸后，用“橄榄油＋盐”反复轻轻按摩脸部,起磨砂、滋润作用。再用蒸脸器或热毛巾敷面，除去毛孔内肉眼看不到的污垢,增加皮肤的光泽和弹性。或将四分之一杯橄榄油放入微波炉稍加热后拿出,待温度适中后均匀涂抹在脸上,可有效调理干性及敏感性肌肤。 <br />2、&nbsp;敷脸,先用温水把脸上的油污洗净，以干毛巾轻轻拭去水分,再用棉花蘸橄榄油，遍抹于脸上，经过10～15分钟后,用热毛巾敷面，最后以干毛巾轻轻擦拭即可。 <br />3、&nbsp;将牛奶50毫升，加4－5滴原生橄榄油，用面粉适量调匀敷面，保留20分钟后用清水洗净,长期使用,可增加皮肤的活力和弹性。 <br />4、&nbsp;4、将一个鸡蛋打散，加入半个柠檬汁及一点点粗盐，充分搅拌均匀，而后将原生橄榄油徐徐加入鸡蛋汁里，使二者混合均匀。平日可将此面膜储存在冰箱里,一周做一至二次,不仅可防皱,还可以促进皮肤的光滑细致。 <br />5、&nbsp;是用一匙砂糖和原生橄榄油混合制成面膜，每周用三次，收缩毛孔，美白肌肤。 <br />6、&nbsp;美手：我们手背上只很少的皮脂腺，日常生活中又经常暴露在外，因此需要经常地补充水分与油分，否则，肌肤很容易变得干燥。无论在夏季还是在冬季，建议你随身准备一瓶原生橄榄油,&nbsp;在你洗手或是感到干燥时及时滋润，以保持双手的娇嫩。每星期给双手进行一次特殊的护理,用少量的磨砂膏按摩双手10－15分钟，去除手部死皮，然后在温热的原生橄榄油中浸泡5分钟。 <br />7、&nbsp;护唇：天气突然转冷、感冒或肠胃不好时,人们常会出现嘴唇脱皮干裂，这时，只要涂上少量橄榄油,两三天后就可以让你的嘴唇重新变得光润.用法:晚上睡觉前用热毛巾敷一下嘴唇，再用化妆棉沾橄榄油覆盖嘴唇，隔天起来又是粉粉动人。尤其对每天都要涂口红的人来说更需要这样来保养嘴唇。现在还有不少女士用橄榄油来取代唇彩，令嘴唇亮出天然光泽。 <br />8、&nbsp;护肤：秋冬季节皮肤比较干燥，特别是手关节、脚关节以及臀部等部位容易干燥和产生皱纹，橄榄油具有滋润及保养作用，能使皮肤恢复自然弹性。在普通干燥的时候可每星期擦三次，特别干燥的日子里可每日擦用。 <br />9、&nbsp;护发：一般的头发护理很简单，只需在梳头前在梳子上滴上三四滴橄榄油即可，而且不会使头发油腻。另外，在洗头之后，像用护发素一样，先擦干头发，再均匀地给秀发抹上橄榄油，然后用热毛巾包裹头发10分钟，便可使头发变得光泽柔顺。或者往护发素里加几滴橄榄油可以使头发得到深层护理，当在浴缸里泡着的时候把调理后的护发素平滑地梳进发丝里，然后就不用管它了，浴缸里冒出的蒸气会帮助护发素渗透的。 <br />10、&nbsp;上妆和卸妆：用法：上妆，在基础化妆品中滴1～2滴橄榄油，揉搓后均匀涂抹于面部，皮肤马上就变得亮泽而生动；卸妆，在化妆棉上滴两滴橄榄油来卸妆，它可以把顽固的彩妆卸掉，包括标榜防水的化妆品。 <br />11、&nbsp;除妊娠纹：用一匙橄榄油擦于妊娠纹处，轻轻按摩，长期坚持，可去除妊娠纹，或使之变浅。 <br />12、&nbsp;防眼角皱纹：用法：眼角纹，对付恼人的细纹、鱼尾纹或者眼部细纹，可以用两滴橄榄油加上少许芦荟胶拌匀后抹于细纹处，一旦涂上去就吸收了，马上看不出纹路（外出时不用洗掉可以直接上妆）。 <br />13、&nbsp;沐浴：用橄榄油洗澡？太奢侈了！不过大美女索菲娅&nbsp;罗兰就是这么保持肌肤的滋润和弹性的。不过我们用的时候可不要这么浪费，只要用棉花浸透橄榄油，均匀遍涂在全身，再用热毛巾包裹10分钟，用温水清洗即可。 <br />14、&nbsp;润身：洗完澡后在一杯水里加入一些橄榄油拌均匀，分次淋在身体的每一个部位，再适当地按摩，可以使皮肤更光滑. <br />15、&nbsp;高纯度冷榨特级原生橄榄油可以直接饮用，有助于促进身体的新陈代谢和排泄，橄榄油所含的维生素E，易于被皮肤吸收，从而有助于保持女性的体态美。它不仅可以帮助消化，还能预防便秘，所以减肥效果显著。记得在每天早餐前，空腹喝两勺橄榄油，减肥的效果十分理想。 <br />16、&nbsp;防晒：橄榄油使紫外线容易被吸收，不致于灼伤皮肤。所以在有太阳但阳光不很烈的情况下，可涂橄榄油防晒。 <br />17、&nbsp;去污：橄榄油也可作为去污的清洁剂，尤其适合利用在耳朵、肚脐等地方。它还是很好的卸妆油，可彻底清洁皮肤，防止化妆品残留在毛孔中，造成阻塞。 <br />18、&nbsp;护脚：据说这是专业模特儿们护足的“秘方”：给双脚上膜.将海盐与橄榄油以2:1的比例调配,充分按摩脚部后,再用毛巾擦干；给双脚按摩。将凡士林加入数滴橄榄油，混和后即可用来给双脚按摩。&nbsp;对付足部长茧、脚底长茧,除了尽量选用质软、低跟的鞋外，常用滋养油润泽脚底也很重要，选择原生橄榄油在晚上睡觉时涂在脚底，穿上棉袜，让热气帮助毛细孔张开，油分被吸收而使双脚皮肤变得柔软光滑第二天起床后用浮石摩擦长茧部位，最后用温水洗净。一星期护理一次，可以看出明显的效果 </font><BR><font face="Arial" color="#993300" size="2">&nbsp; </font><BR><font face="Arial" color="#993300" size="2">★★★大家先来了解一下橄榄油的知识吧！！ <br />首先是：【国际橄榄油协会对橄榄油的等级分类】 <br />&nbsp;&nbsp;&nbsp;&nbsp;一级橄榄油是直接由新鲜的橄榄果实中采取机械冷榨的方法榨取、经过过滤等处理除去异物后得到的油汁，加工过程中完全不经化学处理。此类橄榄油具有独特香味，金黄色中带有绿色，口感丰富，相当于果汁，适用于直接食用或凉拌菜用。根据液体中的酸性值不同，一级橄榄油又分为3个级别： <br />Ⅰ、特级初榨橄榄油（Extra&nbsp;Virgin），质量最高的一种橄榄油，酸性值一般在0.2%，最高不超过1%。一般在1.5%以下，欧蕾特级初榨橄榄油即是属于此种油类，酸性低于1%。 <br />★★★★★大家注意：只有标有（Extra&nbsp;Virgin）也就是特级初榨或者特纯冷榨的橄榄油，才能直接用在皮肤上！！★★★★★★ <br />Ⅱ、优质初榨橄榄油（Fine&nbsp;Virgin），酸性值比特级油稍高，但不超过2%。 <br />Ⅲ、普通初榨橄榄油（Ordinary&nbsp;Virgin）,味道纯正、芳香，酸性值不超过3.3%（酸性值超过3.3%的初榨橄榄油不适合直接食用，需精炼）。 <br /><br />二级橄榄油则为PURE&nbsp;OLIVE&nbsp;OIL，即经过二榨、三榨等级的油品，加进EXTRA&nbsp;VIRGIN油调配而成，质量不及初榨油，但价格较为便宜。 <br />经过五榨或五榨以上的油，一般标示为POMACE、SANSADIOLIVA、ORUJO，在制作过程中会产生一种碳氢化合物，有致癌的可能，因此属于工业用油，不在食用油等级之列。</font> ]]></description>
		<eb:creationDate>2006-11-16 13:23:35</eb:creationDate>
		<eb:modificationDate></eb:modificationDate>
    </item>
    <item>
		<title><![CDATA[ 设为首页  加入收藏 代码 ]]></title>
		<link>http://blog.tom.com/teresa_cj/article/312.html</link>
		<description><![CDATA[ <br />&nbsp;&lt;A href="javascript:window.external.AddFavorite('http://www.xxx.com', 'teresa的blog')"&gt;加入收藏&lt;/a&gt; <br /><br />&lt;a href=# onClick="this.style.behavior='url(#default#homepage)';this.setHomePage(www.xxx.com');"&gt;设为首页&lt;/a&gt; ]]></description>
		<eb:creationDate>2006-11-15 12:28:47</eb:creationDate>
		<eb:modificationDate></eb:modificationDate>
    </item>
    <item>
		<title><![CDATA[ js中时间代码 ]]></title>
		<link>http://blog.tom.com/teresa_cj/article/311.html</link>
		<description><![CDATA[ <BR>document.writeln("&lt;div align="center"&gt;");<br />var today=new Date();<br />date = today.getDate();<br />month= today.getMonth() +1;<br />year= today.getYear();<br />document.write(year);<br />document.write("年");<br />document.write(month);<br />document.write("月");<br />document.write(date);<br />document.write("日");<br />document.write("&amp;nbsp");<br />if (today.getDay() == 0) document.write("星期日")<br />if (today.getDay() == 1) document.write("星期一")<br />if (today.getDay() == 2) document.write("星期二")<br />if (today.getDay() == 3) document.write("星期三")<br />if (today.getDay() == 4) document.write("星期四")<br />if (today.getDay() == 5) document.write("星期五")<br />if (today.getDay() == 6) document.write("星期六")<BR>document.writeln(" &lt;/div&gt;&lt;/td&gt;"); ]]></description>
		<eb:creationDate>2006-11-15 11:45:15</eb:creationDate>
		<eb:modificationDate></eb:modificationDate>
    </item>
    <item>
		<title><![CDATA[ delphi线程类 ]]></title>
		<link>http://blog.tom.com/teresa_cj/article/310.html</link>
		<description><![CDATA[ <BR>Delphi中有一个线程类TThread是用来实现多线程编程的，这个绝大多数Delphi书藉都有说到，但基本上都是对<BR>TThread类的几个成员作一简单介绍，再说明一下Execute的实现和Synchronize的用法就完了。然而这并不是多线程编<br />程的全部，我写此文的目的在于对此作一个补充。<BR>线程本质上是进程中一段并发运行的代码。一个进程至少有一个线程，即所谓的主线程。同时还可以有多个子线程。<br />当一个进程中用到超过一个线程时，就是所谓的“多线程”。<br />那么这个所谓的“一段代码”是如何定义的呢？其实就是一个函数或过程（对Delphi而言）。<br />如果用Windows API来创建线程的话，是通过一个叫做CreateThread的API函数来实现的，它的定义为：<br />HANDLE CreateThread(<br />&nbsp;&nbsp;&nbsp; LPSECURITY_ATTRIBUTES lpThreadAttributes, <br />&nbsp;&nbsp;&nbsp; DWORD dwStackSize, <br />&nbsp;&nbsp;&nbsp; LPTHREAD_START_ROUTINE lpStartAddress, <br />&nbsp;&nbsp;&nbsp; LPVOID lpParameter, <br />&nbsp;&nbsp;&nbsp; DWORD dwCreationFlags, <br />&nbsp;&nbsp;&nbsp; LPDWORD lpThreadId <br />);<BR>其各参数如它们的名称所说，分别是：线程属性（用于在NT下进行线程的安全属性设置，在9X下无效），堆栈大小，<br />起始地址，参数，创建标志（用于设置线程创建时的状态），线程ID，最后返回线程Handle。其中的起始地址就是线<br />程函数的入口，直至线程函数结束，线程也就结束了。<BR>因为CreateThread参数很多，而且是Windows的API，所以在C Runtime Library里提供了一个通用的线程函数（理论上<br />可以在任何支持线程的OS中使用）：<br />unsigned long _beginthread(void (_USERENTRY *__start)(void *), unsigned __stksize, void *__arg);<BR>Delphi也提供了一个相同功能的类似函数：<br />function BeginThread(<br />&nbsp;&nbsp;&nbsp; SecurityAttributes: Pointer; <br />&nbsp;&nbsp;&nbsp; StackSize: LongWord; <br />&nbsp;&nbsp;&nbsp; ThreadFunc: TThreadFunc; <br />&nbsp;&nbsp;&nbsp; Parameter: Pointer; <br />&nbsp;&nbsp;&nbsp; CreationFlags: LongWord; <br />&nbsp;&nbsp;&nbsp; var ThreadId: LongWord<br />): Integer;<BR>&nbsp;<BR>这三个函数的功能是基本相同的，它们都是将线程函数中的代码放到一个独立的线程中执行。线程函数与一般函数的<br />最大不同在于，线程函数一启动，这三个线程启动函数就返回了，主线程继续向下执行，而线程函数在一个独立的线<br />程中执行，它要执行多久，什么时候返回，主线程是不管也不知道的。<br />正常情况下，线程函数返回后，线程就终止了。但也有其它方式：<BR>Windows API：<br />VOID ExitThread( DWORD dwExitCode );<BR>C Runtime Library：<br />void _endthread(void);<BR>Delphi Runtime Library：<br />procedure EndThread(ExitCode: Integer);<BR>为了记录一些必要的线程数据（状态/属性等），OS会为线程创建一个内部Object，如在Windows中那个Handle便是这<br />个内部Object的Handle，所以在线程结束的时候还应该释放这个Object。<BR>虽然说用API或RTL(Runtime Library)已经可以很方便地进行多线程编程了，但是还是需要进行较多的细节处理，为此<br />Delphi在Classes单元中对线程作了一个较好的封装，这就是VCL的线程类：TThread<br />使用这个类也很简单，大多数的Delphi书籍都有说，基本用法是：先从TThread派生一个自己的线程类（因为TThread<br />是一个抽象类，不能生成实例），然后是Override抽象方法：Execute（这就是线程函数，也就是在线程中执行的代码<br />部分），如果需要用到可视VCL对象，还需要通过Synchronize过程进行。关于之方面的具体细节，这里不再赘述，请<br />参考相关书籍。<BR>本文接下来要讨论的是TThread类是如何对线程进行封装的，也就是深入研究一下TThread类的实现。因为只是真正地<br />了解了它，才更好地使用它。<br />下面是DELPHI7中TThread类的声明（本文只讨论在Windows平台下的实现，所以去掉了所有有关Linux平台部分的代码<br />）：<BR>TThread = class<br />private<br />&nbsp;&nbsp;&nbsp; FHandle: THandle;<br />&nbsp;&nbsp;&nbsp; FThreadID: THandle;<br />&nbsp;&nbsp;&nbsp; FCreateSuspended: Boolean;<br />&nbsp;&nbsp;&nbsp; FTerminated: Boolean;<br />&nbsp;&nbsp;&nbsp; FSuspended: Boolean;<br />&nbsp;&nbsp;&nbsp; FFreeOnTerminate: Boolean;<br />&nbsp;&nbsp;&nbsp; FFinished: Boolean;<br />&nbsp;&nbsp;&nbsp; FReturnValue: Integer;<br />&nbsp;&nbsp;&nbsp; FOnTerminate: TNotifyEvent;<br />&nbsp;&nbsp;&nbsp; FSynchronize: TSynchronizeRecord;<br />&nbsp;&nbsp;&nbsp; FFatalException: TObject;<br />&nbsp;&nbsp;&nbsp; procedure CallOnTerminate;<br />&nbsp;&nbsp;&nbsp; class procedure Synchronize(ASyncRec: PSynchronizeRecord); overload;<br />&nbsp;&nbsp;&nbsp; function GetPriority: TThreadPriority;<br />&nbsp;&nbsp;&nbsp; procedure SetPriority(Value: TThreadPriority);<br />&nbsp;&nbsp;&nbsp; procedure SetSuspended(Value: Boolean);<br />protected<br />&nbsp;&nbsp;&nbsp; procedure CheckThreadError(ErrCode: Integer); overload;<br />&nbsp;&nbsp;&nbsp; procedure CheckThreadError(Success: Boolean); overload;<br />&nbsp;&nbsp;&nbsp; procedure DoTerminate; virtual;<br />&nbsp;&nbsp;&nbsp; procedure Execute; virtual; abstract;<br />&nbsp;&nbsp;&nbsp; procedure Synchronize(Method: TThreadMethod); overload;<br />&nbsp;&nbsp;&nbsp; property ReturnValue: Integer read FReturnValue write FReturnValue;<br />&nbsp;&nbsp;&nbsp; property Terminated: Boolean read FTerminated;<br />public<br />&nbsp;&nbsp;&nbsp; constructor Create(CreateSuspended: Boolean);<br />&nbsp;&nbsp;&nbsp; destructor Destroy; override;<br />&nbsp;&nbsp;&nbsp; procedure AfterConstruction; override;<br />&nbsp;&nbsp;&nbsp; procedure Resume;<br />&nbsp;&nbsp;&nbsp; procedure Suspend;<br />&nbsp;&nbsp;&nbsp; procedure Terminate;<br />&nbsp;&nbsp;&nbsp; function WaitFor: LongWord;<br />&nbsp;&nbsp;&nbsp; class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload;<br />&nbsp;&nbsp;&nbsp; class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod);<br />&nbsp;&nbsp;&nbsp; property FatalException: TObject read FFatalException;<br />&nbsp;&nbsp;&nbsp; property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;<br />&nbsp;&nbsp;&nbsp; property Handle: THandle read FHandle;<br />&nbsp;&nbsp;&nbsp; property Priority: TThreadPriority read GetPriority write SetPriority;<br />&nbsp;&nbsp;&nbsp; property Suspended: Boolean read FSuspended write SetSuspended;<br />&nbsp;&nbsp;&nbsp; property ThreadID: THandle read FThreadID;<br />&nbsp;&nbsp;&nbsp; property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;<br />end;<BR>TThread类在Delphi的RTL里算是比较简单的类，类成员也不多，类属性都很简单明白，本文将只对几个比较重要的类<br />成员方法和唯一的事件：OnTerminate作详细分析。<br />首先就是构造函数：<br />constructor TThread.Create(CreateSuspended: Boolean);<br />begin<br />&nbsp;&nbsp;&nbsp; inherited Create;<br />&nbsp;&nbsp;&nbsp; AddThread;<br />&nbsp;&nbsp;&nbsp; FSuspended := CreateSuspended;<br />&nbsp;&nbsp;&nbsp; FCreateSuspended := CreateSuspended;<br />&nbsp;&nbsp;&nbsp; FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);<br />&nbsp;&nbsp;&nbsp; if FHandle = 0 then<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);<br />end;<br />虽然这个构造函数没有多少代码，但却可以算是最重要的一个成员，因为线程就是在这里被创建的。<br />在通过Inherited调用TObject.Create后，第一句就是调用一个过程：AddThread，其源码如下：<br />procedure AddThread;<br />begin<br />&nbsp;&nbsp;&nbsp; InterlockedIncrement(ThreadCount);<br />end;<BR>同样有一个对应的RemoveThread：<br />procedure RemoveThread;<br />begin<br />&nbsp;&nbsp;&nbsp; InterlockedDecrement(ThreadCount);<br />end;<br />它们的功能很简单，就是通过增减一个全局变量来统计进程中的线程数。只是这里用于增减变量的并不是常用的<br />Inc/Dec过程，而是用了InterlockedIncrement/InterlockedDecrement这一对过程，它们实现的功能完全一样，都是<br />对变量加一或减一。但它们有一个最大的区别，那就是InterlockedIncrement/InterlockedDecrement是线程安全的。<br />即它们在多线程下能保证执行结果正确，而Inc/Dec不能。或者按操作系统理论中的术语来说，这是一对“原语”操作。<BR>以加一为例来说明二者实现细节上的不同：<br />一般来说，对内存数据加一的操作分解以后有三个步骤：<br />1、 从内存中读出数据<br />2、 数据加一<br />3、 存入内存<br />现在假设在一个两个线程的应用中用Inc进行加一操作可能出现的一种情况：<br />1、 线程A从内存中读出数据（假设为3）<br />2、 线程B从内存中读出数据（也是3）<br />3、 线程A对数据加一（现在是4）<br />4、 线程B对数据加一（现在也是4）<br />5、 线程A将数据存入内存（现在内存中的数据是4）<br />6、 线程B也将数据存入内存（现在内存中的数据还是4，但两个线程都对它加了一，应该是5才对，所以这里出现了<br />错误的结果）<BR>&nbsp;<BR>而用InterlockIncrement过程则没有这个问题，因为所谓“原语”是一种不可中断的操作，即操作系统能保证在一个<br />“原语”执行完毕前不会进行线程切换。所以在上面那个例子中，只有当线程A执行完将数据存入内存后，线程B才可<br />以开始从中取数并进行加一操作，这样就保证了即使是在多线程情况下，结果也一定会是正确的。<BR>前面那个例子也说明一种“线程访问冲突”的情况，这也就是为什么线程之间需要“同步”（Synchronize），关于这<br />个，在后面说到同步时还会再详细讨论。<BR>说到同步，有一个题外话：加拿大滑铁卢大学的教授李明曾就Synchronize一词在“线程同步”中被译作“同步”提出<br />过异议，个人认为他说的其实很有道理。在中文中“同步”的意思是“同时发生”，而“线程同步”目的就是避免这<br />种“同时发生”的事情。而在英文中，Synchronize的意思有两个：一个是传统意义上的同步（To occur at the same <br />time），另一个是“协调一致”（To operate in unison）。在“线程同步”中的Synchronize一词应该是指后面一种<br />意思，即“保证多个线程在访问同一数据时，保持协调一致，避免出错”。不过像这样译得不准的词在IT业还有很多<br />，既然已经是约定俗成了，本文也将继续沿用，只是在这里说明一下，因为软件开发是一项细致的工作，该弄清楚的<br />，绝不能含糊。<BR>扯远了，回到TThread的构造函数上，接下来最重要就是这句了：<br />FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);<br />这里就用到了前面说到的Delphi RTL函数BeginThread，它有很多参数，关键的是第三、四两个参数。第三个参数就是<br />前面说到的线程函数，即在线程中执行的代码部分。第四个参数则是传递给线程函数的参数，在这里就是创建的线程<br />对象（即Self）。其它的参数中，第五个是用于设置线程在创建后即挂起，不立即执行（启动线程的工作是在<br />AfterConstruction中根据CreateSuspended标志来决定的），第六个是返回线程ID。<BR>现在来看TThread的核心：线程函数ThreadProc。有意思的是这个线程类的核心却不是线程的成员，而是一个全局函数<br />（因为BeginThread过程的参数约定只能用全局函数）。下面是它的代码：<BR>function ThreadProc(Thread: TThread): Integer;<br />var<br />&nbsp;&nbsp;&nbsp; FreeThread: Boolean;<br />begin<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if not Thread.Terminated then<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thread.Execute;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; except<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thread.FFatalException := AcquireExceptionObject;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; finally<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FreeThread := Thread.FFreeOnTerminate;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Result := Thread.FReturnValue;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thread.DoTerminate;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thread.FFinished := True;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SignalSyncEvent;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if FreeThread then Thread.Free;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EndThread(Result);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;<br />end;<br />虽然也没有多少代码，但却是整个TThread中最重要的部分，因为这段代码是真正在线程中执行的代码。下面对代码作<br />逐行说明：<br />首先判断线程类的Terminated标志，如果未被标志为终止，则调用线程类的Execute方法执行线程代码，因为TThread<br />是抽象类，Execute方法是抽象方法，所以本质上是执行派生类中的Execute代码。<BR>所以说，Execute就是线程类中的线程函数，所有在Execute中的代码都需要当作线程代码来考虑，如防止访问冲突等。<br />如果Execute发生异常，则通过AcquireExceptionObject取得异常对象，并存入线程类的FFatalException成员中。<br />最后是线程结束前做的一些收尾工作。局部变量FreeThread记录了线程类的FreeOnTerminated属性的设置，然后将线<br />程返回值设置为线程类的返回值属性的值。然后执行线程类的DoTerminate方法。<BR>DoTerminate方法的代码如下：<br />procedure TThread.DoTerminate;<br />begin<br />&nbsp;&nbsp;&nbsp; if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);<br />end;<BR>很简单，就是通过Synchronize来调用CallOnTerminate方法，而CallOnTerminate方法的代码如下，就是简单地调用<br />OnTerminate事件：<br />procedure TThread.CallOnTerminate;<br />begin<br />&nbsp;&nbsp;&nbsp; if Assigned(FOnTerminate) then FOnTerminate(Self);<br />end;<BR>因为OnTerminate事件是在Synchronize中执行的，所以本质上它并不是线程代码，而是主线程代码（具体见后面对<br />Synchronize的分析）。<BR>执行完OnTerminate后，将线程类的FFinished标志设置为True。接下来执行SignalSyncEvent过程，其代码如下：<br />procedure SignalSyncEvent;<br />begin<br />&nbsp;&nbsp;&nbsp; SetEvent(SyncEvent);<br />end;<BR>也很简单，就是设置一下一个全局Event：SyncEvent，关于Event的使用，本文将在后文详述，而SyncEvent的用途将<br />在WaitFor过程中说明。<BR>然后根据FreeThread中保存的FreeOnTerminate设置决定是否释放线程类，在线程类释放时，还有一些些操作，详见接<br />下来的析构函数实现。<br />最后调用EndThread结束线程，返回线程返回值。至此，线程完全结束。<br />说完构造函数，再来看析构函数：<br />destructor TThread.Destroy;<br />begin<br />&nbsp; if (FThreadID &lt;&gt; 0) and not FFinished then&nbsp; begin<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Terminate;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if FCreateSuspended then<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Resume;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WaitFor;<br />&nbsp; end;<br />&nbsp; if FHandle &lt;&gt; 0 then CloseHandle(FHandle);<br />&nbsp; inherited Destroy;<br />&nbsp; FFatalException.Free;<br />&nbsp; RemoveThread;<br />end;<BR>在线程对象被释放前，首先要检查线程是否还在执行中，如果线程还在执行中（线程ID不为0，并且线程结束标志未设<br />置），则调用Terminate过程结束线程。Terminate过程只是简单地设置线程类的Terminated标志，如下面的代码：<BR>procedure TThread.Terminate;<br />begin<br />&nbsp;&nbsp;&nbsp; FTerminated := True;<br />end;<BR>所以线程仍然必须继续执行到正常结束后才行，而不是立即终止线程，这一点要注意。<BR>在这里说一点题外话：很多人都问过我，如何才能“立即”终止线程（当然是指用TThread创建的线程）。结果当然是<br />不行！终止线程的唯一办法就是让Execute方法执行完毕，所以一般来说，要让你的线程能够尽快终止，必须在<br />Execute方法中在较短的时间内不断地检查Terminated标志，以便能及时地退出。这是设计线程代码的一个很重要的原<br />则！<BR>当然如果你一定要能“立即”退出线程，那么TThread类不是一个好的选择，因为如果用API强制终止线程的话，最终<br />会导致TThread线程对象不能被正确释放，在对象析构时出现Access Violation。这种情况你只能用API或RTL函数来创<br />建线程。<BR>如果线程处于启动挂起状态，则将线程转入运行状态，然后调用WaitFor进行等待，其功能就是等待到线程结束后才继<br />续向下执行。关于WaitFor的实现，将放到后面说明。<BR>线程结束后，关闭线程Handle（正常线程创建的情况下Handle都是存在的），释放操作系统创建的线程对象。<br />然后调用TObject.Destroy释放本对象，并释放已经捕获的异常对象，最后调用RemoveThread减小进程的线程数。<BR>其它关于Suspend/Resume及线程优先级设置等方面，不是本文的重点，不再赘述。下面要讨论的是本文的另两个重点<br />：Synchronize和WaitFor。<BR>但是在介绍这两个函数之前，需要先介绍另外两个线程同步技术：事件和临界区。<BR>事件（Event）与Delphi中的事件有所不同。从本质上说，Event其实相当于一个全局的布尔变量。它有两个赋值操作<br />：Set和Reset，相当于把它设置为True或False。而检查它的值是通过WaitFor操作进行。对应在Windows平台上，是三<br />个API函数：SetEvent、ResetEvent、WaitForSingleObject（实现WaitFor功能的API还有几个，这是最简单的一个）。<BR>这三个都是原语，所以Event可以实现一般布尔变量不能实现的在多线程中的应用。Set和Reset的功能前面已经说过了<br />，现在来说一下WaitFor的功能：<BR>WaitFor的功能是检查Event的状态是否是Set状态（相当于True），如果是则立即返回，如果不是，则等待它变为Set<br />状态，在等待期间，调用WaitFor的线程处于挂起状态。另外WaitFor有一个参数用于超时设置，如果此参数为0，则不<br />等待，立即返回Event的状态，如果是INFINITE则无限等待，直到Set状态发生，若是一个有限的数值，则等待相应的<br />毫秒数后返回Event的状态。<BR>当Event从Reset状态向Set状态转换时，唤醒其它由于WaitFor这个Event而挂起的线程，这就是它为什么叫Event的原<br />因。所谓“事件”就是指“状态的转换”。通过Event可以在线程间传递这种“状态转换”信息。<BR>当然用一个受保护（见下面的临界区介绍）的布尔变量也能实现类似的功能，只要用一个循环检查此布尔值的代码来<br />代替WaitFor即可。从功能上说完全没有问题，但实际使用中就会发现，这样的等待会占用大量的CPU资源，降低系统<br />性能，影响到别的线程的执行速度，所以是不经济的，有的时候甚至可能会有问题。所以不建议这样用。<BR>临界区（CriticalSection）则是一项共享数据访问保护的技术。它其实也是相当于一个全局的布尔变量。但对它的操<br />作有所不同，它只有两个操作：Enter和Leave，同样可以把它的两个状态当作True和False，分别表示现在是否处于临<br />界区中。这两个操作也是原语，所以它可以用于在多线程应用中保护共享数据，防止访问冲突。<BR>用临界区保护共享数据的方法很简单：在每次要访问共享数据之前调用Enter设置进入临界区标志，然后再操作数据，<br />最后调用Leave离开临界区。它的保护原理是这样的：当一个线程进入临界区后，如果此时另一个线程也要访问这个数<br />据，则它会在调用Enter时，发现已经有线程进入临界区，然后此线程就会被挂起，等待当前在临界区的线程调用<br />Leave离开临界区，当另一个线程完成操作，调用Leave离开后，此线程就会被唤醒，并设置临界区标志，开始操作数<br />据，这样就防止了访问冲突。<BR>以前面那个InterlockedIncrement为例，我们用CriticalSection（Windows API）来实现它：<br />Var<br />InterlockedCrit : TRTLCriticalSection;<br />Procedure InterlockedIncrement( var aValue : Integer );<br />Begin<br />&nbsp;&nbsp;&nbsp; EnterCriticalSection( InterlockedCrit );<br />&nbsp;&nbsp;&nbsp; Inc( aValue );<br />&nbsp;&nbsp;&nbsp; LeaveCriticalSection( InterlockedCrit );<br />End;<BR>现在再来看前面那个例子：<br />1. 线程A进入临界区（假设数据为3）<br />2. 线程B进入临界区，因为A已经在临界区中，所以B被挂起<br />3. 线程A对数据加一（现在是4）<br />4. 线程A离开临界区，唤醒线程B（现在内存中的数据是4）<br />5. 线程B被唤醒，对数据加一（现在就是5了）<br />6. 线程B离开临界区，现在的数据就是正确的了。<BR>临界区就是这样保护共享数据的访问。<BR>关于临界区的使用，有一点要注意：即数据访问时的异常情况处理。因为如果在数据操作时发生异常，将导致Leave操<br />作没有被执行，结果将使本应被唤醒的线程未被唤醒，可能造成程序的没有响应。所以一般来说，如下面这样使用临<br />界区才是正确的做法：<BR>EnterCriticalSection<br />Try<br />// 操作临界区数据<br />Finally<br />&nbsp;&nbsp;&nbsp; LeaveCriticalSection<br />End;<BR>最后要说明的是，Event和CriticalSection都是操作系统资源，使用前都需要创建，使用完后也同样需要释放。如<br />TThread类用到的一个全局Event：SyncEvent和全局CriticalSection：TheadLock，都是在<br />InitThreadSynchronization和DoneThreadSynchronization中进行创建和释放的，而它们则是在Classes单元的<br />Initialization和Finalization中被调用的。<BR>由于在TThread中都是用API来操作Event和CriticalSection的，所以前面都是以API为例，其实Delphi已经提供了对它<br />们的封装，在SyncObjs单元中，分别是TEvent类和TCriticalSection类。用法也与前面用API的方法相差无几。因为<br />TEvent的构造函数参数过多，为了简单起见，Delphi还提供了一个用默认参数初始化的Event类：TSimpleEvent。<BR>顺便再介绍一下另一个用于线程同步的类：TMultiReadExclusiveWriteSynchronizer，它是在SysUtils单元中定义的<br />。据我所知，这是Delphi RTL中定义的最长的一个类名，还好它有一个短的别名：TMREWSync。至于它的用处，我想光<br />看名字就可以知道了，我也就不多说了。<BR>有了前面对Event和CriticalSection的准备知识，可以正式开始讨论Synchronize和WaitFor了。<br />我们知道，Synchronize是通过将部分代码放到主线程中执行来实现线程同步的，因为在一个进程中，只有一个主线程<br />。先来看看Synchronize的实现：<BR>procedure TThread.Synchronize(Method: TThreadMethod);<br />begin<br />&nbsp;&nbsp;&nbsp; FSynchronize.FThread := Self;<br />&nbsp;&nbsp;&nbsp; FSynchronize.FSynchronizeException := nil;<br />&nbsp;&nbsp;&nbsp; FSynchronize.FMethod := Method;<br />&nbsp;&nbsp;&nbsp; Synchronize(@FSynchronize);<br />end;<BR>其中FSynchronize是一个记录类型：<br />PSynchronizeRecord = ^TSynchronizeRecord;<br />TSynchronizeRecord = record<br />&nbsp;&nbsp;&nbsp; FThread: TObject;<br />&nbsp;&nbsp;&nbsp; FMethod: TThreadMethod;<br />&nbsp;&nbsp;&nbsp; FSynchronizeException: TObject;<br />end;<BR>用于进行线程和主线程之间进行数据交换，包括传入线程类对象，同步方法及发生的异常。<br />在Synchronize中调用了它的一个重载版本，而且这个重载版本比较特别，它是一个“类方法”。所谓类方法，是一种<br />特殊的类成员方法，它的调用并不需要创建类实例，而是像构造函数那样，通过类名调用。之所以会用类方法来实现<br />它，是因为为了可以在线程对象没有创建时也能调用它。不过实际中是用它的另一个重载版本（也是类方法）和另一<br />个类方法StaticSynchronize。下面是这个Synchronize的代码：<BR>class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord);<br />var<br />&nbsp;&nbsp;&nbsp; SyncProc: TSyncProc;<br />begin<br />&nbsp;&nbsp;&nbsp; if GetCurrentThreadID = MainThreadID then<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ASyncRec.FMethod<br />&nbsp;&nbsp;&nbsp; else begin<br />&nbsp;&nbsp;&nbsp; SyncProc.Signal := CreateEvent(nil, True, False, nil);<br />&nbsp;&nbsp;&nbsp; try<br />&nbsp;&nbsp;&nbsp; EnterCriticalSection(ThreadLock);<br />&nbsp;&nbsp;&nbsp; try<br />&nbsp;&nbsp;&nbsp; if SyncList = nil then<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SyncList := TList.Create;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SyncProc.SyncRec := ASyncRec;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SyncList.Add(@SyncProc);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SignalSyncEvent;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if Assigned(WakeMainThread) then<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WakeMainThread(SyncProc.SyncRec.FThread);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LeaveCriticalSection(ThreadLock);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WaitForSingleObject(SyncProc.Signal, INFINITE);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; finally<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EnterCriticalSection(ThreadLock);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; finally<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LeaveCriticalSection(ThreadLock);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; finally<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CloseHandle(SyncProc.Signal);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if Assigned(ASyncRec.FSynchronizeException) then <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; raise ASyncRec.FSynchronizeException;<br />&nbsp;&nbsp;&nbsp; end;<br />end;<BR>这段代码略多一些，不过也不算太复杂。<br />首先是判断当前线程是否是主线程，如果是，则简单地执行同步方法后返回。<br />如果不是主线程，则准备开始同步过程。<br />通过局部变量SyncProc记录线程交换数据（参数）和一个Event Handle，其记录结构如下：<br />TSyncProc = record<br />SyncRec: PSynchronizeRecord;<br />Signal: THandle;<br />end;<BR>然后创建一个Event，接着进入临界区（通过全局变量ThreadLock进行，因为同时只能有一个线程进入Synchronize状<br />态，所以可以用全局变量记录），然后就是把这个记录数据存入SyncList这个列表中（如果这个列表不存在的话，则<br />创建它）。可见ThreadLock这个临界区就是为了保护对SyncList的访问，这一点在后面介绍CheckSynchronize时会再<br />次看到。<BR>再接下就是调用SignalSyncEvent，其代码在前面介绍TThread的构造函数时已经介绍过了，它的功能就是简单地将<br />SyncEvent作一个Set的操作。关于这个SyncEvent的用途，将在后面介绍WaitFor时再详述。<BR>接下来就是最主要的部分了：调用WakeMainThread事件进行同步操作。WakeMainThread是一个TNotifyEvent类型的全<br />局事件。这里之所以要用事件进行处理，是因为Synchronize方法本质上是通过消息，将需要同步的过程放到主线程中<br />执行，如果在一些没有消息循环的应用中（如Console或DLL）是无法使用的，所以要使用这个事件进行处理。<br />而响应这个事件的是Application对象，下面两个方法分别用于设置和清空WakeMainThread事件的响应（来自Forms单元）：<BR>procedure TApplication.HookSynchronizeWakeup;<br />begin<br />&nbsp;&nbsp;&nbsp; Classes.WakeMainThread := WakeMainThread;<br />end;<BR>procedure TApplication.UnhookSynchronizeWakeup;<br />begin<br />&nbsp;&nbsp;&nbsp; Classes.WakeMainThread := nil;<br />end;<BR>上面两个方法分别是在TApplication类的构造函数和析构函数中被调用。<br />这就是在Application对象中WakeMainThread事件响应的代码，消息就是在这里被发出的，它利用了一个空消息来实现：<BR>procedure TApplication.WakeMainThread(Sender: TObject);<br />begin<br />&nbsp;&nbsp;&nbsp; PostMessage(Handle, WM_NULL, 0, 0);<br />end;<BR>而这个消息的响应也是在Application对象中，见下面的代码（删除无关的部分）：<br />procedure TApplication.WndProc(var Message: TMessage);<br />…<br />begin<br />&nbsp;&nbsp;&nbsp; try<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; …<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; with Message do<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case Msg of<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; …<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WM_NULL:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CheckSynchronize;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; …<br />&nbsp;&nbsp;&nbsp; except<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HandleException(Self);<br />&nbsp;&nbsp;&nbsp; end;<br />end;<BR>其中的CheckSynchronize也是定义在Classes单元中的，由于它比较复杂，暂时不详细说明，只要知道它是具体处理<br />Synchronize功能的部分就好，现在继续分析Synchronize的代码。<br />在执行完WakeMainThread事件后，就退出临界区，然后调用WaitForSingleObject开始等待在进入临界区前创建的那个<br />Event。这个Event的功能是等待这个同步方法的执行结束，关于这点，在后面分析CheckSynchronize时会再说明。<br />注意在WaitForSingleObject之后又重新进入临界区，但没有做任何事就退出了，似乎没有意义，但这是必须的！<br />因为临界区的Enter和Leave必须严格的一一对应。那么是否可以改成这样呢：<BR>if Assigned(WakeMainThread) then<br />&nbsp;&nbsp;&nbsp; WakeMainThread(SyncProc.SyncRec.FThread);<br />&nbsp;&nbsp;&nbsp; WaitForSingleObject(SyncProc.Signal, INFINITE);<br />&nbsp;&nbsp;&nbsp; finally<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LeaveCriticalSection(ThreadLock);<br />end;<BR>上面的代码和原来的代码最大的区别在于把WaitForSingleObject也纳入临界区的限制中了。看上去没什么影响，还使<br />代码大大简化了，但真的可以吗？<br />事实上是不行！<BR>因为我们知道，在Enter临界区后，如果别的线程要再进入，则会被挂起。而WaitFor方法则会挂起当前线程，直到等<br />待别的线程SetEvent后才会被唤醒。如果改成上面那样的代码的话，如果那个SetEvent的线程也需要进入临界区的话<br />，死锁（Deadlock）就发生了（关于死锁的理论，请自行参考操作系统原理方面的资料）。<br />死锁是线程同步中最需要注意的方面之一！<br />最后释放开始时创建的Event，如果被同步的方法返回异常的话，还会在这里再次抛出异常。<BR>回到前面CheckSynchronize，见下面的代码：<BR>function CheckSynchronize(Timeout: Integer = 0): Boolean;<br />var<br />&nbsp;&nbsp;&nbsp;&nbsp; SyncProc: PSyncProc;<br />&nbsp;&nbsp;&nbsp;&nbsp; LocalSyncList: TList;<br />begin<br />&nbsp;&nbsp;&nbsp;&nbsp; if GetCurrentThreadID &lt;&gt; MainThreadID then<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; raise EThread.CreateResFmt(@SCheckSynchronizeError, [GetCurrentThreadID]);<br />&nbsp;&nbsp;&nbsp;&nbsp; if Timeout &gt; 0 then<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WaitForSyncEvent(Timeout)<br />&nbsp;&nbsp;&nbsp;&nbsp; else<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ResetSyncEvent;<br />&nbsp;&nbsp;&nbsp;&nbsp; LocalSyncList := nil;<br />&nbsp;&nbsp;&nbsp;&nbsp; EnterCriticalSection(ThreadLock);<br />&nbsp;&nbsp;&nbsp;&nbsp; try<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer(LocalSyncList) := InterlockedExchange(Integer(SyncList), Integer(LocalSyncList));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Result := (LocalSyncList &lt;&gt; nil) and (LocalSyncList.Count &gt; 0);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if Result then begin<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while LocalSyncList.Count &gt; 0 do begin<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SyncProc := LocalSyncList[0];<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LocalSyncList.Delete(0);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LeaveCriticalSection(ThreadLock);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SyncProc.SyncRec.FMethod;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; except<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; finally<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EnterCriticalSection(ThreadLock);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SetEvent(SyncProc.signal);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; finally<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LocalSyncList.Free;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end;<br />&nbsp;&nbsp;&nbsp;&nbsp; finally<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LeaveCriticalSection(ThreadLock);<br />&nbsp;&nbsp;&nbsp;&nbsp; end;<br />end;<BR>首先，这个方法必须在主线程中被调用（如前面通过消息传递到主线程），否则就抛出异常。<br />接下来调用ResetSyncEvent（它与前面SetSyncEvent对应的，之所以不考虑WaitForSyncEvent的情况，是因为只有在<br />Linux版下才会调用带参数的CheckSynchronize，Windows版下都是调用默认参数0的CheckSynchronize）。<br />现在可以看出SyncList的用途了：它是用于记录所有未被执行的同步方法的。因为主线程只有一个，而子线程可能有<br />很多个，当多个子线程同时调用同步方法时，主线程可能一时无法处理，所以需要一个列表来记录它们。<br />在这里用一个局部变量LocalSyncList来交换SyncList，这里用的也是一个原语：InterlockedExchange。同样，这里<br />也是用临界区将对SyncList的访问保护起来。<br />只要LocalSyncList不为空，则通过一个循环来依次处理累积的所有同步方法调用。最后把处理完的LocalSyncList释<br />放掉，退出临界区。<BR>再来看对同步方法的处理：首先是从列表中移出（取出并从列表中删除）第一个同步方法调用数据。然后退出临界区<br />（原因当然也是为了防止死锁）。<br />接着就是真正的调用同步方法了。<br />如果同步方法中出现异常，将被捕获后存入同步方法数据记录中。<br />重新进入临界区后，调用SetEvent通知调用线程，同步方法执行完成了（详见前面Synchronize中的<br />WaitForSingleObject调用）。<br />至此，整个Synchronize的实现介绍完成。<BR>最后来说一下WaitFor，它的功能就是等待线程执行结束。其代码如下：<br />function TThread.WaitFor: LongWord;<br />var<br />&nbsp;&nbsp;&nbsp; H: array[0..1] of THandle;<br />&nbsp;&nbsp;&nbsp; WaitResult: Cardinal;<br />&nbsp;&nbsp;&nbsp; Msg: TMsg;<br />begin<br />&nbsp;&nbsp;&nbsp; H[0] := FHandle;<br />&nbsp;&nbsp;&nbsp; if GetCurrentThreadID = MainThreadID then&nbsp; begin<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WaitResult := 0;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; H[1] := SyncEvent;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; repeat<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { This prevents a potential deadlock if the background thread does a SendMessage to the foreground thread }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if WaitResult = WAIT_OBJECT_0 + 2 then<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WaitResult := MsgWaitForMultipleObjects(2, H, False, 1000, QS_SENDMESSAGE);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CheckThreadError(WaitResult &lt;&gt; WAIT_FAILED);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if WaitResult = WAIT_OBJECT_0 + 1 then<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CheckSynchronize;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; until WaitResult = WAIT_OBJECT_0;<br />&nbsp;&nbsp;&nbsp; end else <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WaitForSingleObject(H[0], INFINITE);<br />&nbsp;&nbsp;&nbsp; CheckThreadError(GetExitCodeThread(H[0], Result));<br />end;<BR>如果不是在主线程中执行WaitFor的话，很简单，只要调用WaitForSingleObject等待此线程的Handle为Signaled状态<br />即可。<BR>如果是在主线程中执行WaitFor则比较麻烦。首先要在Handle数组中增加一个SyncEvent，然后循环等待，直到线程结<br />束（即MsgWaitForMultipleObjects返回WAIT_OBJECT_0，详见MSDN中关于此API的说明）。<br />在循环等待中作如下处理：如果有消息发生，则通过PeekMessage取出此消息（但并不把它从消息循环中移除），然后<br />调用MsgWaitForMultipleObjects来等待线程Handle或SyncEvent出现Signaled状态，同时监听消息（QS_SENDMESSAGE<br />参数，详见MSDN中关于此API的说明）。可以把此API当作一个可以同时等待多个Handle的WaitForSingleObject。如果<br />是SyncEvent被SetEvent（返回WAIT_OBJECT_0 + 1），则调用CheckSynchronize处理同步方法。<br />为什么在主线程中调用WaitFor必须用MsgWaitForMultipleObjects，而不能用WaitForSingleObject等待线程结束呢？<br />因为防止死锁。由于在线程函数Execute中可能调用Synchronize处理同步方法，而同步方法是在主线程中执行的，如<br />果用WaitForSingleObject等待的话，则主线程在这里被挂起，同步方法无法执行，导致线程也被挂起，于是发生死锁。<br />而改用WaitForMultipleObjects则没有这个问题。首先，它的第三个参数为False，表示只要线程Handle或SyncEvent<br />中只要有一个Signaled即可使主线程被唤醒，至于加上QS_SENDMESSAGE是因为Synchronize是通过消息传到主线程来的<br />，所以还要防止消息被阻塞。这样，当线程中调用Synchronize时，主线程就会被唤醒并处理同步调用，在调用完成后<br />继续进入挂起等待状态，直到线程结束。<br />至此，对线程类TThread的分析可以告一个段落了，对前面的分析作一个总结：<br />1、 线程类的线程必须按正常的方式结束，即Execute执行结束，所以在其中的代码中必须在适当的地方加入足够多<br />&nbsp;&nbsp;&nbsp; 的对Terminated标志的判断，并及时退出。如果必须要“立即”退出，则不能使用线程类，而要改用API或RTL函数。<br />2、 对可视VCL的访问要放在Synchronize中，通过消息传递到主线程中，由主线程处理。<br />3、 线程共享数据的访问应该用临界区进行保护（当然用Synchronize也行）。<br />4、 线程通信可以采用Event进行（当然也可以用Suspend/Resume）。<br />5、 当在多线程应用中使用多种线程同步方式时，一定要小心防止出现死锁。<br />6、 等待线程结束要用WaitFor方法。 ]]></description>
		<eb:creationDate>2006-11-13 12:01:42</eb:creationDate>
		<eb:modificationDate></eb:modificationDate>
    </item>
    <item>
		<title><![CDATA[ 网页底部网址覆盖 ]]></title>
		<link>http://blog.tom.com/teresa_cj/article/309.html</link>
		<description><![CDATA[ <BR>逐个滑入效果<BR>&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"&gt;<br />&lt;HTML&gt;<br />&lt;HEAD&gt;<br />&lt;TITLE&gt; New Document &lt;/TITLE&gt;<br />&lt;META NAME="Generator" CONTENT="EditPlus"&gt;<br />&lt;META NAME="Author" CONTENT=""&gt;<br />&lt;META NAME="Keywords" CONTENT=""&gt;<br />&lt;META NAME="Description" CONTENT=""&gt;<BR>&lt;/HEAD&gt;<BR>&lt;BODY&gt;<br />&nbsp;&lt;script language="JavaScript"&gt;<br />&lt;!--<br />function statusMessageObject(p,d) {<br />this.msg = MESSAGE<br />this.out = " "<br />this.pos = POSITION<br />this.delay = DELAY<br />this.i = 0<br />this.reset = clearMessage<br />}<br />function clearMessage() {<br />this.pos = POSITION<br />}<br />var POSITION = 100<br />var DELAY = 5<br />var MESSAGE = "欢迎光临Teresad的小窝";<br />var scroll = new statusMessageObject()<br />function scroller() {<br />for (scroll.i = 0; scroll.i &lt; scroll.pos; scroll.i++) {<br />scroll.out += " "<br />}<br />if (scroll.pos &gt;= 0)<br />scroll.out += scroll.msg<br />else scroll.out = scroll.msg.substring(-scroll.pos,scroll.msg.length)<br />window.status = scroll.out<br />scroll.out = " "<br />scroll.pos--<br />if (scroll.pos &lt; -(scroll.msg.length)) {<br />scroll.reset()<br />}<br />setTimeout ('scroller()',scroll.delay)<br />}<br />function snapIn(jumpSpaces,position) {<br />var msg = scroll.msg<br />var out = ""<br />for (var i=0; i&lt;position; i++)<br />{out += msg.charAt(i)}<br />for (i=1;i&lt;jumpSpaces;i++)<br />{out += " "}<br />out += msg.charAt(position)<br />window.status = out<br />if (jumpSpaces &lt;= 1) {<br />position++<br />if (msg.charAt(position) == ' ')<br />{position++ }<br />jumpSpaces = 100-position<br />} else if (jumpSpaces &gt; 3)<br />{jumpSpaces *= .75}<br />else<br />{jumpSpaces--}<br />if (position != msg.length) {<br />var cmd = "snapIn(" + jumpSpaces + "," + position + ")";<br />scrollID = window.setTimeout(cmd,scroll.delay);<br />} else {<br />window.status=""<br />jumpSpaces=0<br />position=0<br />cmd = "snapIn(" + jumpSpaces + "," + position + ")";<br />scrollID = window.setTimeout(cmd,scroll.delay);<br />return false<br />}<br />return true<br />}<br />snapIn(100,0);<br />// --&gt;<br />&lt;/script&gt;&nbsp; <br />&nbsp;<br />&lt;/BODY&gt;<br />&lt;/HTML&gt; ]]></description>
		<eb:creationDate>2006-11-10 16:39:58</eb:creationDate>
		<eb:modificationDate></eb:modificationDate>
    </item>
    <item>
		<title><![CDATA[ 从一个程序窗口激活另一个窗口 ]]></title>
		<link>http://blog.tom.com/teresa_cj/article/308.html</link>
		<description><![CDATA[ <br />SetForegroundWindow (FindWindow('窗口类名',0)) ; ]]></description>
		<eb:creationDate>2006-11-10 16:23:20</eb:creationDate>
		<eb:modificationDate></eb:modificationDate>
    </item>
    <item>
		<title><![CDATA[ 改变IE收藏夹中收藏网站前面的IE图标和地址栏图标 ]]></title>
		<link>http://blog.tom.com/teresa_cj/article/307.html</link>
		<description><![CDATA[ <BR><font color="#ff0000" size="2">第一种方法</font><BR><font size="2">&nbsp; 第一步，准备一个图标制作软件。 &nbsp; &nbsp; <br />&nbsp; 首先您必须了解所谓的图标（Icon）是一种特殊的图形文件格式，它是以&nbsp;.ico &nbsp; 作为扩展名。普通的图像设计软件无法使用这种格式，所以您需要到一个软件下载站点去下载一个图标制作软件。您也可以访问 &nbsp; http://www.favicon.com/ &nbsp; ，这里提供一个免费的在线“收藏夹”图标生成器，如果您 &nbsp; E &nbsp; 文够好的话，在这里您还可以了解到更多的关于“收藏夹”图标的知识。 &nbsp; &nbsp; <br />&nbsp; 第二步，确定“收藏夹”图标的规格。 &nbsp; &nbsp; <br />&nbsp; “收藏夹”图标有着它特有的规格：图标的大小为 &nbsp; 16 &nbsp; * &nbsp; 16（以像素为单位）；所使用的颜色不得超过 &nbsp; 16 &nbsp; 色。 &nbsp; &nbsp; <br />&nbsp; 第三步，设计一个属于您自己的图标。 &nbsp; &nbsp; <br />&nbsp; 您可以将您的网站的 &nbsp; LOGO &nbsp; 做成一个缩图或者另外设计一个别具特色的图案来作为“收藏夹”图标。总之，它一定要是属于您自己的，并且能很好的代表您的网站的风格和个性。然后将这个图标文件命名为：favicon.ico。 &nbsp; &nbsp; <br />&nbsp; 最后，将这个图标文件（favicon.ico）上传到您的网站所在的服务器的根目录下。您不需要对您的网页文件作任何的修改，IE5 &nbsp; 会自动的不停的搜索您的网站的根目录，只要它一发现了 &nbsp; favicon.ico &nbsp; 这个文件，就会将该图标显示在访问者的地址栏和收藏夹列表中了。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /><font color="#ff0000"></font></font><BR><font size="2"><font color="#ff0000"></font></font>&nbsp;<BR><font size="2"><font color="#ff0000">第二种方法</font>&nbsp;&nbsp;&nbsp;<br />&nbsp; &lt;HEAD&gt; &nbsp; <br />&nbsp; &lt;link rel="shortcut icon" href="路径favicon.ico"&gt;&nbsp; <br />&nbsp; &lt;TITLE&gt;My &nbsp; Title&lt;/TITLE&gt; &nbsp; <br />&nbsp; &lt;/HEAD&gt; &nbsp; <br />&nbsp; 使用这个做法的话， &nbsp; <br />&nbsp; 就不一定要把图形文件放在网站的根目录下面了， &nbsp; <br />&nbsp; 你可以放在任何位置， &nbsp; <br />&nbsp; 甚至直接连结到其他网站上面的图形文件也可以， &nbsp; <br />&nbsp; 而且图形文件的文件名也不一定要叫做 &nbsp; favicon.ico， &nbsp; <br />&nbsp; 可以自己随便取。 &nbsp; <br />&nbsp; 不过要注意的是使用这个做法的话， &nbsp; <br />&nbsp; 只有在把有加入上面 &nbsp; HTML &nbsp; 语法的页面加入书签的时候才会有作用。&nbsp;&nbsp; <br /></font> ]]></description>
		<eb:creationDate>2006-11-04 16:23:35</eb:creationDate>
		<eb:modificationDate></eb:modificationDate>
    </item>
    <item>
		<title><![CDATA[ WINDOWS下PHP环境的搭建 ]]></title>
		<link>http://blog.tom.com/teresa_cj/article/306.html</link>
		<description><![CDATA[ <br /><font color="#008000" size="2">所需软件：<br /><br />php5.1.6 for windows&nbsp;&nbsp;</font><a target="_blank" href="http://cn.php.net/get/php-5.1.6-Win32.zip/from/this/mirror"><font color="#008000" size="2">http://cn.php.net/get/php-5.1.6-Win32.zip/from/this/mirror</font></a><font color="#008000" size="2">）<br /><br />mysql-5.0.24 for windows </font><a target="_blank" href="http://dev.mysql.com/downloads/mysql/5.1.html"><font color="#008000" size="2">http://dev.mysql.com/downloads/mysql/5.1.html</font></a><font color="#008000" size="2">）<br /><br />apache_2.2.3 For Windows </font><a target="_blank" href="http://phpip.com/down/download.php?downid=216&amp;id=0"><font color="#008000" size="2">http://phpip.com/down/download.php&nbsp;downid=216&amp;id=0</font></a><font color="#008000" size="2">）<br /><br />ZendOptimizer-3.0.1-Windows-i386　</font><a target="_blank" href="http://phpip.com/down/download.php?downid=22&amp;id=0"><font color="#008000" size="2">http://phpip.com/down/download.php&nbsp;downid=22&amp;id=0</font></a><font color="#008000" size="2">）<br /><br />Phpmyadmin-2.9&nbsp; &nbsp; </font><a target="_blank" href="http://phpip.com/down/download.php?downid=22&amp;id=0"><font color="#008000" size="2">http://phpip.com/down/download.php&nbsp;downid=22&amp;id=0</font></a><font color="#008000" size="2">)<br /><br /><br />配置环境:Windows 2003 /XP/2000<br /><br />注意事项:<br /><br />安装过程，任何目录和文件名都不要使用空格,如不要使用D:www root 而要使用D:wwwroot本次安装目录为D:Wwwroot<br /><br /><br />1. 安装 apache_2.2.3-win32-x86-no_ssl.msi <br /><br />双击安装apache_2.2.3,我把它安装到D:wwwrootApache2.2 注意，目录名不要包含空格，否则下面设置php会出错。安装结束后，apache自动运行，在浏览器里输</font><a target="_blank" href="http://127.0.0.1/"><font color="#008000" size="2">http://127.0.0.1</font></a><font size="2"><font color="#008000">是不是显示出了默认的网页，如果你不希望看到这个页面，可以到D:wwwrootApache2.2conf 目录下找到 httpd.conf 打开编辑，并查找 DocumentRoot " 在2.0系列的版本中，只会找到一个 DocumentRoot " ，把引号内的路径改为你自己的路径就可以了，比如 DocumentRoot "D:/phproot" 现在默认的根目录就是 D:/phproot 注意这里用的是"/"。<br /></font><br /></font><font color="#008000" size="2">2、安装php5.1.6<br />下载下来的php-5.1.6-Win32一般是个zip格式的压缩包，解压缩到 D:wwwroot 目录下，并使文件都在一个文件夹下，改文件夹名为php5 , 好了,我们现在开始配置apache 使它支持php5 .<br />首先,找到 D:wwwroot/php5 目录下的php.ini-dist 重命名为php.ini (在这个版本中php.ini不需要复制到”%windir% 目录下)<br /><br />接下去,我们开始配置 D:wwwroot/Apache2.2/conf 下的 httpd.conf文件,打开httpd.conf (可用记事本打开),注意Apache2.2和以前版本有差别,部分的配置文件放置到conf/extra目录中,如语言文件httpd-languages.conf,用户目录文件httpd-userdir.conf<br />1、原来的Apache2中这一步"找到 AddDefaultCharset ISO-8859-1 将其改为 AddDefaultCharset GB2312"可以不需要<br />2、找到DirectoryIndex index.html 在后面加入 index.htm index.php index.html.var<br />3、CGI方式安装配置<br />在httpd.conf文件的末尾加入<br /># php5<br />ScriptAlias /php/ "d:/wwwroot/php5/"<br />Action application/x-httpd-php "/php/php-cgi.exe"<br />AddType application/x-httpd-php .php<br />Action application/x-httpd-php "/php/php-cgi.exe"<br />注意,如果安装后出现You don't have permission to access / on this server.<br />或者出现无法运行php文件的情况下<br />查找httpd.conf中的<br />Deny from all改为Allow from all<br /><br />3、安装mysql-5.0.24-win<br />因为在win环境下配置apache所以,这里用的mysql也是win版本的。解压缩之后,安装mysql-5.0.24到D:/Wwwroot/ 目录下,并使mysql完整的安装到 mysql目录下,我这里安装为D:WwwrootMySQL5,装mysql没什么具体要求,点下一步下一步的就行了,最后设定root的密码就OK。打开Windows任务管理器,看里面是否有mysql-nt.exe,有的话,就正常运行了。<br /><br /><br />4、配置php.ini了<br />进入D:wwwrootphp5 打开 php.ini 找到extension_dir = "./" 改为extension_dir = "D:/wwwroot/php5/ext"<br />找到<br />;extension=php_mbstring.dll<br />;extension=php_mysql.dll<br />;extension=php_gd2.dll<br />将';'去掉改为<br />extension=php_mbstring.dll<br />extension=php_mysql.dll<br />extension=php_gd2.dll<br />找到<br />;session.save_path = "/tmp"<br />将';'去掉 设置你保存session的目录,如<br />session.save_path = " D:/wwwroot/php5/session";<br />注意：在这里需要手工添加 session这个文件夹<br /><br /><br />5、安装zend支持<br />双击 ZendOptimizer-3.0.1-Windows-i386.exe<br /><br />我的安装路径为 D:wwwrootzend3<br />然后会一步步选择安装，<br />选择安装的的环境为：apache2.x，<br />再选择apache所在目录，这里为D:wwwrootApache2.2<br />接下来是php目录 D:wwwrootphp5<br />安装成功后，重新启动apache2即可。<br />好了,到这里已经成功了！<br /><br /><br />6、phpMyAdmin-2.8.0的配置<br />将phpMyAdmin-2.8.0.zip解压到自己定义的WEB根目录中去,重命名文件夹为phpmyadmin或其它<br />打开libraries目录下的config.default.php文件<br />$cfg['Servers'][$i]['auth_type'] = 'cookie'; // Authentication method (config, http or cookie based)&nbsp;<br />这里最好设为cookie,这样,进行数据库管理的话必须需要用户名和密码才可行入,config的话就什么都不需要直接进入了数据库</font> <br /><font color="#008000" size="2">修改$cfg['blowfish_secret'] = '这里自己随便填，相当于一个key<br /><br />$cfg['Servers'][$i]['user'] = 'root'; // MySQL user<br />$cfg['Servers'][$i]['password'] = 'password'; // MySQL password<br /><br /><br />用记事本写上<br />&lt;&nbsp;php<br />phpinfo();<br />&nbsp;&gt;<br />保存到虚拟目录<br />即 D:wwwrootapache2.2htdocs 目录下<br />用浏览器直接访问这个文件即可：<br />如</font><a target="_blank" href="http://127.0.0.1/info.php"><font color="#008000" size="2">http://127.0.0.1/info.php</font></a><br /><br /> ]]></description>
		<eb:creationDate>2006-11-04 14:44:15</eb:creationDate>
		<eb:modificationDate></eb:modificationDate>
    </item>
</channel>
</rss>