| 萧萧's profile时光·漫步BlogLists | Help |
|
April 06 VSS版本管理——使用手册作者:佚名 转贴自:SCM China 论坛 点击数:102541 VSS概述 版本控制是工作组软件开发中的重要方面,它能防止意外的文件丢失、允许反追踪到早期版本、并能对版本进行分支、合并和管理。在软件开发和您需要比较两种版本的文件或找回早期版本的文件时,源代码的控制是非常有用的。 1.1 VSS中的文件 1.2 VSS中的项目 1.3 VSS的版本控制功能 1.4 文件的拆分和共享 1.5 工作文件夹(working folder) 一旦你将文件签出,VSS就开始在你的本地机上创建并管理你的工作文件夹。 2 VSS的客户端安装 2.1 安装VSS的系统条件 2.2 从网络安装VSS客户端 3 VSS的基本使用操作 3.1 登录VSS 3.2 VSS浏览器 当你一打开VSS,如果你设定了密码的话,它会提示你输入密码。如果你没有设定密码,你可以直接看到浏览器。在浏览器上,你可以浏览你的数据库、查看项目列表、显示文件统计信息、执行命令对文件和项目进行操作等。浏览器的最上方的标题栏是你当前连接的数据库。VSS使用符号来提供有关文件和项目信息。 3.3 VSS基本操作 3.3.1创建新的文件夹 3.3.2添加文件夹 3.3.3添加文件 3.3.3查看文件 3.3.4创建工作文件夹 3.3.5修改和编辑文件 3.3.6移动文件/文件夹 3.3.7共享文件/文件夹(share) 3.3.8拆分文件(branch) 3.3.9删除/恢复文件或文件夹 3.3.10查看文件/文件夹的历史信息或早期版本 3.3.11获取文件的最新版本 3.3.12获取文件的早期版本 3.3.13修改用户密码 3.3.15打开/关闭数据库 August 04 星座容易变胖的排名第一名巨蟹座 不只是因为他们消化特别好,圆圆的脸看起来也会比较胖。 第二名金牛座 好吃懒作也是很自然地就会胖起来。 第三名天秤座 其他方面倒还可以控制,但是不由自主地腰围就会一直放大。 第四名射手座 生长激素特别多,如果长得不够高就会容易往横向发展。 第五名双鱼座 可能是因为心理压力小很自然就会心宽体胖。 第六名狮子座 偏好高热量食物,想不胖也难! 第七名白羊座 吃得也不少,只是常有过度的动作消耗掉不少。 第八名双子座 他们行动比较灵活,即使有点胖也看不出来。 第九名水瓶座 有一点挑食,营养比较不好一点。 第十名天蝎座 他们因为藏了太多心事而有点消化不良,自然是胖不起来。 第十一名摩羯座 包了太多工作,过度忙碌所以不容易发胖。 第十二名处女座 因为容易紧张,加上重视饮食的均衡,所以最容易保持好的身材。 12星座接到歹徒电话(搞笑)白羊座 歹徒:星期日把錢放到西郊馬路邊第二個垃圾箱內…… 羊羊:西郊?不如去東郊!你知道嗎?東郊有座山,山勢很險,很刺激…… 歹徒已經準備好行囊去東郊過周末了。 金牛座 歹徒:星期日把錢放到西郊馬路邊第二個垃圾箱內…… 牛牛:好好好!你放心!我一定準時去!你可千萬別傷害我的狗狗,它是我家很重要的一員 !…… (?圩o家庭成員的牛牛可真傻得可?邸贿^這個歹徒也太蠢了!) 雙子座 歹徒:星期日把錢放到西郊馬路邊第二個垃圾箱內…… 雙子:可是我為什麼要聽你的?你要說出個原因?怼D悴恢牢覓赍X也不容易嗎?我6歲開始 上學,學到 20多歲才有了工作,可以掙錢…… (喂喂,別說了,對方早就挂了電話了) 巨蟹座 歹徒:星期日把錢放到西郊馬路邊第二個垃圾箱內…… 蟹蟹:這個,我要和媽媽商量一下。 歹徒:你媽在我手裏! 蟹蟹:那我和爸爸商量一下 歹徒:他也在我手裏! 蟹蟹:那我和姐姐商量一下…… 歹徒:她也在我手裏! 蟹蟹:那我和…… (不會吧!家裏就剩蟹蟹一個人啊?) 獅子座: 歹徒:星期日把錢放到西郊馬路邊第二個垃圾箱內…… 獅子:笨蛋!你憑什麽命令我?你給我聽好了!星期日,把我的家人放在西郊馬路邊第二個 垃圾箱內,否 則我 *** ! (好像……反了……) 處女座 歹徒:星期日把錢放到西郊馬路邊第二個垃圾箱內…… 處女座:那個地方髒死了!?你想得出?恚【驮谀辖嫉目Х任菅Y吧!叫我弟弟聽電話!…… 弟弟啊!吃晚 飯前洗手沒?晚上睡覺前別忘了洗澡,耳朵後邊也洗乾淨…… (電話這邊歹徒早已一頭汗水) 天秤座 歹徒:星期日把錢放到西郊馬路邊第二個垃圾箱內…… 枰兒:我相信你本質不是如此,你一定是遇到麻煩了吧?真可憐!但你不要對生活失去信心 !要堅?姡∥?這裏還有100元,你先拿去用吧……歹徒早已感動的泣不成聲了。 天蝎座 歹徒:星期日把錢放到西郊馬路邊第二個垃圾箱內…… 蝎蝎:嗯。好的。第二個。ok! 第二天。歹徒一打開垃圾箱發現有個警察在裏邊,自己身後是一排警車。蝎蝎拉著家人在不 遠處沖自己招手…… 射手座 歹徒:星期日把錢放到…… 射手:喂!你有點效率好不好?辦事要迅速《畣幔磕悻F在把好表,我5分鐘內准到!(說完 挂掉電話,衝出屋子……) 歹徒:我……我還沒有說地點呢…… 魔羯座 歹徒:星期日把錢放到西郊馬路邊第二個垃圾箱內…… 魔羯:可是這個星期天我?在沒時間!公司還有事……你下星期吧! 歹徒:喂——你別挂!喂!喂!…… 水瓶座 這大概是最冷靜的星座了。挂掉電話就報警。冷靜! 雙魚座 歹徒:星期日…… 魚魚:(已經咧開嘴)哇~~~媽媽~~我要媽媽~~~你快?戆?~~~爸爸~~我怕~~~~媽媽 ~~~~~~~ 歹徒:(心裏也酸酸的)好了好了,別哭了。我也想哭了~~~~~~ 魚魚還是不停的哭。 歹徒:(一咬牙,一跺脚別哭了!我放了你家人還不行嗎~~~~ April 25 delphi中的线程类Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉都有说到,但基本上都是对TThread类的几个成员作一简单介绍,再说明一下Execute的实现和Synchronize的用法就完了。然而这并不是多线程编程的全部,我写此文的目的在于对此作一个补充。
线程本质上是进程中一段并发运行的代码。一个进程至少有一个线程,即所谓的主线程。同时还可以有多个子线程。当一个进程中用到超过一个线程时,就是所谓的“多线程”。 那么这个所谓的“一段代码”是如何定义的呢?其实就是一个函数或过程(对Delphi而言)。 如果用Windows API来创建线程的话,是通过一个叫做CreateThread的API函数来实现的,它的定义为: HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ); 其各参数如它们的名称所说,分别是:线程属性(用于在NT下进行线程的安全属性设置,在9X下无效),堆栈大小,起始地址,参数,创建标志(用于设置线程创建时的状态),线程ID,最后返回线程Handle。其中的起始地址就是线程函数的入口,直至线程函数结束,线程也就结束了。
整个线程的执行过程如下图所示: 因为CreateThread参数很多,而且是Windows的API,所以在C Runtime Library里提供了一个通用的线程函数(理论上可以在任何支持线程的OS中使用):
unsigned long _beginthread(void (_USERENTRY *__start)(void *), unsigned __stksize, void *__arg); Delphi也提供了一个相同功能的类似函数:
function BeginThread(SecurityAttributes: Pointer; StackSize: LongWord; ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord; var ThreadId: LongWord): Integer; 这三个函数的功能是基本相同的,它们都是将线程函数中的代码放到一个独立的线程中执行。线程函数与一般函数的最大不同在于,线程函数一启动,这三个线程启动函数就返回了,主线程继续向下执行,而线程函数在一个独立的线程中执行,它要执行多久,什么时候返回,主线程是不管也不知道的。
正常情况下,线程函数返回后,线程就终止了。但也有其它方式: Windows API:
VOID ExitThread( DWORD dwExitCode ); C Runtime Library: void _endthread(void); Delphi Runtime Library: procedure EndThread(ExitCode: Integer); 为了记录一些必要的线程数据(状态/属性等),OS会为线程创建一个内部Object,如在Windows中那个Handle便是这个内部Object的Handle,所以在线程结束的时候还应该释放这个Object。
虽然说用API或RTL(Runtime Library)已经可以很方便地进行多线程编程了,但是还是需要进行较多的细节处理,为此Delphi在Classes单元中对线程作了一个较好的封装,这就是VCL的线程类:TThread
使用这个类也很简单,大多数的Delphi书籍都有说,基本用法是:先从TThread派生一个自己的线程类(因为TThread是一个抽象类,不能生成实例),然后是Override抽象方法:Execute(这就是线程函数,也就是在线程中执行的代码部分),如果需要用到可视VCL对象,还需要通过Synchronize过程进行。关于之方面的具体细节,这里不再赘述,请参考相关书籍。 本文接下来要讨论的是TThread类是如何对线程进行封装的,也就是深入研究一下TThread类的实现。因为只是真正地了解了它,才更好地使用它。 下面是DELPHI7中TThread类的声明(本文只讨论在Windows平台下的实现,所以去掉了所有有关Linux平台部分的代码): TThread = class
private
FHandle: THandle; FThreadID: THandle; FCreateSuspended: Boolean; FTerminated: Boolean; FSuspended: Boolean; FFreeOnTerminate: Boolean; FFinished: Boolean; FReturnValue: Integer; FOnTerminate: TNotifyEvent; FSynchronize: TSynchronizeRecord; FFatalException: TObject; procedure CallOnTerminate; class procedure Synchronize(ASyncRec: PSynchronizeRecord); overload; function GetPriority: TThreadPriority; procedure SetPriority(Value: TThreadPriority); procedure SetSuspended(Value: Boolean); protected
procedure CheckThreadError(ErrCode: Integer); overload; procedure CheckThreadError(Success: Boolean); overload; procedure DoTerminate; virtual; procedure Execute; virtual; abstract; procedure Synchronize(Method: TThreadMethod); overload; property ReturnValue: Integer read FReturnValue write FReturnValue; property Terminated: Boolean read FTerminated; public
constructor Create(CreateSuspended: Boolean); destructor Destroy; override; procedure AfterConstruction; override; procedure Resume; procedure Suspend; procedure Terminate; function WaitFor: LongWord; class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload; class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod); property FatalException: TObject read FFatalException; property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate; property Handle: THandle read FHandle; property Priority: TThreadPriority read GetPriority write SetPriority; property Suspended: Boolean read FSuspended write SetSuspended; property ThreadID: THandle read FThreadID; property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate; end; TThread类在Delphi的RTL里算是比较简单的类,类成员也不多,类属性都很简单明白,本文将只对几个比较重要的类成员方法和唯一的事件:OnTerminate作详细分析。
首先就是构造函数: constructor TThread.Create(CreateSuspended: Boolean);
begin inherited Create; AddThread; FSuspended := CreateSuspended; FCreateSuspended := CreateSuspended; FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID); if FHandle = 0 then raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]); end;
虽然这个构造函数没有多少代码,但却可以算是最重要的一个成员,因为线程就是在这里被创建的。
在通过Inherited调用TObject.Create后,第一句就是调用一个过程:AddThread,其源码如下: procedure AddThread;
begin InterlockedIncrement(ThreadCount); end; 同样有一个对应的RemoveThread:
procedure RemoveThread; begin InterlockedDecrement(ThreadCount); end; 它们的功能很简单,就是通过增减一个全局变量来统计进程中的线程数。只是这里用于增减变量的并不是常用的Inc/Dec过程,而是用了InterlockedIncrement/InterlockedDecrement这一对过程,它们实现的功能完全一样,都是对变量加一或减一。但它们有一个最大的区别,那就是InterlockedIncrement/InterlockedDecrement是线程安全的。即它们在多线程下能保证执行结果正确,而Inc/Dec不能。或者按操作系统理论中的术语来说,这是一对“原语”操作。
以加一为例来说明二者实现细节上的不同: 一般来说,对内存数据加一的操作分解以后有三个步骤: 1、从内存中读出数据 2、数据加一 3、存入内存 现在假设在一个两个线程的应用中用Inc进行加一操作可能出现的一种情况: 1、线程A从内存中读出数据(假设为3) 2、线程B从内存中读出数据(也是3) 3、线程A对数据加一(现在是4) 4、线程B对数据加一(现在也是4) 5、线程A将数据存入内存(现在内存中的数据是4) 6、线程B也将数据存入内存(现在内存中的数据还是4,但两个线程都对它加了一,应该是5才对,所以这里出现了错误的结果) 而用InterlockIncrement过程则没有这个问题,因为所谓“原语”是一种不可中断的操作,即操作系统能保证在一个“原语”执行完毕前不会进行线程切换。所以在上面那个例子中,只有当线程A执行完将数据存入内存后,线程B才可以开始从中取数并进行加一操作,这样就保证了即使是在多线程情况下,结果也一定会是正确的。 前面那个例子也说明一种“线程访问冲突”的情况,这也就是为什么线程之间需要“同步”(Synchronize),关于这个,在后面说到同步时还会再详细讨论。 说到同步,有一个题外话:加拿大滑铁卢大学的教授李明曾就Synchronize一词在“线程同步”中被译作“同步”提出过异议,个人认为他说的其实很有道理。在中文中“同步”的意思是“同时发生”,而“线程同步”目的就是避免这种“同时发生”的事情。而在英文中,Synchronize的意思有两个:一个是传统意义上的同步(To occur at the same time),另一个是“协调一致”(To operate in unison)。在“线程同步”中的Synchronize一词应该是指后面一种意思,即“保证多个线程在访问同一数据时,保持协调一致,避免出错”。不过像这样译得不准的词在IT业还有很多,既然已经是约定俗成了,本文也将继续沿用,只是在这里说明一下,因为软件开发是一项细致的工作,该弄清楚的,绝不能含糊。 扯远了,回到TThread的构造函数上,接下来最重要就是这句了: FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
这里就用到了前面说到的Delphi RTL函数BeginThread,它有很多参数,关键的是第三、四两个参数。第三个参数就是前面说到的线程函数,即在线程中执行的代码部分。第四个参数则是传递给线程函数的参数,在这里就是创建的线程对象(即Self)。其它的参数中,第五个是用于设置线程在创建后即挂起,不立即执行(启动线程的工作是在AfterConstruction中根据CreateSuspended标志来决定的),第六个是返回线程ID。
现在来看TThread的核心:线程函数ThreadProc。有意思的是这个线程类的核心却不是线程的成员,而是一个全局函数(因为BeginThread过程的参数约定只能用全局函数)。下面是它的代码: function ThreadProc(Thread: TThread): Integer;
var FreeThread: Boolean; begin try if not Thread.Terminated then try Thread.Execute; except Thread.FFatalException := AcquireExceptionObject; end; finally FreeThread := Thread.FFreeOnTerminate; Result := Thread.FReturnValue; Thread.DoTerminate; Thread.FFinished := True; SignalSyncEvent; if FreeThread then Thread.Free; EndThread(Result); end; end; 虽然也没有多少代码,但却是整个TThread中最重要的部分,因为这段代码是真正在线程中执行的代码。下面对代码作逐行说明:
首先判断线程类的Terminated标志,如果未被标志为终止,则调用线程类的Execute方法执行线程代码,因为TThread是抽象类,Execute方法是抽象方法,所以本质上是执行派生类中的Execute代码。 所以说,Execute就是线程类中的线程函数,所有在Execute中的代码都需要当作线程代码来考虑,如防止访问冲突等。 如果Execute发生异常,则通过AcquireExceptionObject取得异常对象,并存入线程类的FFatalException成员中。 最后是线程结束前做的一些收尾工作。局部变量FreeThread记录了线程类的FreeOnTerminated属性的设置,然后将线程返回值设置为线程类的返回值属性的值。然后执行线程类的DoTerminate方法。 DoTerminate方法的代码如下: procedure TThread.DoTerminate;
begin if Assigned(FOnTerminate) then Synchronize(CallOnTerminate); end; 很简单,就是通过Synchronize来调用CallOnTerminate方法,而CallOnTerminate方法的代码如下,就是简单地调用OnTerminate事件:
procedure TThread.CallOnTerminate;
begin if Assigned(FOnTerminate) then FOnTerminate(Self); end; 因为OnTerminate事件是在Synchronize中执行的,所以本质上它并不是线程代码,而是主线程代码(具体见后面对Synchronize的分析)。
执行完OnTerminate后,将线程类的FFinished标志设置为True。 接下来执行SignalSyncEvent过程,其代码如下: procedure SignalSyncEvent;
begin SetEvent(SyncEvent); end; 也很简单,就是设置一下一个全局Event:SyncEvent,关于Event的使用,本文将在后文详述,而SyncEvent的用途将在WaitFor过程中说明。
然后根据FreeThread中保存的FreeOnTerminate设置决定是否释放线程类,在线程类释放时,还有一些些操作,详见接下来的析构函数实现。 最后调用EndThread结束线程,返回线程返回值。 至此,线程完全结束。 说完构造函数,再来看析构函数: destructor TThread.Destroy;
begin if (FThreadID <> 0) and not FFinished then begin Terminate; if FCreateSuspended then Resume; WaitFor; end; if FHandle <> 0 then CloseHandle(FHandle); inherited Destroy; FFatalException.Free; RemoveThread; end; 在线程对象被释放前,首先要检查线程是否还在执行中,如果线程还在执行中(线程ID不为0,并且线程结束标志未设置),则调用Terminate过程结束线程。Terminate过程只是简单地设置线程类的Terminated标志,如下面的代码:
procedure TThread.Terminate;
begin FTerminated := True; end; 所以线程仍然必须继续执行到正常结束后才行,而不是立即终止线程,这一点要注意。
在这里说一点题外话:很多人都问过我,如何才能“立即”终止线程(当然是指用TThread创建的线程)。结果当然是不行!终止线程的唯一办法就是让Execute方法执行完毕,所以一般来说,要让你的线程能够尽快终止,必须在Execute方法中在较短的时间内不断地检查Terminated标志,以便能及时地退出。这是设计线程代码的一个很重要的原则! 当然如果你一定要能“立即”退出线程,那么TThread类不是一个好的选择,因为如果用API强制终止线程的话,最终会导致TThread线程对象不能被正确释放,在对象析构时出现Access Violation。这种情况你只能用API或RTL函数来创建线程。 如果线程处于启动挂起状态,则将线程转入运行状态,然后调用WaitFor进行等待,其功能就是等待到线程结束后才继续向下执行。关于WaitFor的实现,将放到后面说明。 线程结束后,关闭线程Handle(正常线程创建的情况下Handle都是存在的),释放操作系统创建的线程对象。 然后调用TObject.Destroy释放本对象,并释放已经捕获的异常对象,最后调用RemoveThread减小进程的线程数。 其它关于Suspend/Resume及线程优先级设置等方面,不是本文的重点,不再赘述。下面要讨论的是本文的另两个重点:Synchronize和WaitFor。 但是在介绍这两个函数之前,需要先介绍另外两个线程同步技术:事件和临界区。 事件(Event)与Delphi中的事件有所不同。从本质上说,Event其实相当于一个全局的布尔变量。它有两个赋值操作:Set和Reset,相当于把它设置为True或False。而检查它的值是通过WaitFor操作进行。对应在Windows平台上,是三个API函数:SetEvent、ResetEvent、WaitForSingleObject(实现WaitFor功能的API还有几个,这是最简单的一个)。 这三个都是原语,所以Event可以实现一般布尔变量不能实现的在多线程中的应用。Set和Reset的功能前面已经说过了,现在来说一下WaitFor的功能: WaitFor的功能是检查Event的状态是否是Set状态(相当于True),如果是则立即返回,如果不是,则等待它变为Set状态,在等待期间,调用WaitFor的线程处于挂起状态。另外WaitFor有一个参数用于超时设置,如果此参数为0,则不等待,立即返回Event的状态,如果是INFINITE则无限等待,直到Set状态发生,若是一个有限的数值,则等待相应的毫秒数后返回Event的状态。 当Event从Reset状态向Set状态转换时,唤醒其它由于WaitFor这个Event而挂起的线程,这就是它为什么叫Event的原因。所谓“事件”就是指“状态的转换”。通过Event可以在线程间传递这种“状态转换”信息。 当然用一个受保护(见下面的临界区介绍)的布尔变量也能实现类似的功能,只要用一个循环检查此布尔值的代码来代替WaitFor即可。从功能上说完全没有问题,但实际使用中就会发现,这样的等待会占用大量的CPU资源,降低系统性能,影响到别的线程的执行速度,所以是不经济的,有的时候甚至可能会有问题。所以不建议这样用。 临界区(CriticalSection)则是一项共享数据访问保护的技术。它其实也是相当于一个全局的布尔变量。但对它的操作有所不同,它只有两个操作:Enter和Leave,同样可以把它的两个状态当作True和False,分别表示现在是否处于临界区中。这两个操作也是原语,所以它可以用于在多线程应用中保护共享数据,防止访问冲突。 用临界区保护共享数据的方法很简单:在每次要访问共享数据之前调用Enter设置进入临界区标志,然后再操作数据,最后调用Leave离开临界区。它的保护原理是这样的:当一个线程进入临界区后,如果此时另一个线程也要访问这个数据,则它会在调用Enter时,发现已经有线程进入临界区,然后此线程就会被挂起,等待当前在临界区的线程调用Leave离开临界区,当另一个线程完成操作,调用Leave离开后,此线程就会被唤醒,并设置临界区标志,开始操作数据,这样就防止了访问冲突。 以前面那个InterlockedIncrement为例,我们用CriticalSection(Windows API)来实现它: Var
InterlockedCrit : TRTLCriticalSection; Procedure InterlockedIncrement( var aValue : Integer ); Begin EnterCriticalSection( InterlockedCrit ); Inc( aValue ); LeaveCriticalSection( InterlockedCrit ); End; 现在再来看前面那个例子:
1.线程A进入临界区(假设数据为3) 2.线程B进入临界区,因为A已经在临界区中,所以B被挂起 3.线程A对数据加一(现在是4) 4.线程A离开临界区,唤醒线程B(现在内存中的数据是4) 5.线程B被唤醒,对数据加一(现在就是5了) 6.线程B离开临界区,现在的数据就是正确的了。 临界区就是这样保护共享数据的访问。 关于临界区的使用,有一点要注意:即数据访问时的异常情况处理。因为如果在数据操作时发生异常,将导致Leave操作没有被执行,结果将使本应被唤醒的线程未被唤醒,可能造成程序的没有响应。所以一般来说,如下面这样使用临界区才是正确的做法: EnterCriticalSection
Try // 操作临界区数据 Finally LeaveCriticalSection End; 最后要说明的是,Event和CriticalSection都是操作系统资源,使用前都需要创建,使用完后也同样需要释放。如TThread类用到的一个全局Event:SyncEvent和全局CriticalSection:TheadLock,都是在InitThreadSynchronization和DoneThreadSynchronization中进行创建和释放的,而它们则是在Classes单元的Initialization和Finalization中被调用的。
由于在TThread中都是用API来操作Event和CriticalSection的,所以前面都是以API为例,其实Delphi已经提供了对它们的封装,在SyncObjs单元中,分别是TEvent类和TCriticalSection类。用法也与前面用API的方法相差无几。因为TEvent的构造函数参数过多,为了简单起见,Delphi还提供了一个用默认参数初始化的Event类:TSimpleEvent。 顺便再介绍一下另一个用于线程同步的类:TMultiReadExclusiveWriteSynchronizer,它是在SysUtils单元中定义的。据我所知,这是Delphi RTL中定义的最长的一个类名,还好它有一个短的别名:TMREWSync。至于它的用处,我想光看名字就可以知道了,我也就不多说了。 有了前面对Event和CriticalSection的准备知识,可以正式开始讨论Synchronize和WaitFor了。 我们知道,Synchronize是通过将部分代码放到主线程中执行来实现线程同步的,因为在一个进程中,只有一个主线程。先来看看Synchronize的实现: procedure TThread.Synchronize(Method: TThreadMethod);
begin FSynchronize.FThread := Self; FSynchronize.FSynchronizeException := nil; FSynchronize.FMethod := Method; Synchronize(@FSynchronize); end; 其中FSynchronize是一个记录类型:
PSynchronizeRecord = ^TSynchronizeRecord; TSynchronizeRecord = record FThread: TObject; FMethod: TThreadMethod; FSynchronizeException: TObject; end; 用于进行线程和主线程之间进行数据交换,包括传入线程类对象,同步方法及发生的异常。
在Synchronize中调用了它的一个重载版本,而且这个重载版本比较特别,它是一个“类方法”。所谓类方法,是一种特殊的类成员方法,它的调用并不需要创建类实例,而是像构造函数那样,通过类名调用。之所以会用类方法来实现它,是因为为了可以在线程对象没有创建时也能调用它。不过实际中是用它的另一个重载版本(也是类方法)和另一个类方法StaticSynchronize。下面是这个Synchronize的代码: class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord);
var SyncProc: TSyncProc; begin if GetCurrentThreadID = MainThreadID then ASyncRec.FMethod else begin SyncProc.Signal := CreateEvent(nil, True, False, nil); try EnterCriticalSection(ThreadLock); try if SyncList = nil then SyncList := TList.Create; SyncProc.SyncRec := ASyncRec; SyncList.Add(@SyncProc); SignalSyncEvent; if Assigned(WakeMainThread) then WakeMainThread(SyncProc.SyncRec.FThread); LeaveCriticalSection(ThreadLock); try WaitForSingleObject(SyncProc.Signal, INFINITE); finally EnterCriticalSection(ThreadLock); end; finally LeaveCriticalSection(ThreadLock); end; finally CloseHandle(SyncProc.Signal); end; if Assigned(ASyncRec.FSynchronizeException) then raise ASyncRec.FSynchronizeException; end; end; 这段代码略多一些,不过也不算太复杂。
首先是判断当前线程是否是主线程,如果是,则简单地执行同步方法后返回。 如果不是主线程,则准备开始同步过程。 通过局部变量SyncProc记录线程交换数据(参数)和一个Event Handle,其记录结构如下: TSyncProc = record
SyncRec: PSynchronizeRecord; Signal: THandle; end; 然后创建一个Event,接着进入临界区(通过全局变量ThreadLock进行,因为同时只能有一个线程进入Synchronize状态,所以可以用全局变量记录),然后就是把这个记录数据存入SyncList这个列表中(如果这个列表不存在的话,则创建它)。可见ThreadLock这个临界区就是为了保护对SyncList的访问,这一点在后面介绍CheckSynchronize时会再次看到。
再接下就是调用SignalSyncEvent,其代码在前面介绍TThread的构造函数时已经介绍过了,它的功能就是简单地将SyncEvent作一个Set的操作。关于这个SyncEvent的用途,将在后面介绍WaitFor时再详述。 接下来就是最主要的部分了:调用WakeMainThread事件进行同步操作。WakeMainThread是一个TNotifyEvent类型的全局事件。这里之所以要用事件进行处理,是因为Synchronize方法本质上是通过消息,将需要同步的过程放到主线程中执行,如果在一些没有消息循环的应用中(如Console或DLL)是无法使用的,所以要使用这个事件进行处理。 而响应这个事件的是Application对象,下面两个方法分别用于设置和清空WakeMainThread事件的响应(来自Forms单元): procedure TApplication.HookSynchronizeWakeup;
begin Classes.WakeMainThread := WakeMainThread; end; procedure TApplication.UnhookSynchronizeWakeup;
begin Classes.WakeMainThread := nil; end; 上面两个方法分别是在TApplication类的构造函数和析构函数中被调用。
这就是在Application对象中WakeMainThread事件响应的代码,消息就是在这里被发出的,它利用了一个空消息来实现: procedure TApplication.WakeMainThread(Sender: TObject);
begin PostMessage(Handle, WM_NULL, 0, 0); end; 而这个消息的响应也是在Application对象中,见下面的代码(删除无关的部分):
procedure TApplication.WndProc(var Message: TMessage); … begin try … with Message do case Msg of … WM_NULL: CheckSynchronize; … except HandleException(Self); end; end; 其中的CheckSynchronize也是定义在Classes单元中的,由于它比较复杂,暂时不详细说明,只要知道它是具体处理Synchronize功能的部分就好,现在继续分析Synchronize的代码。 在执行完WakeMainThread事件后,就退出临界区,然后调用WaitForSingleObject开始等待在进入临界区前创建的那个Event。这个Event的功能是等待这个同步方法的执行结束,关于这点,在后面分析CheckSynchronize时会再说明。 注意在WaitForSingleObject之后又重新进入临界区,但没有做任何事就退出了,似乎没有意义,但这是必须的! 因为临界区的Enter和Leave必须严格的一一对应。那么是否可以改成这样呢: if Assigned(WakeMainThread) then
WakeMainThread(SyncProc.SyncRec.FThread); WaitForSingleObject(SyncProc.Signal, INFINITE); finally LeaveCriticalSection(ThreadLock); end; 上面的代码和原来的代码最大的区别在于把WaitForSingleObject也纳入临界区的限制中了。看上去没什么影响,还使代码大大简化了,但真的可以吗?
事实上是不行! 因为我们知道,在Enter临界区后,如果别的线程要再进入,则会被挂起。而WaitFor方法则会挂起当前线程,直到等待别的线程SetEvent后才会被唤醒。如果改成上面那样的代码的话,如果那个SetEvent的线程也需要进入临界区的话,死锁(Deadlock)就发生了(关于死锁的理论,请自行参考操作系统原理方面的资料)。 死锁是线程同步中最需要注意的方面之一! 最后释放开始时创建的Event,如果被同步的方法返回异常的话,还会在这里再次抛出异常。 回到前面CheckSynchronize,见下面的代码: function CheckSynchronize(Timeout: Integer = 0): Boolean;
var SyncProc: PSyncProc; LocalSyncList: TList; begin if GetCurrentThreadID <> MainThreadID then raise EThread.CreateResFmt(@SCheckSynchronizeError, [GetCurrentThreadID]); if Timeout > 0 then WaitForSyncEvent(Timeout) else ResetSyncEvent; LocalSyncList := nil; EnterCriticalSection(ThreadLock); try Integer(LocalSyncList) := InterlockedExchange(Integer(SyncList), Integer(LocalSyncList)); try Result := (LocalSyncList <> nil) and (LocalSyncList.Count > 0); if Result then begin while LocalSyncList.Count > 0 do begin SyncProc := LocalSyncList[0]; LocalSyncList.Delete(0); LeaveCriticalSection(ThreadLock); try try SyncProc.SyncRec.FMethod; except SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject; end; finally EnterCriticalSection(ThreadLock); end; SetEvent(SyncProc.signal); end; end; finally LocalSyncList.Free; end; finally LeaveCriticalSection(ThreadLock); end; end; 首先,这个方法必须在主线程中被调用(如前面通过消息传递到主线程),否则就抛出异常。
接下来调用ResetSyncEvent(它与前面SetSyncEvent对应的,之所以不考虑WaitForSyncEvent的情况,是因为只有在Linux版下才会调用带参数的CheckSynchronize,Windows版下都是调用默认参数0的CheckSynchronize)。 现在可以看出SyncList的用途了:它是用于记录所有未被执行的同步方法的。因为主线程只有一个,而子线程可能有很多个,当多个子线程同时调用同步方法时,主线程可能一时无法处理,所以需要一个列表来记录它们。 在这里用一个局部变量LocalSyncList来交换SyncList,这里用的也是一个原语:InterlockedExchange。同样,这里也是用临界区将对SyncList的访问保护起来。 只要LocalSyncList不为空,则通过一个循环来依次处理累积的所有同步方法调用。最后把处理完的LocalSyncList释放掉,退出临界区。 再来看对同步方法的处理:首先是从列表中移出(取出并从列表中删除)第一个同步方法调用数据。然后退出临界区(原因当然也是为了防止死锁)。 接着就是真正的调用同步方法了。 如果同步方法中出现异常,将被捕获后存入同步方法数据记录中。 重新进入临界区后,调用SetEvent通知调用线程,同步方法执行完成了(详见前面Synchronize中的WaitForSingleObject调用)。 至此,整个Synchronize的实现介绍完成。 最后来说一下WaitFor,它的功能就是等待线程执行结束。其代码如下: function TThread.WaitFor: LongWord;
var H: array[0..1] of THandle; WaitResult: Cardinal; Msg: TMsg; begin H[0] := FHandle; if GetCurrentThreadID = MainThreadID then begin WaitResult := 0; H[1] := SyncEvent; repeat { This prevents a potential deadlock if the background thread does a SendMessage to the foreground thread } if WaitResult = WAIT_OBJECT_0 + 2 then PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE); WaitResult := MsgWaitForMultipleObjects(2, H, False, 1000, QS_SENDMESSAGE); CheckThreadError(WaitResult <> WAIT_FAILED); if WaitResult = WAIT_OBJECT_0 + 1 then CheckSynchronize; until WaitResult = WAIT_OBJECT_0; end else WaitForSingleObject(H[0], INFINITE); CheckThreadError(GetExitCodeThread(H[0], Result)); end; 如果不是在主线程中执行WaitFor的话,很简单,只要调用WaitForSingleObject等待此线程的Handle为Signaled状态即可。 如果是在主线程中执行WaitFor则比较麻烦。首先要在Handle数组中增加一个SyncEvent,然后循环等待,直到线程结束(即MsgWaitForMultipleObjects返回WAIT_OBJECT_0,详见MSDN中关于此API的说明)。 在循环等待中作如下处理:如果有消息发生,则通过PeekMessage取出此消息(但并不把它从消息循环中移除),然后调用MsgWaitForMultipleObjects来等待线程Handle或SyncEvent出现Signaled状态,同时监听消息(QS_SENDMESSAGE参数,详见MSDN中关于此API的说明)。可以把此API当作一个可以同时等待多个Handle的WaitForSingleObject。如果是SyncEvent被SetEvent(返回WAIT_OBJECT_0 + 1),则调用CheckSynchronize处理同步方法。 为什么在主线程中调用WaitFor必须用MsgWaitForMultipleObjects,而不能用WaitForSingleObject等待线程结束呢?因为防止死锁。由于在线程函数Execute中可能调用Synchronize处理同步方法,而同步方法是在主线程中执行的,如果用WaitForSingleObject等待的话,则主线程在这里被挂起,同步方法无法执行,导致线程也被挂起,于是发生死锁。 而改用WaitForMultipleObjects则没有这个问题。首先,它的第三个参数为False,表示只要线程Handle或SyncEvent中只要有一个Signaled即可使主线程被唤醒,至于加上QS_SENDMESSAGE是因为Synchronize是通过消息传到主线程来的,所以还要防止消息被阻塞。这样,当线程中调用Synchronize时,主线程就会被唤醒并处理同步调用,在调用完成后继续进入挂起等待状态,直到线程结束。 至此,对线程类TThread的分析可以告一个段落了,对前面的分析作一个总结: 1、线程类的线程必须按正常的方式结束,即Execute执行结束,所以在其中的代码中必须在适当的地方加入足够多的对Terminated标志的判断,并及时退出。如果必须要“立即”退出,则不能使用线程类,而要改用API或RTL函数。 2、对可视VCL的访问要放在Synchronize中,通过消息传递到主线程中,由主线程处理。 3、线程共享数据的访问应该用临界区进行保护(当然用Synchronize也行)。 4、线程通信可以采用Event进行(当然也可以用Suspend/Resume)。 5、当在多线程应用中使用多种线程同步方式时,一定要小心防止出现死锁。 6、等待线程结束要用WaitFor方法。 April 19 永远的1072003年 2月19日 星期三 晴
今天我回到侵蚀,我看见肖鹏在打电话,我问他是不是黄章平送她回的,因为我出教室的前告诉黄:一个女生单独回寝室,很危险,所以你要送她回去。肖鹏说是的。方如君说肖鹏怎么和杜鹃的前夫混到一块去了。她们说前夫太难听了,我说那就叫XXX吧!哈哈……之后,方如君说为什么刘弦每天都在不停地吃东西,我说为什么她总在说别人,最经典,在学方言,“我晕”。 签名:盼盼
2003年 2月20日 星期四 晴(大风)
失去一回与蚊子斗斗的机会,真太遗憾了。方如君这家伙真厉害,空拳斗不过包子,希望明天不是我!没什么可写的,就瞎写点吧! 杜鹃今天买了件新衣服,看起来她心情不错;刘弦还在吃瓜子;吴早霞在剪脚指甲,还不让我写,我偏写,写了你又不可以揍我:) :);方如君在那叽叽呱呱,我的耳朵好像有点麻;肖鹏边写信边玩手机,时尚,新潮;盼盼不失老本行,呼呼地睡了;蚊子回来了,来斗斗……
签名:刘丹
2003年 2月23日 星期日 小雨转多云
晚上溜完冰回来,把她们都吓了一跳,是因为我把弟弟带过来睡觉,他们都觉得很有趣,有很多原因,他是个小男孩,而且今晚要在这里过夜。这可是创寝室记录。还有就是当郑丽芳接电话时,他却站在门口偷听电话,拉也拉不过来,最让他尴尬地是,可是我们洗的时候,他不得不扭过头去左看看右看看,洗完后,头还是不好意思回过来。 还有一些很有趣的事都发生在今天,那就是吴早霞上网忘了带钱,后来碰到一个很久很久……以前认识的男生,然后第一句话:“我是吴早霞,上网忘了带钱,你帮我付一下钱。”他却支支吾吾:“记起来了!”也不知是否真的记得这只大虾,估计吴早霞那时一定尴尬、无奈,想找个地缝钻进去。
其次就是抢肖鹏男朋友的电话听,他被我们吓的都敢说话了,想听听都听不到,后来被逼出话来,也不知是她们吵的太厉害,还是他被我们吓着了,声音很小而且很惊怯,想象一下,他和她甜言蜜语时会是怎样的心情,而且寝室成员一致都认为肖鹏变得更漂亮了,无可非议,这是爱情的力量。
很奇怪耶,方如君今晚做仰卧起坐特别温柔,在下铺一点感觉都没有,希望她以后坚持如此,变得更淑女些,更希望她今天看了不要生气,要不然可要扁我了!
看来,今晚不过一点是不会有睡觉的气氛,现在她们还是有说有笑,谈天说地。
签名:蚊子
2003年 2月24日 星期一 阴
唉,我怎么如此苦命,轮到我来做老大的这天,怎么就这么平静呢?看着寝室的成员,寒心啊,才四个,555,还不敢放纵自己太伤心,此时还是春季,万一突发洪水怎么办?左也不是,右也不是,还是乖乖记下最平凡的一天:) 呵呵,也不知道寝室的人越来越少是不是好兆头,女孩子长大了,都幻想浪漫的爱情,现在不回来,咱也理解,俗话不也说得好,理解万岁!
盼盼下晚自习后就将书本全扔给我,我都怀疑自己是否成了她得全职秘书,纳闷!她说是要去超市买东西,嘿,鬼晓得她去“钓鱼”还是别的,估计也是不言而喻得。她啊,成天说别人,自己呢,哈哈,好像也有绯闻缠身(有兴趣的,就自己去问盼盼,会有意外的收获)。兔子方呢,据说去上网了,可能生活太郁闷又不愿对我们倾诉,又去网上寻找寄托了。斑马呢,她去做什么,我们从来也不去猜测,坠入爱河的斑马啊,多么令人羡慕!新成员大虾的行踪是最诡秘的,谁也不知道她每天会发生些什么爆笑的事情,就因为她是大一的学生,我们就把她当成最小的成员,其实不然,她是我么真正的老大,主要是她太过谦虚,不敢轻易领导♂。乌龟呢,在忙着整理她的窝居,好像很忙,可我看了半天也还是不知道她在忙什么,简而言之,瞎忙!雕,刚找我借刀,放心,这家伙不是想不开,我猜的不错,她又要吃东西了,哈哈,那不是鸟巢,而是一大型仓库,她总有东西吃。提个一件,我们下次颁给她一个特别奖——食神。该写我自己了,算了,我自己也没什么好写的,反正大家都知道,今天该我写日记。
好了,就写这多了,等她们回来的时候估计要杀我了,快闪,睡觉!
签名:杰罗
2003年 2月25日 星期二 雨
今天同学请吃饭,在她们的劝说下,我不得不喝了两大杯啤酒,这对我来说,可是一个不小的自我挑战,要知道,我以前可是滴酒不沾的。回到寝室,已经差不多9:30了,杜鹃的床上可真是热闹,斑马、蚊子、小兔和熊猫依次排排坐,专心致志地看碟子。本想再插进我一个,可我发现已确实不行了,只好搬张凳子坐在一边,“爱碟之心,人皆有之”,对吧?看碟子时间过得快多了,不知不觉就快11点了。青蛙今天好像心情不佳,很早就睡了。盼盼一洗完就开始为她地耳洞烦恼,“这毛衣可怎么脱啊?”就是我们上铺地四个精力最充足,我出了一个绕口令让她们说,“红凤凰,粉凤凰,红粉凤凰花凤凰”,她们一个比一个说地起劲,还闹了不少笑话,蛮有趣的。我发现刘弦挺厉害,我们在上面讨论得热火朝天,她却做怀不乱,认认真真地看她的书,佩服! 签名:大虾
2003年 2月26日 星期三 雨
今天本人没吃晚饭,忽觉肚饿,便与大虾同行,前往当代,吃粉,回到寝室后,看到大家的眼光“刷刷刷”全看向电脑,原来大家正在看《谈判专家》,说到这个电视剧那可大有谈头,这两天全寝室的人都废寝忘食地看那个,我就可怜了,都看了四遍了,可她们还看,瞧,熊猫、雕、蚊子、乌龟还在看呢!呵呵,与昨天无大异。 签名:兔子
2003年 2月27日 星期四 雨
今天,我们一天没课,心情很好,下了晚自习,我回到寝室,就看见蚊子和熊猫在看舞蹈碟子,本一位蚊子心情不错,哪知是和boy朋友吃饭时闹了小矛盾,她是一个很有天赋地舞蹈演员,她男朋友也是,真希望他们俩能在学校将举办地“单双三人舞”比赛中取得好成绩,如果没参加,太可惜了。我们的小兔子也该参加。我嘛,就算了!没时间,唉!不想了。忽听到斑马发感慨“我太喜欢这电视剧的男主角了”,这就是在我们寝室曾经放疯了的《冬季恋歌》的男主角裴永俊,其实我也蛮喜欢的,太帅了。兔儿正在做仰卧起坐,唉!美丽是要付出代价的。耳边一遍遍传来《冬季恋歌》的主题曲,太感人,让人心动,真希望自己将来也有一段美好的恋情。青蛙好像正在看信,幸福啊!总有不断的来信。熊猫这几天都在为她刚打的耳洞忙碌,精心料理,生怕它发炎了。只有我们的龟同学最晚回,现在一边洗脚一边讨论离子烫,有朋友的人就是不一样。好了,就记这么多了,bye!see you next time! 签名:雕
2003年 3月2日 星期日 阴转小雨
呵呵……左肩挎个大包,左手端着刚被李伟吃了两大口(相当于我吃十口有余)的湖南米粉,右手也没闲着,拎着一大包咸鱼,看起来十很有几条了。踏上走向107大门的那条阴森森(尽管现在亮堂堂)的走廊,还没到寝室(又称The Home Of Animals),就传来杰罗甜言蜜语的声音,身着一套休闲服,正在门外煲男朋友的电话粥,瞧,刚挂断,又来了。推开门,斑马像个圆规坐在那里,目光呆滞面对着computer,一定是其他动物怕吵,这不两个耳朵都塞着耳麦,难怪今天寝室出奇的静,只听得到电脑散热的声音,呜……那个大雕,简直是不识抬举,居然模仿俺的姿势,在那认真地看English啊,不愧是成绩最好地一名丫头啊!从兔子的后方看去,半弯着腰,也不知是在搞东还是搞西,白色的秋裤把她那两条QQ的娃娃内裤也印出来了,嘻嘻——还在那装惊,搞么是学习撒!真是少见,少见啊!旁边的虾来了,生怕俺海龟把它给吃了,就不停地提醒我干这干那,底下的国宝盼盼最morden,边听workman边发短信,后来又闹什么电台有鬼故事,这下好了,害得上面的虾米在热锅里乱跳,最后还是给煮了。至于蚊子嘛,正在春眠,它一定是想多睡会,夏天♂就是被它宰的好时机了,好怕哦,好毒哦!接下来,你们将会又一个very good的听觉效果啦!byebye! 签名:乌龟
人气排行榜
盼盼9 斑马4 蚊子6 杰罗9 大虾8 兔子8 雕6 乌龟6 2003年 3月3日 星期日 雨加雪籽
晚上好冷,居然下了雪籽,那声音好恐怖,回到寝室,洗了上床,好舒服。斑马在看《流金岁月》,一句话都不说,直到乌龟回来,她居然拿冰棒欠我们,可恶。不过她还比较有良心,帮我们买了冰棒,虽然我不是很满意,但是i always thank her。可怜的青蛙,不能吃,我也不能吃,但是当时忘了,吃完才想起,晚了,呵呵。她呀,还说什么搞人气排行榜,最可恶地是,今天居然我写日记,不公平呀!乌龟的朋友,哦,boyfriend家的fish很好吃,我们寝室都吃了,不知道她男朋友知道会不会骂♂。那个兔子好懒,说什么写的和我一样多,哪有,她比我写的少一行,她好狡猾,我们得好好调教她,居然叫我“报时器”,我看是她活得不耐烦了,如果是伟她的boyfriend等电话,那就情有可原了。全寝室就差蚊子了,不知道她又去哪里甜蜜去了,哎呀,幸福的人呀,那是“我们”(除掉乌龟、斑马and frog)享受不到的,不过,“我们”才不羡慕,对吧!兔子,大虾,雕,如果你们忍痛,是不是该给我点奖励呢?多多益善哦! 我还犯了一个错误,差点拿剪刀当钢笔:(
签名:盼盼
2003年 3月4日 星期二 阴雨
临近十点钟,我回到寝室第一个看到的是杜鹃,杜鹃坐在床上看《流金岁月》,看来今天她心情不错。她还喜笑颜开地跟我讲“李伟今天死定地要买东西她吃……买十颗糖,他吃一颗,其他的都让我吃,我说不行,如果你不吃我也不吃,一人五颗,那样我就吃”,哎呀,今天她算是格外幸福,看来,朋友之间还是真诚吵吵,方才体会到甜蜜。 盼盼坐在床上和同样也坐在床上的方如君谈判起来了,盼盼说:“兔子,我们来练唱歌吧。”方如君表情怪异,撇着嘴巴,似乎不感兴趣,盼盼于是接着说:“
唱唱歌吧,可以燃烧点脂肪呀,快下来呀!”“没兴趣”,兔子仍稳坐泰山——不! 肖鹏今儿睡的好早啊,好像在听收音机,刘弦也躺在床上,今天可没吃东西,现在还在搞学习,只听见鼠标喳喳的响,吴早霞今天大多在床上度过,别误会,她可不是懒,她很勤快的,只是今儿身体不舒服,阑尾炎,从我回来就看到她睡在床上,后来走到她身旁恰好遇到她伸出头探望下面,于是我就问她:“好点了吗?”她说没事,听了好心痛,真可怜,我也不知道怎么样才能让她肚子不疼。
快十点四十分的时候,肖鹏打电话回家,你知道为什么吗?她是问她爸爸以往得阑尾炎得症状,是啊,寝室里的人都很关心她,怕她手不了,出事。听肖鹏妈妈说,阑尾炎不能揉肚子,最好去医院。
盼盼,兔子,肖鹏都匆忙地穿上衣服,拿着收集的钱。杜鹃最慷慨了,拿出了所有的财——银行卡都给她们了,杜鹃她真的很善良,我已感动了。每次寝室朋友遇难,她都是挺身而出,满实际,我什么都帮不上忙,真惭愧,希望吴早霞谅解,武汉我不熟悉,有我也帮不上忙,去了只会浪费钱,希望她能早日康复,回到我们快乐动物园之家,和我们一起开开心心,快快乐乐地生活。
斑马(记)
2003年 3月12日
又要记一次流水帐了。今晚肖鹏泡了好长时间的电话粥,现在寝室有7个人,当然有两个是引进外资,此时,正目不转睛地盯着电脑看碟,这让我又想起了中午看鬼片,正看到恐怖处,突然出现了两只大白眼,还好,这不是在晚上看到,要不然心一定要吓得跳出来。原来这是杜鹃涂在脸上得面膜,如果是晚上,我想不是我们吓死,那就是她被我们当鬼打死,不知道她今天晚上是否又会讲梦话,这可是她的专利了。 不知道为什么盼盼今天一去不复返了,可能是被感情冲昏了头,不知道白天黑夜,明天可要好好地审问一番。
那个大虾地床还是控地,好长时间没有见她,还是很想念她的,也不知道她现在好的怎么样了,估计星期一回吧!虽然寝室8个人比较多,可还是少她不行。
至于斑马呢?背包出去了,这可是她不回来的标志,这个家伙也不知道注意一下身体,经常泡吧!也难怪她长不胖,看来这倒是保持体重的有效方法,不过,其他动物最好不要采用这种方法减肥。
到此时,还只有兔子一个人睡了,她这个家伙,现在变乖了好多,看来我要向她学习学习了,可不能再这么懒散下去了,所以决定明早去练功房,就请其他动物监督。
大雕今天可不同了,不能上床看书,因为有两位贵宾正再她床上坐着看碟,真是可怜兮兮!不过青蛙也很可怜,现在还在等着看我的日记,不好意思,久等了。
Bye-bye,各位晚安!
签名:蚊子
2003年 3月13日 星期四 雨
不好意思,各位,我差点就忘了写今天的寝室日记,其实这样的日记也近乎于流水帐了,不过大家也看得津津乐道。 现在盼盼正在和她的文人网友煲电话粥,这妮子是够行的,刚刚跟她的黑马王子讲完,现在又……看来今年桃花运都往她那奔,懒得说她了,一点形象都不顾,穿着秋衣秋裤,叉个腰,唉,太难看了,难看死了,也只有我还支持得住:)
乌龟太黑人了,又傻笑了几声,这个乌龟啊,每天都是奇事不断,昨天早晨说梦话,中午像鬼一样抹面膜,不明白,谈恋爱的人是不是都像她这样?太可怕了啊!
蚊子、斑马都没待在寝室里,这二位是咱寝室比较不乖的,大伙可别学啊,不健康的生活。
大虾还在家中养病吧,全寝室的人都在想念她,这几天我和盼盼、兔子在商量一件大事,不能说,等大虾再来落实,应该会受到大虾好评,嘿嘿!
好了,我今天好累,就写到这里,兔子又早早睡去了,睡仙:)
签名:杰罗
2003年 3月16日 星期日 小雨
大病初愈,我终于回到了思念了近两个星期的寝室了,尽管寝室里到处堆满了东西,桌子不怎么整齐,地上也不怎么干净,但总有一种说不出的亲切感。兔子、斑马和蚊子在寝室,难得星期日的下午三位都留在寝室,要是以前,早出去潇洒了。说说笑笑,看来心情都还不错。约莫六点钟左右,我们亲爱的乌龟小姐回来了,我们进行了一个长达10秒钟的热烈拥抱。当兔子告诉她,她的伟哥哥曾多次打电话来关系地询问其行踪时,她又一溜烟地冲了出去,看来,她BF地魅力非同反响。可怜我刚刚到校还没来得及好好休息一下,就得赶去上晚自习。下礼物晚自习回到寝室时,还不见盼盼,看来今天晚上是看不到她了,听说她最近艳运不断,我们的喜糖又有望了! 我看完《夕阳天使》,接下写寝室日记这一艰巨的任务时,其实已是近11点钟了,蚊子、斑马已经睡着了,只有我、兔子、青蛙、雕和乌龟还精神十足,没有睡意,也不知是谁提议玩“一只青蛙跳下水……”可是青蛙拒不参加,就只好♂几个玩了,但是总是不小心说错了,笑得我们肚子都疼了。边笑边说,说到三只青蛙时就坚持不下去了,挺有意思。
好了,就写这么多吧,兔子受不了我超慢字速,已经抗议好几次了。
签名:虾
2003年 3月17日 星期一 小雨
唉!已经躺下去了!可还得再爬起来写日记,呜……好可怜哦!这简直是世界上最痛苦的事啦!哦!今天去了民院,回来全身都湿了,打伞跟没打伞简直没什么两样。回到寝室后,发现大家都在,而且还多了一个,我想这一定湿今天开学以来,寝室人数最多的一晚上啦!而且今天老龟还去租了《谈判专家》,现在龟&雕都在忙着拷碟呢!哈哈……终于拷好了,我们开始看起来了,可不知什么时候,那个盼盼居然睡着了,看来我“睡神”的称号得流动到她那儿去了,今天蚊子是最早一个睡的,到现在为止,只有龟一个人看得津津有味,看来得给她一个称号“看神”,可以吧?不过今天的重点人物可不是她,那今天的重点任务是谁呢?告诉你吧,那就是我们可爱的青蛙小姐,她呀,可幸福了,天天泡电话粥,泡完了还不算完,还得到床上去对这信纸一吐相思之情呀!难怪别人都说恋爱钟得女人最美丽,你看,她现在是面带桃花啊! 好了,流水帐记到这儿为止!哦哦!最后说一句,不知道最近大家为什么都便秘,不过不要紧,我有药啊!各位室友,有便秘的来找我哦!我有药哦!哦哦!886
签名:兔子
2003年 3月18日 星期二 小雨转阴
唉!好累啊!今天听了吴天祥的报告,回来就好累,真想睡觉!可是寝室日记还是得写。看看上面得哪知扎蝴蝶结得小兔,一看就知道心情特别好,几天年却显得沉默得多,可能……可能快……快走……走什么呀?当然是桃花运了。我们的虾子小姐自从周日回来后,每天都有不断的电话来关心,幸福呀!有这么多关心你的人再身边,知足吧!知足常乐嘛! 蚊子今晚没回,相比是和谢又和好了吧!愿这对有情人将来情路一帆风顺。寝室除了电脑中放的片子外,就几乎没什么声音了,太安静了。斑马今天回来得蛮早得,可仍是沉默,或许是和我一样tired了吧!熊猫今天最忙得,我见到得就有3个tel,煲这么多粥,小心喝不完呦!现在这只熊猫正在和青蛙讨论,交流各自得感情,都早点睡吧!小心名太难都长熊猫眼,那我们寝室就成熊猫园了。龟今天睡得最早,好像是头疼吧!不要学我感冒呦,我可不负责得。
好了,Bye!Good night!
签名:雕
2003年 3月19日 星期三 阴转晴
唉,日记不得不写哦!今天晚上我开开心心地回到寝室,可是一到门口,便觉得奇怪,怎么门被锁了,而且灯也没开,这是怎么回事呀?咚咚咚,雕把门打开了,“啊——”,我大叫了一声,真把我吓得有点魂飞魄散,忽然感觉一阵阴风吹过,好冷哦!再往里看,个个像个带子似的直立坐着,看来他们为了XX地到来的确花了不少心思,脸上涂得怪模怪样,杰罗两张脸全都涂的白白的,兔子涂的像个小猫,还有胡须呢!斑马呢!形容不出,最后得知是一颗心,盼盼总是引导时尚潮流,一幅白色太阳镜架在鼻梁上,看着看着,我笑得趴在床上了!肚子好疼哦!不幸地是,后来我们七位布置好周密计划准备吓蚊子时,全被那个小兔和雕搞砸了,真是气死我们了!今天就写道这儿吧,各位晚安,乖宝宝先睡了! 签名:龟龟
2003年 4月10日 星期四 阴雨
我们很好看地寝室日记又遭受劫难,被中断了半个月。 今天我要说地,大家一定会很感兴趣地,至于是什么呢,我也不卖关子了。中午在足迹吃饭,盼盼和她的准男朋友一起,我自然不好意思一个人做电灯泡,于是我就拉来特邀嘉宾——我们班班长来&我一块做大功率的发光电器(其实也是他俩的荣幸,有点像证婚人,哈哈)。本来也没什么,可他俩就是不承认谈恋爱,于是那些细节也就显得不正常了,好朋友怎么把碗里的肉挑出来给对方吃,暧昧哟:)两人合伙吃了一晚饭,完全打破好朋友界限,我和她都没这种历史,算了,人家的甜蜜我也不去羡慕了,因为我也蛮幸福的撒,每天都看免费的爱情电影,男主角黎永鹏,女主角盼盼:)
近来寝室的主题就是爱情,兔子小姐如愿以偿地和她们家那口子在一起,这两个人也真够沉得住气,你有情我有意,何必沉默那么久呢,当然现在她谈恋爱(重坠爱河)也是蛮利于我们的,没那么多时间来嘴我们了。
大虾呢,好好努力吧,革命尚未成功,同志仍需努力,不过建议要适可而止地追求,咱女生还是要摆摆家资的,给他点机会来追追你:)
乌龟在用牛奶抹脸,有人问她要那么白干什么,臭美的乌龟!
雕的歌声好特别,唱一会儿就要唉声叹气,有趣。
蚊子可幸福了,她姐打电话来关心,我怎么就没个哥哥或者姐姐呢?
斑马又没在寝室,我的上铺空了好久,不知她何时回归故里。
先写到这里,太久不写,有点手生,轮到谁写了,快接住……
签名:杰罗蛙:)
2003年 4月14日 星期一 晴 唉,现在寝室里面都用牙签做房子送给各自的男朋友,也不知道这样做值不值得,不过我自己也在做,不过如果他把我惹烦了,就不送给他,自己留着。 真是羡慕啊!方如君的男朋友可真是够体贴,还帮她洗衣服补衣服。
今天也没什么特别新闻,也不知道具体写些什么好,只知道现在杰罗&盼盼正准备记数学公式,想专升本,不过对于我这种没上进心的人呢?那就免谈了,马上就要期中考试了,看来还是有些影响的。
截止时刻,吴早霞已经快泡了一个小时的电话粥了。
因为本人今晚心情不是很好,所以日记到此为止。
签名:蚊子:)
February 23 Oracle一家的幸福生活??? 一个名叫Sid的男人,狂热地爱好拍摄、保存和整理照片。Sid的妻子名叫Debbie,他们有三个儿子Logan、Archie和Chuck。 (sid是oracle实例,照片是数据) ??? 他有一所很大的房子,同住的有男管家Simon和女仆Pam。我将介绍他的家庭、他的房子和他的爱好:拍摄、收集和炫耀他的照片。 现在,Sid快乐地生活着—家庭、朋友和假期。他不时地拍照。事实上,他总是随身带着相机。他不想错过任何东西。 每一个早餐、午餐和晚宴,都会被拍下来。当孩子们从学校回家,镜头摄入他们对父亲的问候。当孩子们做家庭作业的时候,“卡嚓、卡嚓、卡嚓”,照相机的快门响个不停。 (照相只是要说明数据库不断从外部接受数据) ??? 棒球赛上,儿子Archie的每一个投掷都被拍摄下来。学校的舞会上,Chuck的父亲拍下了每一个缓慢的舞步以及在苏打水机器旁的交谈。而他的狗——Sadie则是这个世界上被拍摄次数最多的狗。 他和他的家庭收藏了数以百万计的照片。一些照片被销毁掉了,一些则经历了改变。Sid有一套神奇的系统来处理这些照片。 让我来告诉你一些关于他的家庭、他的房子的事,以及关于他和他的家人如何致力于发展、组织和保护所有这些照片。 Sid用一架特殊的相机拍摄。他的相机每拍摄一次,就同时产生一张照片和一张底片。他从来不用换胶卷(这是一种科幻技术)。 照片被贴在房间的墙壁上,而底片则被保存在一个安全的地方。 ? 1.1.1 Sid的家 我介绍过Sid的家了吗?嗯。那里非常的大而且到处都是照片。因为照片实在是太多了,所以Sid必须寻找地方来放置它们。 一些照片被挂在墙上,一些被藏在照相簿内,一些被插入剪贴簿里,一些被放在储藏室的一个盒子里再也不会被撇上一眼。所有这些照片都被放在他家中的某处。(照相簿和剪贴簿应该指的是SGA中的某部分) 他称呼这些放置照片的地方为“相片空间”。一个相片空间可能是一个有着很多墙壁的房间,或者是有着很多页的剪贴簿。一个房间是照片的逻辑存储单元。 (房间是表空间) 房间内的墙壁则是相片空间中保存照片的物理结构。 (墙壁是数据文件,房间是由多个墙壁组成的) ? 1.1. 2 Debbie的家务事 因为Sid忙于拍摄和处理照片,他没有足够的精力来放置和储存它们—那是她妻子的工作。Debbie的全名是Debra Wrider。 (写进程) 她的T恤衫上印着DBWR—这是一种家庭传统—为所有的东西做标签。 把所有这些照片放置得井井有条是一件巨大的工作,不过Sid和Debbie已经建立了一个系统。当Sid拍摄结束后,他把照片都放在厨房里一个特定的架子上。 (架子是DBBUFFER) 那个特定的架子被称为“DB_BUFFER_CACHE”。Debbie时常检查那个架子,看看上面是否有新拍摄的照片。在白天,她把从架子上取回的照片分别放置到对应的相片空间。 (这里没有说明她工作的几个要求,干什么事情都要有激发条件的,不然谁做^_^) ? 事实上,在一场棒球赛或者学校的舞会上,Sid制作照片的速度太快了。以至于Debbie几乎跟不上。这是一个特别的情景,Sid拍摄照片,然后放置在那个特定的架子上, Debbie把这些照片放在剪贴簿里,挂在墙上或者插入照相簿内。 当Sid带着照片和底片进入厨房时,他和他妻子之间可能会有这样的一段对话: (照片是提交后数据,底片为日志数据) Sid:“亲爱的,猜猜我给你带回来了什么?” Debbie:“你又拍了很多照片,是吗?你知道我是多么喜欢整理照片啊。” Sid:“当然了,Archie的棒球队赢得了最后一局。他在第九局以两个二垒打获胜,我全部都拍摄下来了。” Debbie:“太好了,那些照片放在相片空间9号墙壁的运动类里,看上去一定棒极了。我已经等不及去放置它们了,我将邀请所有的邻居来观赏。” Sid:“Archie也非常高兴。OK,我再去拍一些照片,我将在23毫秒后回来。哦,顺便说一下,我刚才将那些照片放在DB_BUFFER_CACHE架子上。 现在Sid的目录号为1332935。”Debbie:“知道了,再见。” 为了易于管理照片、框架和日志,他使用一种特殊的数字来建立目录,称之为Sid的目录编号。他用这些独特的、不断递增的数字来追踪对这所房子内所有照片的操作。(SCN) Sid和Debbie已经对查阅Sid的目录编号感到厌倦。Sid在所有的照片、录像带和底片上都写上“Sid的目录编号”,累得手要抽筋。 最后,他们将“Sid的目录编号”缩略为SCN。记住SCN,Sid的生活都围绕着它展开。 现在,全家人都知道这个数字被简称为SCN。你可以在每一个地方都看到SCN。Sid把它们写在底片上,然后他把低位和高位的SCN写在每一包底片外面。 Debbie把它们写在她放置照片的地方。这些数字在Sid的复杂家庭生活中显得尤为重要。 Debbie把照片放在框架里,房子里的每一个框架都是一样大小。一些框架只保存了一张照片,一些框架则保存了几张照片。一些特别大的照片则要占据多个框架。(框架是segment) Sid有一张和原物同样大小的照片,拍摄的是他在跳伞后在空中做造型动作,这张照片占据了两个框架。当照片跨越框架时,Sid称之为照片链。 任何时候,只要Debbie改变框架内的照片,她就把照片的SCN写在框架上。房子内的每张照片都有一个惟一的数字。他们称照片的这个数字为照片ID。 (rowid) 这些照片ID帮助Sid管理家中所有墙上的照片。如果他需要尽快找到一张照片,他通过照片ID来实现。通过照片ID,Sid能够迅速找到墙壁和保存照片的框架。 有时候,Sid会对照片进行一些改变。如果他要编辑一张照片,那么他让Debbie通过照片ID找到他的照片。Debbie给他一份包括所需照片的整个框架的拷贝。(数据更改前映像) 为防止Sid可能改变主意,不喜欢他已经做的一些改变,他把那份拷贝放在一个储藏室内,称之为“回退”储藏室。当Sid完成了更改并且确定了以后,Debbie把更改后的照片放回到墙壁上原先的地方,并且把新的SCN写在框架上。如果Sid不喜欢他的更改,他们就从“回退”储藏室里取出框架并且把它放回墙上。(储藏室是回滚段或者UNDO空间) 这样一来,照片就和Sid没有改动以前一样。 ? 1.1.3 Logan的家务事 Debbie为了处理Sid的照片已经忙得焦头烂额,所以她没有足够的时间和精力来处理Sid的底片。Sid认为他的儿子们在玩电子游戏和吃土豆片上浪费了太多的时间,他决定给他们安排任务。 Sid的大儿子叫做Logan William Randolph。Logan的衬衫上都印着LGWR。Logan有一个非常简单但是非常重要的任务。 (LGWR日志写进程) Sid的相机产生照片的同时产生了底片,Logan的任务是将底片放入笔记本里。家中的厨房里竖着另一个架子,架子上Sid贴了一个标签,用来标记那些发送给大儿子的底片。 标签被称为“LOG_BUFFER”,Logan坐在厨房的桌子旁,观察这个特殊的LOG_BUFFER架子。当有底片被放到这个架子上时,他立即把底片放在笔记本里。 (又一个架是logbuffer) Sid称这些笔记本为“重做笔记本”。他使用这个名称是因为以后他可以用里面的底片重新制作一张照片。Logan检查他的架子的频率要比Debbie的高得多。 (lgwr要比dbwr更频繁,不过还是没指名lgwr工作的触发条件) Sid表示,只要有底片,他就可以随时重新制作一份照片。Logan和他父亲的对话简短而温馨: Sid:“Logan,底片在架子上了!” Logan:“知道了,我会立即处理的。” Logan按照他得到底片的顺序将这些底片放入“重做笔记本”。一旦当前的笔记本装满了底片,他就换另一个新的笔记本。每一次他更换“重做笔记本”后, 都在“告警笔记本”上做一个记录,然后通知他的兄弟来进行他们的家务活。 (日志切换点log switch,发生的时候会记录到告警日志中) 悲哀,是不是?老爸和他的大儿子太专注于底片了! 顺便提到,不是所有的照片都有对应的底片保存。Sid指定某些照片不必被做日志,这些照片就没有对应的底片保存在“重做笔记本”里。Sid称其为“无日志”。 (nolog的情况) ? 1.1.4 Archie的家务事 Sid的第二个儿子Archibald则另有任务。他的衬衫上写着ARCH。Archie从重做笔记本里提取底片后,把它们放置在一个安全的地方。 (ARCHIVE归档进程) 当Archie发现Logan更换到另一个重做笔记本的时候,他就开始行动。他拿起重做笔记本中的那些底片,把它们放在房子外面的一个安全地方。 Sid在后院建造了一个控制好温湿的地下室。那是他保存底片的安全地点。如果暴风来到,破坏了房子,他可以使用保存在地下室的底片来替换那些被损坏的照片。 每一份Logan的“重做笔记本”拷贝,被称为“归档的重做笔记本”。 可能某一天,你可以听到Sid和他次子之间的笑谈: Sid::“Archie?你在哪里?” Archie:“老爸,我在自己房间里,Logan现在切换笔记本了吗?” Sid:“是的,他刚刚干完。” Archie:“知道了,老爸。我会复制这些底片并且制作一个新的笔记本放在地下室里。然后,我会更新‘控制笔记本’,并且在‘告警笔记本’里写一个记录。” Sid:“你是一个好孩子,我知道可以相信你。” (archive完成后会更新控制文件,并记入到告警日志中) ? 1.1.5 Chuck的家务事 Sid最小的儿子Chuck确保每一件事情都是同步的。他的全名是Charles Kenneth Patrick Thomas。他衬衫上的字母是CKPT, (CKPT检查点进程) Chuck的任务是将每一面墙壁、相片册和剪贴簿上的相片数字和控制笔记本中的数字协调起来。当他得到信号,他就开始在房子里奔跑起来,在每一面墙壁上写下数字, 同时将同样的数字也记录在控制笔记本里。这使得每一件事情都协调起来。以下是一段Sid和Chuck的对话: Sid:“hi,Chuck。” Chuck:“老爸,Logan又开始更换笔记本了吗?” Sid:“是的,儿子,你知道规矩。带着目录编号1332935跑遍房间,把这个数字写到每一面墙壁的开头,每一个剪贴簿的封面和照片盒子的顶部。” Chuck:“没问题,给我几微秒时间搞定”。 Sid:“别忘记把1332935写在控制笔记本上!” Chuck:“老爸,我知道了,同样的事我一天内已经做过几百次了。” 我敢打赌,你已经对阅读这个疯狂的Sid之家感到厌倦,是不是?到现在为止,你可能已经看出Sid有一个功能失常的家庭。功能失常?是的!不过却有效而可靠。Sid喜欢这样。 (检查点进程负责更新控制文件、数据文件的头信息,以使其保持一致) ? 1.1.6控制笔记本 这个家庭怎样维持所有这些活动?Sid、Debbie、Logan、Archie和Chuck断定,要想把这项照片收集和整理工作处理的井井有条,必须要制作很多详细的笔记。 他们每个人都把自己的东西写入一个日志,称之为“控制笔记本”。 (控制文件) 控制笔记本有一个记录整个房子中每一堵墙壁、剪贴簿和照片盒子的家庭财产清单条目。如果Sid增加了一个房间,来放置更多的照片,他在控制笔记本里做一个记录。 当Logan更换重做笔记本时,他在控制笔记本里做摘要记录。每次Archie制作一个归档的重做笔记本放入地下室时,他把名字和SCN的范围写在控制笔记本里。 每次Chuck在房子里跑完一圈,他把细节也记录在控制笔记本里。这特殊的笔记本动态保存了所有地方的资产情况,和房子里以数字表示的进程。 (控制文件中包含数据库名、数据文件、日志文件、归档文件,检查点及scn等相关信息) ? 1.1.7告警日志 时不时的总会有些意外发生,也可能出现错误。Debbie可能在把照片放进框架的时候发生问题,因为那面墙上已经没有多余的空间了。Archie可能无法折叠起底片条, 因为有人把苏打水喷在上面。Sid考虑到了所有的可能,他买了一个螺旋轴的笔记本,放在厨房的抽屉里。在笔记本的面子上写上“告警日志”。 每一个家庭成员将遇到的问题写在这个日志里。他们不仅要记下发生的错误,还要记下正常的活动过程。 这个告警日志是一个用来保存在Sid家里已经和即将发生的事情的便利场所。如果他们要解释某个情形下额外的细节,他们制作一个新的“追踪笔记本”,把信息记录在里面。 这样一来,这个告警笔记本不会变得比它原先的样子更杂乱。 (告警日志记录实例所有的变动和错误信息) ? 1.1.8清晨程序 全家早晨一起醒来,也总是同时上床睡觉。他们称这些时间为一天的“启动”和“关闭”。下面是早晨的启动程序: Sid从床上起来,端上一杯咖啡开始他的一天。在咖啡壶边上,他将一系列指令保存在笔记本里,称之为初始化参数文件。阅读这列指令帮助他开始新的一天。 啜着咖啡,他走过去确认控制笔记本是否放在老地方。他在房子里打转叫醒Debbie、Chuck、Logan、Archie、Simon和Pamela(你很快就会遇到他们)。 当每一个人都醒来后,就打开控制笔记本。确认根据控制笔记本的内容,所有的照片墙壁都没有问题,当所有的东西都被检查过以后,他打开房子迎接宾客, 并开始拍摄照片。Sid雇佣了一个男管家,Simon,在早晨帮助他,Simon的燕尾服上写着字母SMON(省掉名字里的一个字母)。有些早晨房子里特别凌乱, 因为在前一个就寝时间,家庭的每一个成员都没有及时清理好各自的事物。当Simon就位后,Sid打开窗帘,挂好电话,打开门锁,他的家庭开放,准备好了迎接客人。 (启动后执行必要的系统清理和实例恢复是smon一项主要工作) ? Sid和Simon可能这样开始一天: Simon:“早上好,先生。我今天能帮你做什么吗?” Sid:“是的Simon,早上好。看来我们这里很凌乱啊。我们昨天晚上突然中断了。你能否处理一下所有这些没有完成的照片和‘回退’。” Simon:“当然了,所有这些底片在重做笔记本里的都要被重新制作,然后以简单的顺序放置在正确的框架里。” Sid:“你处理完这些后,接着处理‘回退’,是吗?” Simon:“当然了,先生。我从回退储藏室里取出照片框架,然后把旧的版本放回它们原来的地方。” Sid:“太棒了,你完成后,我就打开房子。” (实例恢复是在数据库open之前完成的) ? 1.1.9夜间程序 Sid家的夜间程序也很有趣。当Sid躺下准备休息的时候,他要求家里所有的东西都放好、关掉和停下。就寝时间的第一个行动是要求房子里所有的客人回家去。 Sid晚上的情绪有四种情况。第一种,有时他很有耐性,等待所有的男女客人自行离去后,再就寝;第二种,Sid没有耐性,他亲自来监督每一个客人, 当客人干完各自的事情(看照片等等)后,他给客人指示出门的方向;第三种,Sid非常没有耐性,他揪着客人陪他们出门,不让他们干完正在干的事; 第四种,Sid拉下房子的电闸,大叫“时间到”,房子里一片狼藉。Simon不得不在第二天早晨来清理混乱的场面。 (数据库关闭的几种情形) 当一个客人走后留下一团糟时,Sid叫来他的女佣PamelaMonica。她监控客人的活动,并且在客人走后处理善后。她的清洁设备上贴着标签PMON。 (PMON用户监控进程,负责对用户资源的清理和回收) 如果Sid没有突然拉闸,每个人都打开灯,进行清理。同步处理笔记本来完成各自的任务。Debbie从DB_BUFFER_CACHE架子上取走所有的照片,放到合适的墙壁上。 Logan确保LOG_BUFFER架子是空的,所有的底片都在当前的重做笔记本里。Chuck在房子里兜一圈,用最高和最低的目录编号(SCN)来更新墙壁和笔记本。 Archie把最后的那张底片放入归档的重做笔记本,放到后院的地下室。然后更新控制和告警笔记本。当每个人都干完后,Sid关灯,大伙儿睡觉。 ? 1.1.10 Sid的问题 有时候,Sid家会出现问题。当以下这些事件中任何一件发生时,房子里的活动就会停止。 当Sid醒来,而他无法找到初始化文件,则无法开始一天的工作。 有时,Sid需要用Archie保存在后院地下室的底片来恢复丢失的照片,如果他丢失了一张底片,或者在恢复过程中底片遭到破坏。 他无法恢复任何在那张无效底片以后拍摄的所有照片。 Sid一次丢失了几张底片。他不小心打开相机,曝光了胶卷。这使得他情绪极坏,他的世界好像走到了尽头。他联系了照相机厂家,他们提供了一个特殊的照相机, 带有两个胶卷舱。如此一来,万一胶卷破坏了,他还可以指望另一个舱内的胶卷。Sid称之为镜像。 (cluster?) 如果后院的地下室满了,Archie没有空间来放置新的底片。Archie只是在地下室里等着新的空间。这使得Sid和Debbie也跟着等待,整个Sid的家庭活动陷于停顿状态。 (hang) 如果Debbie得了流感,卧病在床。你猜猜看会怎么样:Sid疯狂地关上了门。尽管有这些问题和其他一些家庭挫折及功能失常, 但Sid的家庭仍然可以处理百万计的照片和数以千计的同时到访的客人。 ? ?? 和我一样,你可能会想知道,Sid怎么会有那么多的时间和精力来进行照片收集活动。他没有工作吗?事实上,没有。钱不是Sid的目标。他不必工作。 他在几年以前完成了一个聪明的投资。他在1984年以后买入了大量的电脑公司的股票,那时它们刚开始向公众发行。他所投资的公司总部在加利福尼亚州的红树海岸。 站长统计 February 09 delphi中的线程类线程本质上是进程中一段并发运行的代码。一个进程至少有一个线程,即所谓的主线程。同时还可以有多个子线程。当一个进程中用到超过一个线程时,就是所谓的“多线程”。 那么这个所谓的“一段代码”是如何定义的呢?其实就是一个函数或过程(对Delphi而言)。 如果用Windows API来创建线程的话,是通过一个叫做CreateThread的API函数来实现的,它的定义为: HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ); 其各参数如它们的名称所说,分别是:线程属性(用于在NT下进行线程的安全属性设置,在9X下无效),堆栈大小,起始地址,参数,创建标志(用于设置线程创建时的状态),线程ID,最后返回线程Handle。其中的起始地址就是线程函数的入口,直至线程函数结束,线程也就结束了。 整个线程的执行过程如下图所示: 因为CreateThread参数很多,而且是Windows的API,所以在C Runtime Library里提供了一个通用的线程函数(理论上可以在任何支持线程的OS中使用): unsigned long _beginthread(void (_USERENTRY *__start)(void *), unsigned __stksize, void *__arg); Delphi也提供了一个相同功能的类似函数: function BeginThread(SecurityAttributes: Pointer; StackSize: LongWord; ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord; var ThreadId: LongWord): Integer; 这三个函数的功能是基本相同的,它们都是将线程函数中的代码放到一个独立的线程中执行。线程函数与一般函数的最大不同在于,线程函数一启动,这三个线程启动函数就返回了,主线程继续向下执行,而线程函数在一个独立的线程中执行,它要执行多久,什么时候返回,主线程是不管也不知道的。 正常情况下,线程函数返回后,线程就终止了。但也有其它方式: Windows API: VOID ExitThread( DWORD dwExitCode ); C Runtime Library: void _endthread(void); Delphi Runtime Library: procedure EndThread(ExitCode: Integer); 为了记录一些必要的线程数据(状态/属性等),OS会为线程创建一个内部Object,如在Windows中那个Handle便是这个内部Object的Handle,所以在线程结束的时候还应该释放这个Object。 虽然说用API或RTL(Runtime Library)已经可以很方便地进行多线程编程了,但是还是需要进行较多的细节处理,为此Delphi在Classes单元中对线程作了一个较好的封装,这就是VCL的线程类:TThread 使用这个类也很简单,大多数的Delphi书籍都有说,基本用法是:先从TThread派生一个自己的线程类(因为TThread是一个抽象类,不能生成实例),然后是Override抽象方法:Execute(这就是线程函数,也就是在线程中执行的代码部分),如果需要用到可视VCL对象,还需要通过Synchronize过程进行。关于之方面的具体细节,这里不再赘述,请参考相关书籍。 本文接下来要讨论的是TThread类是如何对线程进行封装的,也就是深入研究一下TThread类的实现。因为只是真正地了解了它,才更好地使用它。 下面是DELPHI7中TThread类的声明(本文只讨论在Windows平台下的实现,所以去掉了所有有关Linux平台部分的代码): TThread = class private FHandle: THandle; FThreadID: THandle; FCreateSuspended: Boolean; FTerminated: Boolean; FSuspended: Boolean; FFreeOnTerminate: Boolean; FFinished: Boolean; FReturnValue: Integer; FOnTerminate: TNotifyEvent; FSynchronize: TSynchronizeRecord; FFatalException: TObject; procedure CallOnTerminate; class procedure Synchronize(ASyncRec: PSynchronizeRecord); overload; function GetPriority: TThreadPriority; procedure SetPriority(Value: TThreadPriority); procedure SetSuspended(Value: Boolean); protected procedure CheckThreadError(ErrCode: Integer); overload; procedure CheckThreadError(Success: Boolean); overload; procedure DoTerminate; virtual; procedure Execute; virtual; abstract; procedure Synchronize(Method: TThreadMethod); overload; property ReturnValue: Integer read FReturnValue write FReturnValue; property Terminated: Boolean read FTerminated; public constructor Create(CreateSuspended: Boolean); destructor Destroy; override; procedure AfterConstruction; override; procedure Resume; procedure Suspend; procedure Terminate; function WaitFor: LongWord; class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload; class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod); property FatalException: TObject read FFatalException; property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate; property Handle: THandle read FHandle; property Priority: TThreadPriority read GetPriority write SetPriority; property Suspended: Boolean read FSuspended write SetSuspended; property ThreadID: THandle read FThreadID; property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate; end; TThread类在Delphi的RTL里算是比较简单的类,类成员也不多,类属性都很简单明白,本文将只对几个比较重要的类成员方法和唯一的事件:OnTerminate作详细分析。 首先就是构造函数: constructor TThread.Create(CreateSuspended: Boolean); begin inherited Create; AddThread; FSuspended := CreateSuspended; FCreateSuspended := CreateSuspended; FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID); if FHandle = 0 then raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]); end; 虽然这个构造函数没有多少代码,但却可以算是最重要的一个成员,因为线程就是在这里被创建的。 在通过Inherited调用TObject.Create后,第一句就是调用一个过程:AddThread,其源码如下: procedure AddThread; begin InterlockedIncrement(ThreadCount); end; 同样有一个对应的RemoveThread: procedure RemoveThread; begin InterlockedDecrement(ThreadCount); end; 它们的功能很简单,就是通过增减一个全局变量来统计进程中的线程数。只是这里用于增减变量的并不是常用的Inc/Dec过程,而是用了InterlockedIncrement/InterlockedDecrement这一对过程,它们实现的功能完全一样,都是对变量加一或减一。但它们有一个最大的区别,那就是InterlockedIncrement/InterlockedDecrement是线程安全的。即它们在多线程下能保证执行结果正确,而Inc/Dec不能。或者按操作系统理论中的术语来说,这是一对“原语”操作。 以加一为例来说明二者实现细节上的不同: 一般来说,对内存数据加一的操作分解以后有三个步骤: 1、从内存中读出数据 2、数据加一 3、存入内存 现在假设在一个两个线程的应用中用Inc进行加一操作可能出现的一种情况: 1、线程A从内存中读出数据(假设为3) 2、线程B从内存中读出数据(也是3) 3、线程A对数据加一(现在是4) 4、线程B对数据加一(现在也是4) 5、线程A将数据存入内存(现在内存中的数据是4) 6、线程B也将数据存入内存(现在内存中的数据还是4,但两个线程都对它加了一,应该是5才对,所以这里出现了错误的结果) 而用InterlockIncrement过程则没有这个问题,因为所谓“原语”是一种不可中断的操作,即操作系统能保证在一个“原语”执行完毕前不会进行线程切换。所以在上面那个例子中,只有当线程A执行完将数据存入内存后,线程B才可以开始从中取数并进行加一操作,这样就保证了即使是在多线程情况下,结果也一定会是正确的。 前面那个例子也说明一种“线程访问冲突”的情况,这也就是为什么线程之间需要“同步”(Synchronize),关于这个,在后面说到同步时还会再详细讨论。 说到同步,有一个题外话:加拿大滑铁卢大学的教授李明曾就Synchronize一词在“线程同步”中被译作“同步”提出过异议,个人认为他说的其实很有道理。在中文中“同步”的意思是“同时发生”,而“线程同步”目的就是避免这种“同时发生”的事情。而在英文中,Synchronize的意思有两个:一个是传统意义上的同步(To occur at the same time),另一个是“协调一致”(To operate in unison)。在“线程同步”中的Synchronize一词应该是指后面一种意思,即“保证多个线程在访问同一数据时,保持协调一致,避免出错”。不过像这样译得不准的词在IT业还有很多,既然已经是约定俗成了,本文也将继续沿用,只是在这里说明一下,因为软件开发是一项细致的工作,该弄清楚的,绝不能含糊。 扯远了,回到TThread的构造函数上,接下来最重要就是这句了: FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID); 这里就用到了前面说到的Delphi RTL函数BeginThread,它有很多参数,关键的是第三、四两个参数。第三个参数就是前面说到的线程函数,即在线程中执行的代码部分。第四个参数则是传递给线程函数的参数,在这里就是创建的线程对象(即Self)。其它的参数中,第五个是用于设置线程在创建后即挂起,不立即执行(启动线程的工作是在AfterConstruction中根据CreateSuspended标志来决定的),第六个是返回线程ID。 现在来看TThread的核心:线程函数ThreadProc。有意思的是这个线程类的核心却不是线程的成员,而是一个全局函数(因为BeginThread过程的参数约定只能用全局函数)。下面是它的代码: function ThreadProc(Thread: TThread): Integer; var FreeThread: Boolean; begin try if not Thread.Terminated then try Thread.Execute; except Thread.FFatalException := AcquireExceptionObject; end; finally FreeThread := Thread.FFreeOnTerminate; Result := Thread.FReturnValue; Thread.DoTerminate; Thread.FFinished := True; SignalSyncEvent; if FreeThread then Thread.Free; EndThread(Result); end; end; 虽然也没有多少代码,但却是整个TThread中最重要的部分,因为这段代码是真正在线程中执行的代码。下面对代码作逐行说明: 首先判断线程类的Terminated标志,如果未被标志为终止,则调用线程类的Execute方法执行线程代码,因为TThread是抽象类,Execute方法是抽象方法,所以本质上是执行派生类中的Execute代码。 所以说,Execute就是线程类中的线程函数,所有在Execute中的代码都需要当作线程代码来考虑,如防止访问冲突等。 如果Execute发生异常,则通过AcquireExceptionObject取得异常对象,并存入线程类的FFatalException成员中。 最后是线程结束前做的一些收尾工作。局部变量FreeThread记录了线程类的FreeOnTerminated属性的设置,然后将线程返回值设置为线程类的返回值属性的值。然后执行线程类的DoTerminate方法。 DoTerminate方法的代码如下: procedure TThread.DoTerminate; begin if Assigned(FOnTerminate) then Synchronize(CallOnTerminate); end; 很简单,就是通过Synchronize来调用CallOnTerminate方法,而CallOnTerminate方法的代码如下,就是简单地调用OnTerminate事件: procedure TThread.CallOnTerminate; begin if Assigned(FOnTerminate) then FOnTerminate(Self); end; 因为OnTerminate事件是在Synchronize中执行的,所以本质上它并不是线程代码,而是主线程代码(具体见后面对Synchronize的分析)。 执行完OnTerminate后,将线程类的FFinished标志设置为True。 接下来执行SignalSyncEvent过程,其代码如下: procedure SignalSyncEvent; begin SetEvent(SyncEvent); end; 也很简单,就是设置一下一个全局Event:SyncEvent,关于Event的使用,本文将在后文详述,而SyncEvent的用途将在WaitFor过程中说明。 然后根据FreeThread中保存的FreeOnTerminate设置决定是否释放线程类,在线程类释放时,还有一些些操作,详见接下来的析构函数实现。 最后调用EndThread结束线程,返回线程返回值。 至此,线程完全结束。 说完构造函数,再来看析构函数: destructor TThread.Destroy; begin if (FThreadID <> 0) and not FFinished then begin Terminate; if FCreateSuspended then Resume; WaitFor; end; if FHandle <> 0 then CloseHandle(FHandle); inherited Destroy; FFatalException.Free; RemoveThread; end; 在线程对象被释放前,首先要检查线程是否还在执行中,如果线程还在执行中(线程ID不为0,并且线程结束标志未设置),则调用Terminate过程结束线程。Terminate过程只是简单地设置线程类的Terminated标志,如下面的代码: procedure TThread.Terminate; begin FTerminated := True; end; 所以线程仍然必须继续执行到正常结束后才行,而不是立即终止线程,这一点要注意。 在这里说一点题外话:很多人都问过我,如何才能“立即”终止线程(当然是指用TThread创建的线程)。结果当然是不行!终止线程的唯一办法就是让Execute方法执行完毕,所以一般来说,要让你的线程能够尽快终止,必须在Execute方法中在较短的时间内不断地检查Terminated标志,以便能及时地退出。这是设计线程代码的一个很重要的原则! 当然如果你一定要能“立即”退出线程,那么TThread类不是一个好的选择,因为如果用API强制终止线程的话,最终会导致TThread线程对象不能被正确释放,在对象析构时出现Access Violation。这种情况你只能用API或RTL函数来创建线程。 如果线程处于启动挂起状态,则将线程转入运行状态,然后调用WaitFor进行等待,其功能就是等待到线程结束后才继续向下执行。关于WaitFor的实现,将放到后面说明。 线程结束后,关闭线程Handle(正常线程创建的情况下Handle都是存在的),释放操作系统创建的线程对象。 然后调用TObject.Destroy释放本对象,并释放已经捕获的异常对象,最后调用RemoveThread减小进程的线程数。 其它关于Suspend/Resume及线程优先级设置等方面,不是本文的重点,不再赘述。下面要讨论的是本文的另两个重点:Synchronize和WaitFor。 但是在介绍这两个函数之前,需要先介绍另外两个线程同步技术:事件和临界区。 事件(Event)与Delphi中的事件有所不同。从本质上说,Event其实相当于一个全局的布尔变量。它有两个赋值操作:Set和Reset,相当于把它设置为True或False。而检查它的值是通过WaitFor操作进行。对应在Windows平台上,是三个API函数:SetEvent、ResetEvent、WaitForSingleObject(实现WaitFor功能的API还有几个,这是最简单的一个)。 这三个都是原语,所以Event可以实现一般布尔变量不能实现的在多线程中的应用。Set和Reset的功能前面已经说过了,现在来说一下WaitFor的功能: WaitFor的功能是检查Event的状态是否是Set状态(相当于True),如果是则立即返回,如果不是,则等待它变为Set状态,在等待期间,调用WaitFor的线程处于挂起状态。另外WaitFor有一个参数用于超时设置,如果此参数为0,则不等待,立即返回Event的状态,如果是INFINITE则无限等待,直到Set状态发生,若是一个有限的数值,则等待相应的毫秒数后返回Event的状态。 当Event从Reset状态向Set状态转换时,唤醒其它由于WaitFor这个Event而挂起的线程,这就是它为什么叫Event的原因。所谓“事件”就是指“状态的转换”。通过Event可以在线程间传递这种“状态转换”信息。 当然用一个受保护(见下面的临界区介绍)的布尔变量也能实现类似的功能,只要用一个循环检查此布尔值的代码来代替WaitFor即可。从功能上说完全没有问题,但实际使用中就会发现,这样的等待会占用大量的CPU资源,降低系统性能,影响到别的线程的执行速度,所以是不经济的,有的时候甚至可能会有问题。所以不建议这样用。 临界区(CriticalSection)则是一项共享数据访问保护的技术。它其实也是相当于一个全局的布尔变量。但对它的操作有所不同,它只有两个操作:Enter和Leave,同样可以把它的两个状态当作True和False,分别表示现在是否处于临界区中。这两个操作也是原语,所以它可以用于在多线程应用中保护共享数据,防止访问冲突。 用临界区保护共享数据的方法很简单:在每次要访问共享数据之前调用Enter设置进入临界区标志,然后再操作数据,最后调用Leave离开临界区。它的保护原理是这样的:当一个线程进入临界区后,如果此时另一个线程也要访问这个数据,则它会在调用Enter时,发现已经有线程进入临界区,然后此线程就会被挂起,等待当前在临界区的线程调用Leave离开临界区,当另一个线程完成操作,调用Leave离开后,此线程就会被唤醒,并设置临界区标志,开始操作数据,这样就防止了访问冲突。 以前面那个InterlockedIncrement为例,我们用CriticalSection(Windows API)来实现它: Var InterlockedCrit : TRTLCriticalSection; Procedure InterlockedIncrement( var aValue : Integer ); Begin EnterCriticalSection( InterlockedCrit ); Inc( aValue ); LeaveCriticalSection( InterlockedCrit ); End; 现在再来看前面那个例子: 1.线程A进入临界区(假设数据为3) 2.线程B进入临界区,因为A已经在临界区中,所以B被挂起 3.线程A对数据加一(现在是4) 4.线程A离开临界区,唤醒线程B(现在内存中的数据是4) 5.线程B被唤醒,对数据加一(现在就是5了) 6.线程B离开临界区,现在的数据就是正确的了。 临界区就是这样保护共享数据的访问。 关于临界区的使用,有一点要注意:即数据访问时的异常情况处理。因为如果在数据操作时发生异常,将导致Leave操作没有被执行,结果将使本应被唤醒的线程未被唤醒,可能造成程序的没有响应。所以一般来说,如下面这样使用临界区才是正确的做法: EnterCriticalSection Try // 操作临界区数据 Finally LeaveCriticalSection End; 最后要说明的是,Event和CriticalSection都是操作系统资源,使用前都需要创建,使用完后也同样需要释放。如TThread类用到的一个全局Event:SyncEvent和全局CriticalSection:TheadLock,都是在InitThreadSynchronization和DoneThreadSynchronization中进行创建和释放的,而它们则是在Classes单元的Initialization和Finalization中被调用的。 由于在TThread中都是用API来操作Event和CriticalSection的,所以前面都是以API为例,其实Delphi已经提供了对它们的封装,在SyncObjs单元中,分别是TEvent类和TCriticalSection类。用法也与前面用API的方法相差无几。因为TEvent的构造函数参数过多,为了简单起见,Delphi还提供了一个用默认参数初始化的Event类:TSimpleEvent。 顺便再介绍一下另一个用于线程同步的类:TMultiReadExclusiveWriteSynchronizer,它是在SysUtils单元中定义的。据我所知,这是Delphi RTL中定义的最长的一个类名,还好它有一个短的别名:TMREWSync。至于它的用处,我想光看名字就可以知道了,我也就不多说了。 有了前面对Event和CriticalSection的准备知识,可以正式开始讨论Synchronize和WaitFor了。 我们知道,Synchronize是通过将部分代码放到主线程中执行来实现线程同步的,因为在一个进程中,只有一个主线程。先来看看Synchronize的实现: procedure TThread.Synchronize(Method: TThreadMethod); begin FSynchronize.FThread := Self; FSynchronize.FSynchronizeException := nil; FSynchronize.FMethod := Method; Synchronize(@FSynchronize); end; 其中FSynchronize是一个记录类型: PSynchronizeRecord = ^TSynchronizeRecord; TSynchronizeRecord = record FThread: TObject; FMethod: TThreadMethod; FSynchronizeException: TObject; end; 用于进行线程和主线程之间进行数据交换,包括传入线程类对象,同步方法及发生的异常。 在Synchronize中调用了它的一个重载版本,而且这个重载版本比较特别,它是一个“类方法”。所谓类方法,是一种特殊的类成员方法,它的调用并不需要创建类实例,而是像构造函数那样,通过类名调用。之所以会用类方法来实现它,是因为为了可以在线程对象没有创建时也能调用它。不过实际中是用它的另一个重载版本(也是类方法)和另一个类方法StaticSynchronize。下面是这个Synchronize的代码: class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord); var SyncProc: TSyncProc; begin if GetCurrentThreadID = MainThreadID then ASyncRec.FMethod else begin SyncProc.Signal := CreateEvent(nil, True, False, nil); try EnterCriticalSection(ThreadLock); try if SyncList = nil then SyncList := TList.Create; SyncProc.SyncRec := ASyncRec; SyncList.Add(@SyncProc); SignalSyncEvent; if Assigned(WakeMainThread) then WakeMainThread(SyncProc.SyncRec.FThread); LeaveCriticalSection(ThreadLock); try WaitForSingleObject(SyncProc.Signal, INFINITE); finally EnterCriticalSection(ThreadLock); end; finally LeaveCriticalSection(ThreadLock); end; finally CloseHandle(SyncProc.Signal); end; if Assigned(ASyncRec.FSynchronizeException) then raise ASyncRec.FSynchronizeException; end; end; 这段代码略多一些,不过也不算太复杂。 首先是判断当前线程是否是主线程,如果是,则简单地执行同步方法后返回。 如果不是主线程,则准备开始同步过程。 通过局部变量SyncProc记录线程交换数据(参数)和一个Event Handle,其记录结构如下: TSyncProc = record SyncRec: PSynchronizeRecord; Signal: THandle; end; 然后创建一个Event,接着进入临界区(通过全局变量ThreadLock进行,因为同时只能有一个线程进入Synchronize状态,所以可以用全局变量记录),然后就是把这个记录数据存入SyncList这个列表中(如果这个列表不存在的话,则创建它)。可见ThreadLock这个临界区就是为了保护对SyncList的访问,这一点在后面介绍CheckSynchronize时会再次看到。 再接下就是调用SignalSyncEvent,其代码在前面介绍TThread的构造函数时已经介绍过了,它的功能就是简单地将SyncEvent作一个Set的操作。关于这个SyncEvent的用途,将在后面介绍WaitFor时再详述。 接下来就是最主要的部分了:调用WakeMainThread事件进行同步操作。WakeMainThread是一个TNotifyEvent类型的全局事件。这里之所以要用事件进行处理,是因为Synchronize方法本质上是通过消息,将需要同步的过程放到主线程中执行,如果在一些没有消息循环的应用中(如Console或DLL)是无法使用的,所以要使用这个事件进行处理。 而响应这个事件的是Application对象,下面两个方法分别用于设置和清空WakeMainThread事件的响应(来自Forms单元): procedure TApplication.HookSynchronizeWakeup; begin Classes.WakeMainThread := WakeMainThread; end; procedure TApplication.UnhookSynchronizeWakeup; begin Classes.WakeMainThread := nil; end; 上面两个方法分别是在TApplication类的构造函数和析构函数中被调用。 这就是在Application对象中WakeMainThread事件响应的代码,消息就是在这里被发出的,它利用了一个空消息来实现: procedure TApplication.WakeMainThread(Sender: TObject); begin PostMessage(Handle, WM_NULL, 0, 0); end; 而这个消息的响应也是在Application对象中,见下面的代码(删除无关的部分): procedure TApplication.WndProc(var Message: TMessage); … begin try … with Message do case Msg of … WM_NULL: CheckSynchronize; … except HandleException(Self); end; end; 其中的CheckSynchronize也是定义在Classes单元中的,由于它比较复杂,暂时不详细说明,只要知道它是具体处理Synchronize功能的部分就好,现在继续分析Synchronize的代码。 在执行完WakeMainThread事件后,就退出临界区,然后调用WaitForSingleObject开始等待在进入临界区前创建的那个Event。这个Event的功能是等待这个同步方法的执行结束,关于这点,在后面分析CheckSynchronize时会再说明。 注意在WaitForSingleObject之后又重新进入临界区,但没有做任何事就退出了,似乎没有意义,但这是必须的! 因为临界区的Enter和Leave必须严格的一一对应。那么是否可以改成这样呢: if Assigned(WakeMainThread) then WakeMainThread(SyncProc.SyncRec.FThread); WaitForSingleObject(SyncProc.Signal, INFINITE); finally LeaveCriticalSection(ThreadLock); end; 上面的代码和原来的代码最大的区别在于把WaitForSingleObject也纳入临界区的限制中了。看上去没什么影响,还使代码大大简化了,但真的可以吗? 事实上是不行! 因为我们知道,在Enter临界区后,如果别的线程要再进入,则会被挂起。而WaitFor方法则会挂起当前线程,直到等待别的线程SetEvent后才会被唤醒。如果改成上面那样的代码的话,如果那个SetEvent的线程也需要进入临界区的话,死锁(Deadlock)就发生了(关于死锁的理论,请自行参考操作系统原理方面的资料)。 死锁是线程同步中最需要注意的方面之一! 最后释放开始时创建的Event,如果被同步的方法返回异常的话,还会在这里再次抛出异常。 回到前面CheckSynchronize,见下面的代码: function CheckSynchronize(Timeout: Integer = 0): Boolean; var SyncProc: PSyncProc; LocalSyncList: TList; begin if GetCurrentThreadID <> MainThreadID then raise EThread.CreateResFmt(@SCheckSynchronizeError, [GetCurrentThreadID]); if Timeout > 0 then WaitForSyncEvent(Timeout) else ResetSyncEvent; LocalSyncList := nil; EnterCriticalSection(ThreadLock); try Integer(LocalSyncList) := InterlockedExchange(Integer(SyncList), Integer(LocalSyncList)); try Result := (LocalSyncList <> nil) and (LocalSyncList.Count > 0); if Result then begin while LocalSyncList.Count > 0 do begin SyncProc := LocalSyncList[0]; LocalSyncList.Delete(0); LeaveCriticalSection(ThreadLock); try try SyncProc.SyncRec.FMethod; except SyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject; end; finally EnterCriticalSection(ThreadLock); end; SetEvent(SyncProc.signal); end; end; finally LocalSyncList.Free; end; finally LeaveCriticalSection(ThreadLock); end; end; 首先,这个方法必须在主线程中被调用(如前面通过消息传递到主线程),否则就抛出异常。 接下来调用ResetSyncEvent(它与前面SetSyncEvent对应的,之所以不考虑WaitForSyncEvent的情况,是因为只有在Linux版下才会调用带参数的CheckSynchronize,Windows版下都是调用默认参数0的CheckSynchronize)。 现在可以看出SyncList的用途了:它是用于记录所有未被执行的同步方法的。因为主线程只有一个,而子线程可能有很多个,当多个子线程同时调用同步方法时,主线程可能一时无法处理,所以需要一个列表来记录它们。 在这里用一个局部变量LocalSyncList来交换SyncList,这里用的也是一个原语:InterlockedExchange。同样,这里也是用临界区将对SyncList的访问保护起来。 只要LocalSyncList不为空,则通过一个循环来依次处理累积的所有同步方法调用。最后把处理完的LocalSyncList释放掉,退出临界区。 再来看对同步方法的处理:首先是从列表中移出(取出并从列表中删除)第一个同步方法调用数据。然后退出临界区(原因当然也是为了防止死锁)。 接着就是真正的调用同步方法了。 如果同步方法中出现异常,将被捕获后存入同步方法数据记录中。 重新进入临界区后,调用SetEvent通知调用线程,同步方法执行完成了(详见前面Synchronize中的WaitForSingleObject调用)。 至此,整个Synchronize的实现介绍完成。 最后来说一下WaitFor,它的功能就是等待线程执行结束。其代码如下: function TThread.WaitFor: LongWord; var H: array[0..1] of THandle; WaitResult: Cardinal; Msg: TMsg; begin H[0] := FHandle; if GetCurrentThreadID = MainThreadID then begin WaitResult := 0; H[1] := SyncEvent; repeat { This prevents a potential deadlock if the background thread does a SendMessage to the foreground thread } if WaitResult = WAIT_OBJECT_0 + 2 then PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE); WaitResult := MsgWaitForMultipleObjects(2, H, False, 1000, QS_SENDMESSAGE); CheckThreadError(WaitResult <> WAIT_FAILED); if WaitResult = WAIT_OBJECT_0 + 1 then CheckSynchronize; until WaitResult = WAIT_OBJECT_0; end else WaitForSingleObject(H[0], INFINITE); CheckThreadError(GetExitCodeThread(H[0], Result)); end; 如果不是在主线程中执行WaitFor的话,很简单,只要调用WaitForSingleObject等待此线程的Handle为Signaled状态即可。 如果是在主线程中执行WaitFor则比较麻烦。首先要在Handle数组中增加一个SyncEvent,然后循环等待,直到线程结束(即MsgWaitForMultipleObjects返回WAIT_OBJECT_0,详见MSDN中关于此API的说明)。 在循环等待中作如下处理:如果有消息发生,则通过PeekMessage取出此消息(但并不把它从消息循环中移除),然后调用MsgWaitForMultipleObjects来等待线程Handle或SyncEvent出现Signaled状态,同时监听消息(QS_SENDMESSAGE参数,详见MSDN中关于此API的说明)。可以把此API当作一个可以同时等待多个Handle的WaitForSingleObject。如果是SyncEvent被SetEvent(返回WAIT_OBJECT_0 + 1),则调用CheckSynchronize处理同步方法。 为什么在主线程中调用WaitFor必须用MsgWaitForMultipleObjects,而不能用WaitForSingleObject等待线程结束呢?因为防止死锁。由于在线程函数Execute中可能调用Synchronize处理同步方法,而同步方法是在主线程中执行的,如果用WaitForSingleObject等待的话,则主线程在这里被挂起,同步方法无法执行,导致线程也被挂起,于是发生死锁。 而改用WaitForMultipleObjects则没有这个问题。首先,它的第三个参数为False,表示只要线程Handle或SyncEvent中只要有一个Signaled即可使主线程被唤醒,至于加上QS_SENDMESSAGE是因为Synchronize是通过消息传到主线程来的,所以还要防止消息被阻塞。这样,当线程中调用Synchronize时,主线程就会被唤醒并处理同步调用,在调用完成后继续进入挂起等待状态,直到线程结束。 至此,对线程类TThread的分析可以告一个段落了,对前面的分析作一个总结: 1、线程类的线程必须按正常的方式结束,即Execute执行结束,所以在其中的代码中必须在适当的地方加入足够多的对Terminated标志的判断,并及时退出。如果必须要“立即”退出,则不能使用线程类,而要改用API或RTL函数。 2、对可视VCL的访问要放在Synchronize中,通过消息传递到主线程中,由主线程处理。 3、线程共享数据的访问应该用临界区进行保护(当然用Synchronize也行)。 4、线程通信可以采用Event进行(当然也可以用Suspend/Resume)。 5、当在多线程应用中使用多种线程同步方式时,一定要小心防止出现死锁。 6、等待线程结束要用WaitFor方法。 January 12 Delphi 7.0常用函数速查手册函数由一句或多句代码组成,可以实现某个特定的功能。使用函数可以使代码更加易读、易懂,加快编程速度及减少重复代码。过程与函数类似,过程与函数最重要的区别在于,过程没有返回值,而函数能有返回值。
在Delphi 7.0中,已为我们定义好了非常多的函数,大致分类有6种:数据类型转换函数、字符串、数组操作函数、文件、磁盘操作函数、内存、指针操作函数、数学运算函数、日期函数。 在Delphi中调用函数,一般情况下可以直接使用函数即可,但由于有一些函数未包含在Uses中列出的单元中(默认单元有Windows,Messages,SysUtils,Variants,Classes,Graphics, Controls,Forms,Dialogs;),所以需要我们手动添加单元。比如,MidStr函数就未包含在这些单元中,MidStr所属单元在StrUtils中,因此我们将StrUtils添加Uses中即可。 在本手册中,凡是未包含在默认列出的单元中的函数,都标明了其所属的单元,使用时应当注意。 一、数据类型转换函数 1.FloatToStr 参考实例: Edit1.Text := FloatToStr(1.981); 2.IntToStr 参考实例: S := IntToStr(10);(注:S为String类型变量。) 3.IntToHex 参考实例: Edit1.Text := IntToHex('100', 2); 执行结果,Edit1.Text等于64。 注意:Delphi没有提供专门的“十六进制”转换为“十进制”的函数。使用StrToInt函数可以实现这个功能。具体代码是:I := StrToInt('S\' + '64'); 这时I等于100。加上一个'S\'即可将“十六进制”转换为“十进制”。 4.StrToInt 参考实例: I := StrToInt('100'); 注意:不能转换如 StrToInt('ab')或StrToInt('好')这样的类型,因为他们并不存在数字型。 5.StrToFloat 参考实例: N := StrToFloat(Edit1.Text); 注意:Edit1.Text中的内容为1.981(凡在Edit控件中显示的文本均为字符串)。N为Double类型,用于保存转换后的浮点型数据。 二、字符串、数组操作函数 1.Copy 参考实例: var S: String; MyStr: String; // 保存新的字符串 begin S := 'I Love China!'; //下面将获取I Love China中的“Love”字符串。 MyStr := Copy(S, 3, 4); end; 执行结果,MyStr等于“Love”,“Love”字符串在“I Love China!”中第3个位置开始,所以第二个参数为3,“Love”一共有4个字符,所以第三个参数为4。 2.Concat 参考实例: var S1, S2: String; begin S1 := Concat('A', 'B'); // 连接两个字符串,S1变量等于AB。 S2 := Concat('Borland', ' Delphi', ' 7.0'); // 连接三个字符,S2变量等于Borland Delphi 7.0。 end; 3.Delete 参考实例: var S: String; begin S := 'I Like Reading CPCW.'; // 下面的代码将删除S变量中的“C”字符。 Delete(S, 16, 1); end; 此时S变量则是I Like Reading PCW.(“C”已经不存在了)。 4.High 参考实例: var arrText: array[0..9] of Char; i: Integer; begin i := High(arrText); // i的值则为9 end; 5.Insert 参考实例: var S: String; begin S := 'Wat is your name?'; // 上面句子中的What单词查一个“h”字符,下面使用Insert函数将h添加进去。 Insert('h', S, 2); // 将“h”从第2位处插入。 end; 6.LeftStr(所在单元:StrUtils) 参考实例: var S, A: String; begin S := 'MSN Messenger'; A := LeftStr(S, 3); // 从最左边开始,获取左边的三个字符。因此A变量则等于MSN。 end; 7.Length 参考实例: var nLen1, nLen2: Integer; // 用于保存字符串长度 begin nLen1 := Length('CPCW'); nLen2 := Length('电脑报'); end; 执行结果,nLen1等于4,nLen2等于6。由于一个汉字相当于两个字符的长度,所以3个汉字的长度为6。 8.Low 参考实例: var arrText: array[1..9] of Char; i: Integer; begin i:= High(arrText); // i的值则为1 end; 9.LowerCase 参考实例: var S, A: String; begin S := 'ABC'; A := UpperCase(S); // 经过UpperCase函数转换后,A则等于abc。 end; 10.MidStr(所在单元:StrUtils) Copy函数与此函数类似。MidStr主要用于处理含有中文字符的字符串。 参考实例: var S: String; H: String; begin S := MidStr('China', 1, 2); // S变量为Ch H := MidStr('电脑报', 1, 1); // H变量为“电”。如果使用Copy函数,则应是H := Copy('电脑报, 1, 2),否则返回的将不是“电”字。因此在使用操作含有中文的字符串时,最好使用MidStr。 end; 11.Pos 参考实例: var nPos: Integer; // 用于保存查找的字符所在位置 begin nPos := Pos('Like', 'I Like Reading!'); end; 此时nPos等于3。如果没有查找到,则nPos为0。 注意:Pos函数在查找时是要区分字符大小的。如果要实现不区分大小,那么需要使用UpperCase或LowerCase函数将两个参数的字符(串)转换为“大写”或“小写”再进行查找。 另外还有一个查找字符(串)的函数----AnsiPos,该函数的使用方法与Pos函数完全一样。当你查找的是汉字时,最好使用AnsiPos函数。 12.RightStr(所在单元:StrUtils) 参考实例: var S, A: String; begin S := 'MSN Messenger'; A := RightStr(S, 3); // 从最右边开始,获取右边的三个字符。因此A变量则等于ger。 end; 13.SetLength 参考实例: var S: String; arrText: array of Char; // 定义一个动态数组 begin SetLength(S, 10); // 当设置后,S变量只能赋值长度为10的字符串。 SetLength(arrText, 10); // 只有使用SetLength为动态数组分配内存空间后才能使用动态数组。这句代码的作用相当于arrText: array[0..9] of Char end; 14.StrPCopy 参考实例: var arrChar: array[0..255] of Char; // 这里声明了长度为256的Char型数组 begin StrPCopy(arrChar, 'Come on, baby!'); end; 15.Trim 参考实例: var S: String; begin S := ' Delphi 7.0 '; S := Trim(S); end; 16.TrimLeft 参考实例: S := TrimLeft(' Delphi'); 17.TrimRight 参考实例: S := TrimRight('Delphi '); 18.UpperCase 参考实例: var S, A: String; begin S := 'abc'; A := UpperCase(S); // 经过UpperCase函数转换后,A则等于ABC。 end; 三、文件、磁盘操作函数 1.Append 参考实例: var S: String; F: TextFile; begin S := 'This is a book.'; AssignFile(F, 'C:\MyFile.txt'); // 将C:\MyFile.txt文件与F变量建立连接,后面可以使用F变量对文件进行操作。 Append(F); // 以追加的方式打开文件 Writeln(F, S); // 将S变量中的内容追加到文本尾后。 CloseFile(F); // 关闭文件 end; 2.AssignFile 参考实例: var F: TextFile; // 声明文本文件类型变量 begin AssignFile(F, 'C:\MyFile.txt'); // 将C:\MyFile.txt文件与F变量建立连接,后面可以使用F变量对文件进行操作。 end; 3.ChDir 参考实例: var sDir: String; begin sDir := 'C:\Windows'; ChDir(sDir); // 此时,系统的当前目录则为C:\Windows目录。 end; 4.CloseFile 参考实例: var S: String; AllText: String; F: TextFile; begin AssignFile(F, 'C:\MyFile.txt'); // 将C:\MyFile.txt文件与F变量建立连接,后面可以使用F变量对文件进行操作。 Reset(F); // 打开文件 while not EOF(F) do begin // 使用While循环,一直判断是否到了文件未尾 Readln(F, S); // 读取一行文本 AllText := AllText + S; end; CloseFile(F); // 关闭文件 end; 5.DeleteFile 参考实例: var IsOK: Boolean; // 用于保存删除状态 begin IsOK := DeleteFile('C:\My Documents\Index.html'); // 函数执着后,将结果返回给IsOK变量。如果IsOK变量为True,则表示文件删除成功。 if IsOK then ShowMessage('文件删除成功!') else ShowMessage('文件删除失败!'); end; 6.DirectoryExists 参考实例: var IsExists: Boolean; begin IsExists := DirectoryExists ('C:\Windows'); if IsExists then ShowMessage('Windows文件夹存在!') else ShowMessage('Windows文件夹不存在!'); end; 7.DiskFree 参考实例: var FreeSize: Int64; begin FreeSize := DiskFree(3); // 获取C盘中的剩余空间。返回的值是以“字节”为单位的。 end; 8.DiskSize 参考实例: var DiskSize: Int64; begin DiskSize:= DiskSize(3); // 获取C盘的空间。返回的值是以“字节”为单位的。 end; 9.EOF 参考实例: var S: String; AllText: String; F: TextFile; begin AssignFile(F, 'C:\MyFile.txt'); // 将C:\MyFile.txt文件与F变量建立连接,后面可以使用F变量对文件进行操作。 Reset(F); // 打开文件 while not EOF(F) do begin // 使用While循环,一直判断是否到了文件未尾 Readln(F, S); // 读取一行文本 AllText := AllText + S; end; end; 10.Erase 参考实例: var F: File; begin AssignFile(F, 'C:\MyFile.txt'); // 将C:\MyFile.txt文件与F变量建立连接,后面可以使用F变量对文件进行操作。 Reset(F); // 打开文件 CloseFile(F); // 关闭文件 Erase(F); // 删除文件。在删除文件之前必须先关闭文件。 end; 11.FileExists 参考实例: var IsExists: Boolean; begin IsExists := FileExists('C:\Test.txt'); if IsExists then ShowMessage('该文件存在!') else ShowMessage('该文件不存在!'); end; 12.FileSize 参考实例: var F: file of Byte; nSize: Longint; begin AssignFile(F, 'C:\MyFile.txt'); // 将C:\MyFile.txt文件与F变量建立连接,后面可以使用F变量对文件进行操作。 Reset(F); // 打开文件 nSize := FileSize(F); // 获取文件大小。 CloseFile(F); // 关闭文件 end; 13.ForceDirectories 参考实例: ForceDirectories('C:\Flash\MyFolder'); // 如果Flash文件夹本身不存的话,那么将会先创建Flash文件夹,然后再创建MyFolder文件夹。 14.MkDir 参考实例: MkDir('C:\MyFolder'); // 在C盘根目录中创建了一个名叫MyFolder的文件夹。 需要注意的是,如果在本身不存在的目录中创建子目录将会失败。比如C盘根本不存在Flash文件夹,写成MkDir('C:\Flash\MyFolder');这样将不能成功创建MyFolder文件夹。 15.Reset 参考实例: var F: TextFile; // 声明文本文件类型变量 begin AssignFile(F, 'C:\MyFile.txt'); // 将C:\MyFile.txt文件与F变量建立连接,后面可以使用F变量对文件进行操作。 Reset(F); // 打开文件 end; 16.Rewrite 参考实例: var F: TextFile; // 声明文本文件类型变量 begin AssignFile(F, 'C:\MyFile.txt'); // 将C:\MyFile.txt文件与F变量建立连接,后面可以使用F变量对文件进行操作。 Rewrite(F); // 打开文件。如果文件不存在,将会在C盘中创建MyFile.txt文件。如果文件存在,将会覆盖MyFile.txt中的所有内容。 end; 17.Readln 参考实例: var S: String; F: TextFile; begin AssignFile(F, 'C:\MyFile.txt'); // 将C:\MyFile.txt文件与F变量建立连接,后面可以使用F变量对文件进行操作。 Reset(F); // 打开文件 Readln(F, S); // 读取一行文本到S变量中 end; 18.Rename 参考实例: var F: File; begin AssignFile(F, 'C:\MyFile.txt'); // 将C:\MyFile.txt文件与F变量建立连接,后面可以使用F变量对文件进行操作。 ReName(F, 'C:\NewFile.txt'); // 更名为NewFile.txt end; 19.Writeln 参考实例: var S: String; F: TextFile; begin S := 'This is a book.'; AssignFile(F, 'C:\MyFile.txt'); // 将C:\MyFile.txt文件与F变量建立连接,后面可以使用F变量对文件进行操作。 Rewrite(F); // 创建新文件 Writeln(F, S); // 将S变量中的内容写入文本中。 end; 四、内存、指针操作函数 1.AllocMem 参考实例: var Buffer: PChar; begin Buffer := AllocMem(256); // 分配大小为256个字节的内存空间 end; 2.Dispose 参考实例: var P: Pinteger; // 声明一个Integer(整型)指针 begin New(P); P^ := 100; Dispose(P) // 释放内存 end; 3.FreeMem 参考实例: var Buffer: Pchar; begin GetMem(Buffer, 256); // 分配大小为256个字节的内存空间。 FreeMem(Buffer); // 释放内存空间 end; 4.GetMem 参考实例: var Buffer: PChar; begin GetMem(Buffer, 256); // 分配大小为256个字节的内存空间。 end; 5.New 参考实例: var P: PInteger; // 声明一个Integer(整型)指针 begin New(P); // 分配内存 P^ := 100; // 赋值 end; 五、数学运算函数 默认情况下,Delphi新建的工程里,没有包含大多数的数学运行函数,因此需要在Uses中加入Math单元。 1.Abs 参考实例: var r: Single; i: Integer; begin r := Abs(-2.8); // r等于 2.8 i := Abs(-156); // I 等于 156 end; 2.Exp 参考实例: e := Exp(1.0); // e为real型变量 end; 3.Floor 参考实例: Floor(-2.8) = -3 Floor(2.8) = 2 Floor(-1.0) = -1 4.Int 参考实例: var R: Real; begin R := Int(123.456); // R等于 123.0 R := Int(-123.456); // R等于 -123.0 end; 5.Max(所在单元:Math) 参考实例: var k: Integer; begin k := Max(10, 20); // k将为20 end; 6.Min(所在单元:Math) 参考实例: var k: Integer; begin k := Min(10, 20); // k将为10 end; 7.PI 参考实例: var x Extended; begin x := PI; // x等于3.1415926535897932385 end; 8.Round 参考实例: var I, j: Integer; begin i := Round(1.25); // i等于1 j := Round(1.62); // j等于2 end; 9.Sqr 参考实例: var i: Integer; begin i := Sqr(3); // i等于9 end; 六、日期函数 1.Date 参考实例: procedure TForm1.Button1Click(Sender: TObject); begin Label1.Caption := '今天是:' + DateToStr(Date); end; Label显示为:今天是2005年1月1日。 2.DateToStr 参考实例: var S: String; begin S := DateToStr(Date); end; 3.DateTimeToStr 参考实例: var S: String; begin S := DateTimeToStr(Now); end; 4.DayOfTheMonth(所在单元:DateUtils) 参考实例: Label1.Caption := IntToStr(DayOfTheMonth(Now)); 假设当前日期为2005年1月2日,那么Label将显示为2。 5.DayOfTheWeek(所在单元:DateUtils) 参考实例: Label1.Caption := IntToStr(DayOfTheMonth(Now)); 假设当前日期为2005年1月2日,那么Label将显示为7。根据返回的值来判断是周几。7表示星期天,1为星期一,依类类推。 6.DayOfTheYear(所在单元:DateUtils) 参考实例: Label1.Caption := IntToStr(DayOfTheYear(Now)); 假设当前日期为2005年1月2日,那么Label将显示为2。表示是2005年的第2天。 7.DayOf(所在单元:DateUtils) 参考实例: Label1.Caption := IntToStr(DayOf(Date)); 假设当前日期为2005年1月2日,那么Label将显示为2。 8.IsLeapYear 参考实例: procedure TForm1.Button1Click(Sender: TObject); begin if IsLeapYear(YearOf(Date)) then ShowMessage('是闰年') else ShowMessage('不是闰年'); end; 9.MonthOf(所在单元:DateUtils) 参考实例: Label1.Caption := IntToStr(MonthOf(Date)); 假设当前日期为2005年1月2日,那么Label将显示为1。 10.Now 参考实例: procedure TForm1.Button1Click(Sender: TObject); begin Label1.Caption := '现在是:' + DateTimeToStr(Now); end; 11.YearOf(所在单元:DateUtils) 参考实例: Label1.Caption := IntToStr(YearOf(Date)); 假设当前日期为2005年1月2日,那么Label将显示为2005。 December 19 武汉风情——江汉路步行街上的铜塑我是一个彻头彻尾的武汉人,走在大街上,我会全然忘记女孩的身份,大声说话,哈哈地笑,或许还会在街上边走边吃,有人曾告诉我,你是一个女孩子,不应该是那样的。我想,是一方水土养了一方人。武汉是典型的码头文化之城,所以骨子里也就多了份豁达,少了那些忸怩。
一直都在试图用一些方式来表达自己对这个城市的热爱,文字?还是选择了图片这种最为直观的方式。
以前总以为自己不喜欢这里,总在设法逃离这个一直生活的地方,因为熟悉?因为厌倦?时隔多年,我才恍然大悟,自己已深深融合在这个城市里,所有对这个城市的熟悉,早已转化为一种情结。
步行街是我去的较多的地方。因为家在汉口,步行街总是逛街的首选。其实不大喜欢在这条街上买东西的,可是却极为喜欢走在这条街上的散漫感觉,即使是在节日里拥挤不堪,我依然喜欢挤在其中。不经意间会听到步行街北段——江汉关的钟声,尽管有些沉闷,但早已与这时尚气息尚浓的商业街融合在一起了。当然我最喜欢的还是步行街北段的那几座铜雕,总让我情不自禁地想起旧武汉的一切,怀念那段难忘的童年时光。
纳凉
这是我最喜欢的一座铜塑,其实设计师取的只是旧武汉很寻常的一景,炎炎夏日,晚饭后的傍晚,家家搬出竹床或板凳到户外纳凉,其间也会有一些小娱乐,如象棋,尽管武汉火炉天气让人难以忍受,但一年又一年,一代代的武汉人都是这么过过来的。
此景正是两位老爷爷拿着蒲扇,光着膀子,围坐在竹床上下象棋。本想纯粹取景,不想一位路过的小朋友坐下就不愿走了,于是如此拍下这张纳凉。之后发现,有了小朋友的增色,似乎更加贴合纳凉的主题,只是若是换上夏装,只怕更好。
热干面
热干面,是武汉的名小吃,来武汉的游客,大多都会选择一碗地道的热干面。不过说实话,如今菜林记的热干面已没有什么名声了,所以,只要随意在武汉的哪条巷子里来碗热干面,绝对地道。热干面的好坏,在于两点,一个是掸面,一个是芝麻酱,这二者好了,热干面也就爽口了。当然,并不是每个人都会喜欢它的口味。如今在北京、上海、广州、深圳等一些地方也有热干面,可是吃过武汉地道的口味之后,便会觉得那些地方是欺骗了自己的味觉。
此景正是师傅在抓面放进捞子里,准备下锅烫面。这座铜雕总是不乏冷清,或许是热干面的招牌让它引来路人无数。刚想趁人少拍一张,走过来才发现,又一小朋友坐着。他看到我的镜头,却依然故我,我倒满意,他是正等着师傅的热干面吧。
卖水
还是在老一辈人口中听说卖水的由来。以前用的水不是从自来水管中哗啦啦流出的,都是去花钱挑回来的。当初步行街修好之后,最让我好奇的就是这座铜塑,应该是因为这个故事年代久远的原因,而愈加吸引我。
迎面的站着的,是卖水的女掌柜,侧面站着提水的是买客。因为是在步行街,来往的路人很多,我等了半天,结果还是多拍了一个人。不过倒是可以相象,买客和女掌柜刚谈好价钱欲挑水离开,突然柜台后又出来一个人,买客急忙回头,生怕掌柜反悔加价。
ps 我也不明白为何三张铜塑中都有人的加入,这不是我的本意,但看到相片后,自己又是如此满意。 用Delphi创建服务程序来自 lanyus 的 Blog http://blog.csdn.net/lanyus/
作者:未知 文章来源:岁月联盟 Windows 2000/XP和2003等支持一种叫做"服务程序"的东西.程序作为服务启动有以下几个好处: (1)不用登陆进系统即可运行. (2)具有SYSTEM特权.所以你在进程管理器里面是无法结束它的. 笔者在2003年为一公司开发机顶盒项目的时候,曾经写过课件上传和媒体服务,下面就介绍一下如何用Delphi7创建一个Service程序. 运行Delphi7,选择菜单File-->New-->Other--->Service Application.将生成一个服务程序的框架.将工程保存为ServiceDemo.dpr和Unit_Main.pas,然后回到主框架.我们注意到,Service有几个属性.其中以下几个是我们比较常用的: (1)DisplayName:服务的显示名称 (2)Name:服务名称. 我们在这里将DisplayName的值改为"Delphi服务演示程序",Name改为"DelphiService".编译这个项目,将得到 ServiceDemo.exe.这已经是一个服务程序了!进入CMD模式,切换致工程所在目录,运行命令"ServiceDemo.exe /install",将提示服务安装成功!然后"net start DelphiService"将启动这个服务.进入控制面版-->管理工具-->服务,将显示这个服务和当前状态.不过这个服务现在什么也干不了,因为我们还没有写代码:)先"net stop DelphiService"停止再"ServiceDemo.exe /uninstall"删除这个服务.回到Delphi7的IDE. 我们的计划是为这个服务添加一个主窗口,运行后任务栏显示程序的图标,双击图标将显示主窗口,上面有一个按钮,点击该按钮将实现Ctrl+Alt+Del功能. 实际上,服务程序莫认是工作于Winlogon桌面的,可以打开控制面板,查看我们刚才那个服务的属性-->登陆,其中"允许服务与桌面交互 "是不打钩的.怎么办?呵呵,回到IDE,注意那个布尔属性:Interactive,当这个属性为True的时候,该服务程序就可以与桌面交互了. File-->New-->Form为服务添加窗口FrmMain,单元保存为Unit_FrmMain,并且把这个窗口设置为手工创建.完成后的代码如下: unit Unit_Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs, Unit_FrmMain; type TDelphiService = class(TService) procedure ServiceContinue(Sender: TService; var Continued: Boolean); procedure ServiceExecute(Sender: TService); procedure ServicePause(Sender: TService; var Paused: Boolean); procedure ServiceShutdown(Sender: TService); procedure ServiceStart(Sender: TService; var Started: Boolean); procedure ServiceStop(Sender: TService; var Stopped: Boolean); private { Private declarations } public function GetServiceController: TServiceController; override; { Public declarations } end; var DelphiService: TDelphiService; FrmMain: TFrmMain; implementation {$R *.DFM} procedure ServiceController(CtrlCode: DWord); stdcall; begin DelphiService.Controller(CtrlCode); end; function TDelphiService.GetServiceController: TServiceController; begin Result := ServiceController; end; procedure TDelphiService.ServiceContinue(Sender: TService; var Continued: Boolean); begin while not Terminated do begin Sleep(10); ServiceThread.ProcessRequests(False); end; end; procedure TDelphiService.ServiceExecute(Sender: TService); begin while not Terminated do begin Sleep(10); ServiceThread.ProcessRequests(False); end; end; procedure TDelphiService.ServicePause(Sender: TService; var Paused: Boolean); begin Paused := True; end; procedure TDelphiService.ServiceShutdown(Sender: TService); begin gbCanClose := true; FrmMain.Free; Status := csStopped; ReportStatus(); end; procedure TDelphiService.ServiceStart(Sender: TService; var Started: Boolean); begin Started := True; Svcmgr.Application.CreateForm(TFrmMain, FrmMain); gbCanClose := False; FrmMain.Hide; end; procedure TDelphiService.ServiceStop(Sender: TService; var Stopped: Boolean); begin Stopped := True; gbCanClose := True; FrmMain.Free; end; end. 主窗口单元如下: unit Unit_FrmMain; interface uses Windows, Messages, SysUtils, Variants, Classes, ShellApi, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls; const WM_TrayIcon = WM_USER + 1234; type TFrmMain = class(TForm) Timer1: TTimer; Button1: TButton; procedure FormCreate(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); procedure FormDestroy(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure Button1Click(Sender: TObject); private { Private declarations } IconData: TNotifyIconData; procedure AddIconToTray; procedure DelIconFromTray; procedure TrayIconMessage(var Msg: TMessage); message WM_TrayIcon; procedure SysButtonMsg(var Msg: TMessage); message WM_SYSCOMMAND; public { Public declarations } end; var FrmMain: TFrmMain; gbCanClose: Boolean; implementation {$R *.dfm} procedure TFrmMain.FormCreate(Sender: TObject); begin FormStyle := fsStayOnTop; SetWindowLong(Application.Handle, GWL_EXSTYLE, WS_EX_TOOLWINDOW); gbCanClose := False; Timer1.Interval := 1000; Timer1.Enabled := True; end; procedure TFrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin CanClose := gbCanClose; if not CanClose then begin Hide; end; end; procedure TFrmMain.FormDestroy(Sender: TObject); begin Timer1.Enabled := False; DelIconFromTray; end; procedure TFrmMain.AddIconToTray; begin ZeroMemory(@IconData, SizeOf(TNotifyIconData)); IconData.cbSize := SizeOf(TNotifyIconData); IconData.Wnd := Handle; IconData.uID := 1; IconData.uFlags := NIF_MESSAGE or NIF_ICON or NIF_TIP; IconData.uCallbackMessage := WM_TrayIcon; IconData.hIcon := Application.Icon.Handle; IconData.szTip := Delphi服务演示程序; Shell_NotifyIcon(NIM_ADD, @IconData); end; procedure TFrmMain.DelIconFromTray; begin Shell_NotifyIcon(NIM_DELETE, @IconData); end; procedure TFrmMain.SysButtonMsg(var Msg: TMessage); begin if (Msg.wParam = SC_CLOSE) or (Msg.wParam = SC_MINIMIZE) then Hide else inherited; // 执行默认动作 end; procedure TFrmMain.TrayIconMessage(var Msg: TMessage); begin if (Msg.LParam = WM_LBUTTONDBLCLK) then Show(); end; procedure TFrmMain.Timer1Timer(Sender: TObject); begin AddIconToTray; end; procedure SendHokKey;stdcall; var HDesk_WL: HDESK; begin HDesk_WL := OpenDesktop (Winlogon, 0, False, DESKTOP_JOURNALPLAYBACK); if (HDesk_WL <> 0) then if (SetThreadDesktop (HDesk_WL) = True) then PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, MAKELONG (MOD_ALT or MOD_CONTROL, VK_DELETE)); end; procedure TFrmMain.Button1Click(Sender: TObject); var dwThreadID : DWORD; begin CreateThread(nil, 0, @SendHokKey, nil, 0, dwThreadID); end; end. 补充: (1)关于更多服务程序的演示程序,请访问以下Url:http://www.torry.net/pages.php?id=226,上面包含了多个演示如何控制和管理系统服务的代码. (2)请切记:Windows实际上存在多个桌面.例如屏幕传输会出现白屏,可能有两个原因:一是系统处于锁定或未登陆桌面,二是处于屏幕保护桌面.这时候要将当前桌面切换到该桌面才能抓屏. (3)关于服务程序与桌面交互,还有种动态切换方法.大概单元如下: unit ServiceDesktop; interface function InitServiceDesktop: boolean; procedure DoneServiceDeskTop; implementation uses Windows, SysUtils; const DefaultWindowStation = WinSta0; DefaultDesktop = Default; var hwinstaSave: HWINSTA; hdeskSave: HDESK; hwinstaUser: HWINSTA; hdeskUser: HDESK; function InitServiceDesktop: boolean; var dwThreadId: DWORD; begin dwThreadId := GetCurrentThreadID; // Ensure connection to service window station and desktop, and // save their handles. hwinstaSave := GetProcessWindowStation; hdeskSave := GetThreadDesktop(dwThreadId); hwinstaUser := OpenWindowStation(DefaultWindowStation, FALSE, MAXIMUM_ALLOWED); if hwinstaUser = 0 then begin OutputDebugString(PChar(OpenWindowStation failed + SysErrorMessage(GetLastError))); Result := false; exit; end; if not SetProcessWindowStation(hwinstaUser) then begin OutputDebugString(SetProcessWindowStation failed); Result := false; exit; end; hdeskUser := OpenDesktop(DefaultDesktop, 0, FALSE, MAXIMUM_ALLOWED); if hdeskUser = 0 then begin OutputDebugString(OpenDesktop failed); SetProcessWindowStation(hwinstaSave); CloseWindowStation(hwinstaUser); Result := false; exit; end; Result := SetThreadDesktop(hdeskUser); if not Result then OutputDebugString(PChar(SetThreadDesktop + SysErrorMessage(GetLastError))); end; procedure DoneServiceDeskTop; begin // Restore window station and desktop. SetThreadDesktop(hdeskSave); SetProcessWindowStation(hwinstaSave); if hwinstaUser <> 0 then CloseWindowStation(hwinstaUser); if hdeskUser <> 0 then CloseDesktop(hdeskUser); end; initialization InitServiceDesktop; finalization DoneServiceDesktop; end. 更详细的演示代码请参看:http://www.torry.net/samples/samples/os/isarticle.zip (4)关于安装服务如何添加服务描述.有两种方法:一是修改注册表.服务的详细信息都位于HKEY_LOCAL_MACHINE\SYSTEM\ ControlSet001\Services\下面,例如我们刚才那个服务就位于HKEY_LOCAL_MACHINE\SYSTEM\ ControlSet001\Services\DelphiService下.第二种方法就是先用QueryServiceConfig2函数获取服务信息,然后ChangeServiceConfig2来改变描述.用Delphi实现的话,单元如下: unit WinSvcEx; interface uses Windows, WinSvc; const // // Service config info levels // SERVICE_CONFIG_DESCRIPTION = 1; SERVICE_CONFIG_FAILURE_ACTIONS = 2; // // DLL name of imported functions // AdvApiDLL = advapi32.dll; type // // Service description string // PServiceDescriptionA = ^TServiceDescriptionA; PServiceDescriptionW = ^TServiceDescriptionW; PServiceDescription = PServiceDescriptionA; {$EXTERNALSYM _SERVICE_DESCRIPTIONA} _SERVICE_DESCRIPTIONA = record lpDescription : PAnsiChar; end; {$EXTERNALSYM _SERVICE_DESCRIPTIONW} _SERVICE_DESCRIPTIONW = record lpDescription : PWideChar; end; {$EXTERNALSYM _SERVICE_DESCRIPTION} _SERVICE_DESCRIPTION = _SERVICE_DESCRIPTIONA; {$EXTERNALSYM SERVICE_DESCRIPTIONA} SERVICE_DESCRIPTIONA = _SERVICE_DESCRIPTIONA; {$EXTERNALSYM SERVICE_DESCRIPTIONW} SERVICE_DESCRIPTIONW = _SERVICE_DESCRIPTIONW; {$EXTERNALSYM SERVICE_DESCRIPTION} SERVICE_DESCRIPTION = _SERVICE_DESCRIPTIONA; TServiceDescriptionA = _SERVICE_DESCRIPTIONA; TServiceDescriptionW = _SERVICE_DESCRIPTIONW; TServiceDescription = TServiceDescriptionA; // // Actions to take on service failure // {$EXTERNALSYM _SC_ACTION_TYPE} _SC_ACTION_TYPE = (SC_ACTION_NONE, SC_ACTION_RESTART, SC_ACTION_REBOOT, SC_ACTION_RUN_COMMAND); {$EXTERNALSYM SC_ACTION_TYPE} SC_ACTION_TYPE = _SC_ACTION_TYPE; PServiceAction = ^TServiceAction; {$EXTERNALSYM _SC_ACTION} _SC_ACTION = record aType : SC_ACTION_TYPE; Delay : DWORD; end; {$EXTERNALSYM SC_ACTION} SC_ACTION = _SC_ACTION; TServiceAction = _SC_ACTION; PServiceFailureActionsA = ^TServiceFailureActionsA; PServiceFailureActionsW = ^TServiceFailureActionsW; PServiceFailureActions = PServiceFailureActionsA; {$EXTERNALSYM _SERVICE_FAILURE_ACTIONSA} _SERVICE_FAILURE_ACTIONSA = record dwResetPeriod : DWORD; lpRebootMsg : LPSTR; lpCommand : LPSTR; cActions : DWORD; lpsaActions : ^SC_ACTION; end; {$EXTERNALSYM _SERVICE_FAILURE_ACTIONSW} _SERVICE_FAILURE_ACTIONSW = record dwResetPeriod : DWORD; lpRebootMsg : LPWSTR; lpCommand : LPWSTR; cActions : DWORD; lpsaActions : ^SC_ACTION; end; {$EXTERNALSYM _SERVICE_FAILURE_ACTIONS} _SERVICE_FAILURE_ACTIONS = _SERVICE_FAILURE_ACTIONSA; {$EXTERNALSYM SERVICE_FAILURE_ACTIONSA} SERVICE_FAILURE_ACTIONSA = _SERVICE_FAILURE_ACTIONSA; {$EXTERNALSYM SERVICE_FAILURE_ACTIONSW} SERVICE_FAILURE_ACTIONSW = _SERVICE_FAILURE_ACTIONSW; {$EXTERNALSYM SERVICE_FAILURE_ACTIONS} SERVICE_FAILURE_ACTIONS = _SERVICE_FAILURE_ACTIONSA; TServiceFailureActionsA = _SERVICE_FAILURE_ACTIONSA; TServiceFailureActionsW = _SERVICE_FAILURE_ACTIONSW; TServiceFailureActions = TServiceFailureActionsA; /////////////////////////////////////////////////////////////////////////// // API Function Prototypes /////////////////////////////////////////////////////////////////////////// TQueryServiceConfig2 = function (hService : SC_HANDLE; dwInfoLevel : DWORD; lpBuffer : pointer; cbBufSize : DWORD; var pcbBytesNeeded) : BOOL; stdcall; TChangeServiceConfig2 = function (hService : SC_HANDLE; dwInfoLevel : DWORD; lpInfo : pointer) : BOOL; stdcall; var hDLL : THandle ; LibLoaded : boolean ; var OSVersionInfo : TOSVersionInfo; {$EXTERNALSYM QueryServiceConfig2A} QueryServiceConfig2A : TQueryServiceConfig2; {$EXTERNALSYM QueryServiceConfig2W} QueryServiceConfig2W : TQueryServiceConfig2; {$EXTERNALSYM QueryServiceConfig2} QueryServiceConfig2 : TQueryServiceConfig2; {$EXTERNALSYM ChangeServiceConfig2A} ChangeServiceConfig2A : TChangeServiceConfig2; {$EXTERNALSYM ChangeServiceConfig2W} ChangeServiceConfig2W : TChangeServiceConfig2; {$EXTERNALSYM ChangeServiceConfig2} ChangeServiceConfig2 : TChangeServiceConfig2; implementation initialization OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo); GetVersionEx(OSVersionInfo); if (OSVersionInfo.dwPlatformId = VER_PLATFORM_WIN32_NT) and (OSVersionInfo.dwMajorVersion >= 5) then begin if hDLL = 0 then begin hDLL:=GetModuleHandle(AdvApiDLL); LibLoaded := False; if hDLL = 0 then begin hDLL := LoadLibrary(AdvApiDLL); LibLoaded := True; end; end; if hDLL <> 0 then begin @QueryServiceConfig2A := GetProcAddress(hDLL, QueryServiceConfig2A); @QueryServiceConfig2W := GetProcAddress(hDLL, QueryServiceConfig2W); @QueryServiceConfig2 := @QueryServiceConfig2A; @ChangeServiceConfig2A := GetProcAddress(hDLL, ChangeServiceConfig2A); @ChangeServiceConfig2W := GetProcAddress(hDLL, ChangeServiceConfig2W); @ChangeServiceConfig2 := @ChangeServiceConfig2A; end; end else begin @QueryServiceConfig2A := nil; @QueryServiceConfig2W := nil; @QueryServiceConfig2 := nil; @ChangeServiceConfig2A := nil; @ChangeServiceConfig2W := nil; @ChangeServiceConfig2 := nil; end; finalization if (hDLL <> 0) and LibLoaded then FreeLibrary(hDLL); end. unit winntService; interface uses Windows,WinSvc,WinSvcEx; function InstallService(const strServiceName,strDisplayName,strDescription,strFilename: string):Boolean; //eg:InstallService(服务名称,显示名称,描述信息,服务文件); procedure UninstallService(strServiceName:string); implementation function StrLCopy(Dest: PChar; const Source: PChar; MaxLen: Cardinal): PChar; assembler; asm PUSH EDI PUSH ESI PUSH EBX MOV ESI,EAX MOV EDI,EDX MOV EBX,ECX XOR AL,AL TEST ECX,ECX JZ @@1 REPNE SCASB JNE @@1 INC ECX @@1: SUB EBX,ECX MOV EDI,ESI MOV ESI,EDX MOV EDX,EDI MOV ECX,EBX SHR ECX,2 REP MOVSD MOV ECX,EBX AND ECX,3 REP MOVSB STOSB MOV EAX,EDX POP EBX POP ESI POP EDI end; function StrPCopy(Dest: PChar; const Source: string): PChar; begin Result := StrLCopy(Dest, PChar(Source), Length(Source)); end; function InstallService(const strServiceName,strDisplayName,strDescription,strFilename: string):Boolean; var //ss : TServiceStatus; //psTemp : PChar; hSCM,hSCS:THandle; srvdesc : PServiceDescription; desc : string; //SrvType : DWord; lpServiceArgVectors:pchar; begin Result:=False; //psTemp := nil; //SrvType := SERVICE_WIN32_OWN_PROCESS and SERVICE_INTERACTIVE_PROCESS; hSCM:=OpenSCManager(nil,nil,SC_MANAGER_ALL_ACCESS);//连接服务数据库 if hSCM=0 then Exit;//MessageBox(hHandle,Pchar(SysErrorMessage(GetLastError)),服务程序管理器,MB_ICONERROR+MB_TOPMOST); hSCS:=CreateService( //创建服务函数 hSCM, // 服务控制管理句柄 Pchar(strServiceName), // 服务名称 Pchar(strDisplayName), // 显示的服务名称 SERVICE_ALL_ACCESS, // 存取权利 SERVICE_WIN32_OWN_PROCESS or SERVICE_INTERACTIVE_PROCESS,// 服务类型 SERVICE_WIN32_SHARE_PROCESS SERVICE_AUTO_START, // 启动类型 SERVICE_ERROR_IGNORE, // 错误控制类型 Pchar(strFilename), // 服务程序 nil, // 组服务名称 nil, // 组标识 nil, // 依赖的服务 nil, // 启动服务帐号 nil); // 启动服务口令 if hSCS=0 then Exit;//MessageBox(hHandle,Pchar(SysErrorMessage(GetLastError)),Pchar(Application.Title),MB_ICONERROR+MB_TOPMOST); if Assigned(ChangeServiceConfig2) then begin desc := Copy(strDescription,1,1024); GetMem(srvdesc,SizeOf(TServiceDescription)); GetMem(srvdesc^.lpDescription,Length(desc) + 1); try StrPCopy(srvdesc^.lpDescription, desc); ChangeServiceConfig2(hSCS,SERVICE_CONFIG_DESCRIPTION,srvdesc); finally FreeMem(srvdesc^.lpDescription); FreeMem(srvdesc); end; end; lpServiceArgVectors := nil; if not StartService(hSCS, 0, lpServiceArgVectors) then //启动服务 Exit; //MessageBox(hHandle,Pchar(SysErrorMessage(GetLastError)),Pchar(Application.Title),MB_ICONERROR+MB_TOPMOST); CloseServiceHandle(hSCS); //关闭句柄 Result:=True; end; procedure UninstallService(strServiceName:string); var SCManager: SC_HANDLE; Service: SC_HANDLE; Status: TServiceStatus; begin SCManager := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS); if SCManager = 0 then Exit; try Service := OpenService(SCManager, Pchar(strServiceName), SERVICE_ALL_ACCESS); ControlService(Service, SERVICE_CONTROL_STOP, Status); DeleteService(Service); CloseServiceHandle(Service); finally CloseServiceHandle(SCManager); end; end; end. (5)如何暴力关闭一个服务程序,实现我们以前那个"NT工具箱"的功能?首先,根据进程名称来杀死进程是用以下函数: uses Tlhelp32; function KillTask(ExeFileName: string): Integer; const PROCESS_TERMINATE = 01; var ContinueLoop: BOOL; FSnapshotHandle: THandle; FProcessEntry32: TProcessEntry32; begin Result := 0; FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); FProcessEntry32.dwSize := SizeOf(FProcessEntry32); ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32); while Integer(ContinueLoop) <> 0 do begin if ((UpperCase(ExtractFileName(FProcessEntry32.szExeFile)) = UpperCase(ExeFileName)) or (UpperCase(FProcessEntry32.szExeFile) = UpperCase(ExeFileName))) then Result := Integer(TerminateProcess( OpenProcess(PROCESS_TERMINATE, BOOL(0), FProcessEntry32.th32ProcessID), 0)); ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32); end; CloseHandle(FSnapshotHandle); end; 但是对于服务程序,它会提示"拒绝访问".其实只要程序拥有Debug权限即可: function EnableDebugPrivilege: Boolean; function EnablePrivilege(hToken: Cardinal; PrivName: string; bEnable: Boolean): Boolean; var TP: TOKEN_PRIVILEGES; Dummy: Cardinal; begin TP.PrivilegeCount := 1; LookupPrivilegeValue(nil, pchar(PrivName), TP.Privileges[0].Luid); if bEnable then TP.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED else TP.Privileges[0].Attributes := 0; AdjustTokenPrivileges(hToken, False, TP, SizeOf(TP), nil, Dummy); Result := GetLastError = ERROR_SUCCESS; end; var hToken: Cardinal; begin OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES, hToken); result:=EnablePrivilege(hToken, SeDebugPrivilege, True); CloseHandle(hToken); end; 使用方法: EnableDebugPrivilege;//提升权限 KillTask(xxxx.exe);//关闭该服务程序. December 08 Delphi中的字符串来自Jenry的blog http://dev.csdn.net/user/Jenry
一:各种字符串
字符串是Object Pascal所有数据类型中最有用的类型。许多函数以字符串为传递参数。由于在Delphi中字符串的定义和使用有各种方式,包括Pascal中典型的字符串(String),Delphi支持的长字符串(ANSIString),类似于C语言的字符数组(Array of Char),指向字符的指针(Pchar)等。下面的文章就跟谈谈这些类型在定义和应用中的区别和注意事项。 1. 传统的Pascal字符串 在Pascal中,典型的字符串是一定长度的字符序列。每一字符串有一设定的长度(缺省值为255),下面是一个例子: Var Address:String; Code:String[50]; Address是一长度为255的字符串,Code的最大长度为50。 传统的Pascal字符串长度不能超过255。 可以用字符串连接操作"+"把字符串连接在一起: Result:=String1+String2; 2. Delphi中的长字符串 Delphi除了支持传统的Pascal短字符串还支持长字符串。长字符称为ANSIString。长字符串动态分配内存,即用字符串时才分配字符串所需内存,所以其长度不受限制。在Delphi中你如果用String1:String作类型说明,则String1既可能是短字符串也可能是长字符串,这取决于编译器中$H开关的设置。默认值为$H+,代表ANSI长字符串,VCL中的组件使用ANSI长字符串。长字符串以null结束,这就说明长字符串与C语言中的以null结束的字符串完全兼容。 可以通过SetLength函数设置字符串的最大长度: SetLength(String1,100);用TrimLeft,TrimRight和Trim函数分别来消除字符串开头,结尾和首尾的空白区。 3. 类似于C的字符数组 可以用以0为起点的数组来存储以null结束的字符串。如下定义: Var Name:Array[0..50] of Char; 4. Pchar指针 如果Delphi中的Exended Syntax 已经设置(缺省值),以0为起点的字符数组就和指向字符的指针Pchar完全兼容,因为以0为起点的字符数组名即指向该字符数组首字符的指针。可以将字符串直接付值给Pchar指针。例如: var P: PChar; begin P := 'Hello world'; end; 这样P就指向存储字符串'Hello world'并以null结束的一块内存。 许多Windows的应用程序接口API函数要求用Pchar类型作参数。Pchar指针在使用是首先用GetMem(var P: Pointer; Size: Integer)函数申请分配内存,程序结束时用FreeMem(var P: Pointer[; Size: Integer])函数释放内存。例如: Var WinDir,SysDir:Pchar; Begin GetMem(WinDir,256);{为指针分配内存} GetWindowsDirectory(WinDir,128);{将Windows安装目录放至WinDir} ShowMessage('Windows directory is'+WinDir);{显示结果} End; 二:字符串转换 以上介绍了Delphi中的四类字符串的定义和使用。由于各类函数对字符串参数类型要求不一,这就需要进行字符串类型转换。 1. 可以用StrPas将以null结束的字符串转换为Pascal短字符串。StrpCopy则完成相反的转换。 2. 因为长字符串以null结束,所以可以用强制类型转换将长字符串转换成Pchar类型。用法是:Pchar(s),s是一个长字符串。强制类型转换返回一个指向长字符串首字符的指针,并且所指字符串以null 结束。例如: Var Caption,Message:string; Caption:='Hello World!'; Mssage:='This is a test of long string'; MessageBox(0,Pchar(Message),Pchar(Caption),MB_OK); 小结:在使用Delphi中的字符串时,要时刻清楚该字符串的类型,以免引起混淆。在理解字符串时要把字符串与指针,内存分配联系起来,加强理解。 Delphi中对象解除技巧来自srw的blog http://dev.csdn.net/user/srw
在Delphi程序设计中,我们需要通过解除建立的所有对象来避免内存泄漏。但是,如果你在程序中不谨慎小心的话,很容易多次解除同一个对象,这将会引起很严重的错误,造成应用程序崩溃,甚至是死机。 请看下面的程序:
在上例中,当执行到Button3.Free时会产生一个错误,因为在调用Button2.Free时,Button2被解除,但因为Button2是Button3的宿主,Button3也同时被解除,因此错误就不可避免地发生了。 要避免上述错误,有两种方法:第一,在解除宿主之前要先解除其所属的所有组件。这种方法很简单,只要将上例中Button2.Free和Button3.Free换位即可。第二,在Button3.Free执行之前将Button3变量设置为nil。程序如下:
在上面的程序中,将Button3置为nil是为了阻止应用程序重新使用已解除对象的内存。在调用Free对象方法时,它会确定对象是否为nil,如果不是,Free对象方法会假定对象仍然存在,使用对象引用为它分配内存,然后调用Destroy释放对象内存,并清除任何相关的虚拟对象方法的数据表格信息。而Destroy对于有对象存在的内存地址不会做任何改变,所以如果在调用Destroy之后测试该内存地址,它仍可能有效。所以当在程序中两次调用一个对象而没有将对象引用置为nil时,对象引用将仍然引用对象解除之前所占用的内存地址,这就是产生错误的原因。 当然,在实际开发过程中,大多数错误不会这样明显,解决的方法也要依具体而定,不会这么简单地加一句话就能解决问题。我在这里只是说明一种编程思想,起一个抛砖引玉的作用,具体的东西还要靠你在实践中去探索,积累。 如何根据类的名字来生成对象来自Hunto的blog http://dev.csdn.net/user/Hunto
有时候我们可能在程序中需要动态去生成一些对象,但我们又不想写大量的case分支语句来实现,而是建一张表,根据类的名称或其他一些索引来生成类的实例——对象。如果你有这方面的需要,本文将会帮助你实现这一目的。 在写正文之前首先要引进一个概念,类的变量(自己起的准确不准确无所谓,只大家明白就行啦)。这个类的变量并非我们平时看到的这样的定义如:Edit1:TEdit。这样的定义只是定义了一个对象。如果想定义类的变量,我们需要这样定义,首先要定义一个类型类的类 Type TEditClass=class of Tedit;这样的话TEditClass就是一个类的类型。我们就可以通过:Var EditClass:TEditClaa;来定义类的变量了。如果我们想来生成一个TEdit的实例,就可以通过下面这段程序来实现: type TEditClass=class of TEdit; 这段代码也许没什么意义,但如果有大量的对象的时候,这样做的用处就显出来了。那就是可以根据类名或其它的东西来生成类的实例。而不是用大量的CASE语段来判断该生成什么对象。需要注意的是,我上面定了的类之类是TEditClass,如果换成TEDIT的父类也是适用的。 如果把 Type TEditClass = class of TEdit 换成 TWInControlClass = class of TWinControl,程序照样可以正确运行。 要想实现标题所提到的想法,我们可以这样实现,构建一张表,其中至少有两个字段,一个索引(比如字符串或整数等等),另一个就是类的变量,当然也可以加其他一些辅助的信息。这要视具体的情况而定。在需要生成对象的时候,我们可以先查找索引,如字符串或整数,再根据索引指向的类的变量来生成实例。 使用这种方法一定要注意:定义类的类的时候,这个类要是你需要生成类的父类,并且它们的构造函数的参数完全一样。 这种方案可以做为使用工厂模式的一种具体实现方案。工厂类可以不知道自己有多少产品类,产品类自动用工厂类的增加方法来把自己添加到工厂类的产品列表中去。到产出产品对象的时候,工厂类自动根据产品名称来生成产品类。这样可以保证工厂类的绝对稳定性,即新增加产品的时候,工厂类不用做任何改动就可以运行。 一个导出Excel非常快的类来自dreamnest的blog http://dev.csdn.net/user/Dreamnest
unit DBGridEhToExcel; interface type //分解DBGridEh的标题 TDBGridEhToExcel = class(TComponent) procedure IncColRow; var implementation function TDBGridEhTitle.GetTitleColumn: integer; Result := ColumnCount; procedure TDBGridEhTitle.GetTitleData(var TitleCell: TTitleCell); function TDBGridEhTitle.GetTitleRow: integer; if MaxRow < Row then Result := MaxRow; procedure TDBGridEhTitle.SetDBGridEh(const Value: TDBGridEh); { TDBGridEhToExcel } constructor TDBGridEhToExcel.Create(AOwner: TComponent); procedure TDBGridEhToExcel.SetShowProgress(const Value: Boolean); procedure TDBGridEhToExcel.SetDBGridEh(const Value: TDBGridEh); procedure TDBGridEhToExcel.SetBeginDate(const Value: TCaption); procedure TDBGridEhToExcel.SetEndDate(const Value: TCaption); procedure TDBGridEhToExcel.SetTitleName(const Value: TCaption); procedure TDBGridEhToExcel.SetUserName(const Value: TCaption); procedure TDBGridEhToExcel.SetFileName(const Value: String); procedure TDBGridEhToExcel.IncColRow; procedure TDBGridEhToExcel.WriteBlankCell; procedure TDBGridEhToExcel.WriteFloatCell(const AValue: Double; const IncStatus: Boolean=True); if IncStatus then procedure TDBGridEhToExcel.WriteIntegerCell(const AValue: Integer; const IncStatus: Boolean=True); if IncStatus then procedure TDBGridEhToExcel.WriteStringCell(const AValue: string; const IncStatus: Boolean=True); if IncStatus then procedure TDBGridEhToExcel.WritePrefix; procedure TDBGridEhToExcel.WriteSuffix; procedure TDBGridEhToExcel.WriteHeader; Inc(FRow); if Trim(BeginDate) <> '' then if Trim(EndDate) <> '' then if (Trim(BeginDate) <> '') or (Trim(EndDate) <> '') then //制表人 //制表时间 Inc(FRow); procedure TDBGridEhToExcel.WriteTitle; try end; procedure TDBGridEhToExcel.WriteDataCell; //显示进度条进度过程 DBGridEh.DataSource.DataSet.Next; finally DBGridEh.DataSource.DataSet.EnableControls; procedure TDBGridEhToExcel.WriteFooter; FCol := 0; procedure TDBGridEhToExcel.SaveStream(aStream: TStream); //输出前缀 //输出表格标题 //输出列标题 //输出数据集内容 //输出DBGridEh表脚 //输出后缀 procedure TDBGridEhToExcel.ExportToExcel; //如果保存的文件名为空则退出 Screen.Cursor := crHourGlass; //显示进度窗体 end; destructor TDBGridEhToExcel.Destroy; procedure TDBGridEhToExcel.CreateProcessForm(AOwner: TComponent); FProgressForm := TForm.Create(AOwner); Panel := TPanel.Create(FProgressForm); Prompt := TLabel.Create(Panel); FGauge := TGauge.Create(Panel); end; FProgressForm.Show; procedure TDBGridEhToExcel.SetDataSetCrossIndexDBGridEh; for i := 0 to DBGridEh.DataSource.DataSet.FieldCount - 1 do end. /*****************************************************************/ 调用的例子 var 两个男孩[吃冰淇淋的男孩]
昨天站在永清小路等581,如果不是下雨,很可能会错过一些什么。 等到第三辆581,我才得以挤上去,占得一席之地,只能立着。车过长江二桥,刚挪了一下,身旁又挤过来一个男生,枣红色的外套,斜背一个包,短发,戴黑框眼镜。我用余光瞥见他左手中的蛋筒冰淇淋。
我们并排站着,我直直得望着窗外,其实什么也没有看见,由于下雨,车窗全都关着,车窗的玻璃上已是蒙蒙薄雾。身旁的男孩专注地吃着手中的冰淇淋,右手则牢牢抓住座椅把手。我只用余光注意着这个男孩,吸引我的只是他左手中的冰淇淋,他的嘴中不时发出吃东西的声音,用耳朵就知道,他此刻全然陶醉在他左手中的冰淇淋里,那是嘴、眼、心的专注,直到他手中的冰淇淋消失。他用舌尖将嘴唇舔了一圈,最后也直直地望着窗外潮湿的空气。
确切地说,吸引我的只是那冰淇淋的味道,是我溺爱的香芋,浅浅的紫,淡淡的香,柔柔的质感,入口即化。很久没去品尝了,却一如既往地熟悉。
窗外的雨还在下,我庆幸着这样的邂逅。
12:54 2004-4-26
[吹口哨的男孩]
已经三个礼拜了,从五一长假后,我就没再回过家了,今天就闪组成原理老师的实验课,悄悄回家了。 路面是潮湿的,却不见水,有的只是落雨的痕迹。庆幸在车上寻了个位子,不然一个半钟头的行程会站到脚发麻。坐在车上看窗外的风景也是种闲适的心情。
车上的人渐渐多了,甚至开始拥挤。不知何时飘来一阵轻快的口哨声,视线从窗外转到车厢里,是个洒脱的男生,吹的曲子是机器猫。我细细听着,吸引我的并非曲子,而是欢愉悠闲的口哨声,身处拥挤的车厢,他还能轻松地享受着。
我喜欢和童心未泯的人一起,因为我怀念纯真年代,背着书包,哼着小曲,束起马尾,蹦蹦跳跳地往返于学校和家,羞涩地躲在爸妈身后指着自己想要的玩意,下课后疯跑到操场的某个角落和小伙伴牵起皮筋,只为躲避捣乱的男生……
有时候真希望自己是个长不大的小孩!
15:41 2004-5-30
喜欢的文字往日子的深处一天一天写下去,同时以写作的速度死亡。
我们内心的单薄、虚弱有时就是靠着一些繁琐冗长的细节才能逐渐丰混、强壮起来的。
当时光如细沙般在指间缓缓流逝之后,我们手上留有的仅仅是岁月霉变的气息和一点点能硌疼你掌心的细节的沙粒。 鱼说
与生俱来的恐惧 令我落水为鱼 不用掩饰 尽可以将泪溶于水 水的反面
极光滑的手感 掏出柔软无骨 寸金寸长的光阴 恋爱中的人们习惯 隐于水的流逝中 以最妩媚最锐利的形式流淌 解释前生,约定来世 在时间之前,在时间之后 我们的日子 通篇地暗无天日 惟有沉浸于水 倾心倾力扮演一条嬉戏的鱼 偶尔探出水面试探一下世态的炎凉 心愿昨天就这样坐在书桌前收到苏的短信,让我意外地是,她用这种方式平静地表达自己的心愿。
“萧,再次去了你的学校后,我感慨良多,我极力期盼你能在大学校园里找到一个他,这是我未完成的梦,你在学校的日子也不多了,管他是高是矮是胖是瘦,好吗?”
我清楚地告诉苏,我没法忘记他,没法放下那段感情,没法再去爱其他人,我只想日子平淡地过去。苏说的没错,未能实现的总是最好的,我曾经是多么不舍,爱他胜过自己,可最终还是分开了。
是的,我想过要用新的感情来替代,可我做不到,真的做不到,期盼着新的感情能到来,却又心如死水。
从前都是一个人,可分手以后,我是强烈地惧怕寂寞,惶恐地过着每一天,没有你,我怎么办?
我依然平静地陷在寂寞的日子里,恐怕朋友的心愿也只能说说而已吧。
16:23 2004-10-10 文字欲 不经意间,我总在努力回忆爱上文字的那一天,然而却好像是一瞬间的事情,我记不起那一刻。
长久以来,我发现在我的脑海里不断涌现着各种各样的想法,我想写。
想写,我也总说,只要有笔有纸,我就能一直写下去。事实上,也确实如此,脑海里有一个又一个的故事,有我的,也有别人的,我想用自己的情绪将之一一写出。我想我只是想单纯地写,至于读者怎么看,我都不在乎,不管怎么说,我都只是想用文字来表达自己。
有一阵子,我随身携带着便签本和笔,我记下我偶然的感受,那时许许多多简短的文字,我的文字力没有喧嚣和华丽,如同浅吟低唱的歌谣。
我喜欢说故事,我喜欢用婉转的方式,我喜欢淡淡的忧伤与快乐。于是我将这些带进了我的文字我的故事中,我会一直写下去,直到不能写的那一天。 December 06 Delphi7语言手册见闻来源:Delphi园地
1、一个控制台程序 Delphi加注册表自启动的最简单代码作者:jondy 来源:jondy's Blog(http://zhack.blog.163.com/)
[感谢jondy朋友的热心!把他更新后的代码贴上来,这个更适合写小软件] program exe; uses windows; // 注册表新建键值的函数 procedure CreateKey(const RootKey : HKey; Key, ValueName, Value: string); var Handle: HKey; Res, Disposition: Integer; begin Res := RegCreateKeyEx(RootKey, PChar(Key), 0, '', REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nil, Handle, @Disposition); if Res = 0 then begin Res := RegSetValueEx(Handle, PChar(ValueName), 0, REG_SZ, PChar(Value), Length(Value) + 1); RegCloseKey(Handle) end; end; begin // 跟位置名、文件路径 CreateKey(HKEY_LOCAL_MACHINE,'SoftWare\Microsoft\Windows\CurrentVersion\Run','AutoRun','C:\WINDOWS\regedit.exe'); end. |
|
|