| 萧萧 的个人资料时光·漫步日志列表 | 帮助 |
|
|
4月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方法。 2月9日 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方法。 1月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。 12月19日 用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);//关闭该服务程序. 12月8日 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 12月6日 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. 怎样在Delphi中实现文件切割/组合作者:秦凯 来源:yesky
一、 文件切割/组合原理 二、 实现
{*注释:请注意COPYFROM方法的使用,当第二个参数为0时,表示将输入流InStream的数据全部写入输出流OutStream;当大于0为N时,表示从输入流InStream的当前流指针位置开始,将N个字节数据写入输出流OutStream,并且InStream的流指针会向后偏移N个字节位数。详细解释请查看DELPHI自带的帮助*} 三、结束语 12月2日 Delphi删除IE临时文件uses Delphi一句话帮助1. 如果想你的程序能够正确处理异常情况的话,请引用SysUtils.pas单元,否则即使程序使用了try。。。except。。。也不能正确捕获异常。 2. 定义常量字符串的一种方式 resourcestring aa=aaaa; raise Exception.CreateRes(@aa); 3. 字符串常量数组的初始化 const constarray:array [0..2] of string=(‘first’,’second’,’third’); 4. 结构体初始化 type Tstructinit=record A1:integer; A2:array [0..2] of integer; End; Const m_structinit:Tstructinit=(A1:0;A2:(0,1,2)); 5. 多维数组的长度 var array2:array of array of integer; setlength(array2,2,2); 6. 使用Create和New开辟的空间都存在于堆中,不能自动释放,建议使用FreeAndNil释放, 参数以及局部变量存在于栈中,自动释放。 7. SizeOf不适合于对象,返回的总是4;对于固定类型可以正确返回. 8. Create(nil)需要手工释放,Creat(self)会随着拥有者的释放而释放. 9. 动态改变已定义常量的值 procedure ChangeConst(const Const;var Value;Size:Integer); begin Move((@Value)^,(@Constant)^,Size); End; 10. 进行删除操作的时候循环使用DownTo,会避免错误. 11. 汉字的Ascii码>128,可以用它来判别是否为汉字 12. dll编写中,需要使用Sharemem单元来引用BORLANDMM.DLL内存管理. 13. PostMessage只将消息放到消息队列中,需要排队等待处理。 SendMessage绕过消息队列直接发送到窗口过程,等到消息处理返回值才返回. 14. 鼠标移入移出消息:CM_MOUSEENTER,CM_MOUSELEAVE 15. 关机消息WM_QUERYENDSESSION 16. 可以利用ThintWindow和类的方法ActivateHint来创建浮动窗体. 17. 调出文件属性对话框 uses ShellAPI; function ShowFileProperties(FileName: String; Wnd: HWND):Boolean; var sfi: TSHELLEXECUTEINFO; begin with sfi do begin cbSize := SizeOf(sfi); lpFile := PAnsiChar(FileName); Wnd := Wnd; fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_INVOKEIDLIST or SEE_MASK_FLAG_NO_UI; lpVerb := PAnsiChar(properties); lpIDList := nil; lpDirectory := nil; nShow := 0; hInstApp := 0; lpParameters := nil; dwHotKey := 0; hIcon := 0; hkeyClass := 0; hProcess := 0; lpClass := nil; end; Result := ShellExecuteEX(@sfi); end; procedure TForm1.Button1Click(Sender: TObject); begin ShowFileProperties(c:\AA.txt, Handle); end; 18. 更改系统时间 uses Windows,Dialogs,Forms; var MyTime:TsystemTime; begin FillChar(MyTime,sizeof(MyTime),#0); MyTime.wYear:=2003; MyTime.wMonth:=06; MyTime.wDay:=01; If not SetSystem(MyTime) then Showmessage(‘Failure’); End; 19. 复制文件夹Xcopy . procedure Xcopy(SourceDir,DestinationDir:String); var procedure DrawTrans(DestCanvas: TCanvas; X,Y: smallint; SrcBitmap: TBitmap; AColor, BackColor: TColor); var ANDBitmap, ORBitmap: TBitmap; CM: TCopyMode; Src: TRect; begin ANDBitmap:= NIL; ORBitmap:= NIL; try ANDBitmap:= TBitmap.Create; ORBitmap:= TBitmap.Create; Src := Bounds(0,0, SrcBitmap.Width, SrcBitmap.Height); with ORBitmap do begin Width:= SrcBitmap.Width; Height:= SrcBitmap.Height; Canvas.Brush.Color := clBlack; Canvas.CopyMode := cmSrcCopy; Canvas.BrushCopy(Src, SrcBitmap, Src, AColor); end; with ANDBitmap do begin Width:= SrcBitmap.Width; Height:= SrcBitmap.Height; Canvas.Brush.Color := BackColor; Canvas.CopyMode := cmSrcInvert; Canvas.BrushCopy(Src, SrcBitmap, Src, AColor); end; with DestCanvas do begin CM := CopyMode; CopyMode := cmSrcAnd; Draw(X,Y, ANDBitmap); CopyMode := cmSrcPaint; Draw(X,Y, ORBitmap); CopyMode := CM; end; finally ANDBitmap.Free; ORBitmap.Free; end; end; procedure TForm1.Button4Click(Sender: TObject); begin DrawTrans(Image1.Canvas, 0,0, Image2.Picture.Bitmap, clBlack, clSilver); end; 21. 获取CPU速度 function GetCpuSpeed: Extended; var t, mhi, mlo, nhi, nlo: dword; shr32 : comp; begin shr32 := 65536; shr32 := shr32 * 65536; t := GetTickCount; while t = GetTickCount do ; asm DB 0FH,031H // rdtsc mov mhi,edx mov mlo,eax end; while GetTickCount < (t + 1000) do ; asm DB 0FH,031H // rdtsc mov nhi,edx mov nlo,eax end; Result := ((nhi * shr32 + nlo) - (mhi * shr32 + mlo)) / 1E6; end; procedure TForm1.Button4Click(Sender: TObject); begin label1.Caption := FloatToStr(GetCpuSpeed) + mhz; end; 12月1日 Delphi 属性、函数、事件参考abort 函数 引起放弃的意外处理
addexitproc 函数 将一过程添加到运行时库的结束过程表中
addr 函数 返回指定对象的地址
adjustlinebreaks 函数 将给定字符串的行分隔符调整为cr/lf 序列
allocmem 函数 在堆栈上分配给定大小的块
ansicomparestr 函数 比较字符串(区分大小写)
ansicomparetext 函数 比较字符串(不区分大小写)
ansilowercase 函数 将字符转换为小写
ansiuppercase 函数 将字符转换为大写
append 函数 以附加的方式打开已有的文件
assignfile 函数 给文件变量赋一外部文件名
assigned 函数 测试函数或过程变量是否为空
beginthread 函数 以适当的方式建立用于内存管理的线程
blockread 函数 读一个或多个记录到变量中
blockwrite 函数 从变量中写一个或多个记录
changefileext 函数 改变文件的后缀
chdir 函数 改变当前目录
chr 函数 返回指定序数的字符
closefile 命令 关闭打开的文件
comparestr 函数 比较字符串(区分大小写)
copy 函数 返回一字符串的子串
datetimetofiledate 函数 将delphi 的日期格式转换为dos 的日期格式
datetimetostr 函数 将日期时间格式转换为字符串 d
atetimetostring 函数 将日期时间格式转换为字符串
datetostr 函数 将日期格式转换为字符串
dayofweek 函数 返回星期的数值
dec 函数 递减变量值
decodedate 函数 将日期格式分解为年月日
decodetime 函数 将时间格式分解为时、分、秒、毫秒
delete 函数 从字符串中删除子串
deletefile 命令 删除文件
diskfree 函数 返回剩余磁盘空间的大小
disksize 函数 返回指定磁盘的容量
dispose 函数 释放动态变量所占的空间
disposestr 函数 释放字符串在堆栈中的内存空间
ditherbackground 命令 使背景色的色彩加重或减少50%
dragcursor 属性 当鼠标按下时光标的形状
dragmode 属性 按动的作用方式
dropdowncount 属性 容许的显示数据项的数目
editmask 属性 编辑模式
encodedate 函数 将年月日合成为日期格式
encodetime 函数 将时、分、秒、毫秒合成为时间格式
endmargin 属性 末尾边缘
eoln 函数 返回文本文件的行结束状态
erase 命令 删除外部文件
exceptaddr 函数 返回引起当前意外的地址
exclude 函数 从集合中删除一些元素
exceptobject 函数 返回当前意外的索引
extendedselect 属性 是否允许存在选择模式,true 时,multiselect 才有意义
fileage 函数 返回文件已存在的时间
fileclose 命令 关闭指定的文件
filecreate 命令 用指定的文件名建立新文件
filedatetodatetime 函数 将dos 的日期格式转换为delphi 的日期格式
filegatattr 函数 返回文件的属性
filegetdate 函数 返回文件的dos 日期时间标记
fileopen 命令 用指定的存取模式打开指定的文件
filepos 函数 返回文件的当前指针位置 fileread 命令 从指定的文件读取
filesearch 命令 在目录中搜索指定的文件
fileseek 函数 改变文件的指针
filesetattr 函数 设置文件属性
filesetdate 函数 设置文件的dos 日期时间标记
filesize 函数 返回当前文件的大小
filewrite 函数 对指定的文件做写操作
fillchar 函数 用指定的值填充连续字节的数
findclose 命令 终止findfirst/findnext 序列
findfirst 命令 对指定的文件名及属性搜索目录
findnext 命令 返回与文件名及属性匹配的下一入口
floattodecimal 函数 将浮点数转换为十进制数
floattostrf 函数 将浮点数转换为字符串
floattostr 函数 将浮点数转换为字符串
floattotext 函数 将给定的浮点数转换为十进制数
floattotextfmt 函数 将给定的浮点数转换为十进制数
flush 函数 将缓冲区的内容刷新到输出的文本文件中
fmtloadstr 函数 从程序的资源字符串表中装载字符串
fmtstr 函数 格式化一系列的参数,其结果以参数result 返回
format 函数 格式化一系列的参数并返回pascal 字符串
formatbuf 函数 格式化一系列的参数
formatdatetime 函数 用指定的格式来格式化日期和时间
formatfloat 函数 指定浮点数格式 frac 函数 返回参数的小数部分
freemem 函数 按给定大小释放动态变量所占的空间
getdir 函数 返回指定驱动器的当前目录
getheapstatus 函数 返回内存管理器的当前状态
getmem 函数 建立一指定大小的动态变量,并将指针指向该处
getmemorymanager 函数 返回内存管理器的入口点
glyph 函数 按钮上的图象 halt 命令 停止程序的执行并返回到操作系统
hi 函数 返回参数的高地址位
high 函数 返回参数的上限值
int 函数 返回参数的整数部分
include 函数 添加元素到集合中
insert 函数 在字符串中插入子串
inttohex 函数 将整型数转换为十六进制数
inttostr 函数 将整型数转换为字符串
ioresult 函数 返回最新的i/o 操作完成状态
isvalidident 函数 测试字符串是否为有效的标识符
largechange 属性 最大改变值
length 函数 返回字符串的动态长度
lines 属性 缺省显示内容
ln 函数 自然对数函数
lo 函数 返回参数的低地址位
loadstr 函数 从应用程序的可执行文件中装载字符资源
lowercase 函数 将给定的字符串变为小写
low 函数 返回参数的下限值
mkdir 命令 建立一子目录
move 函数 从源到目标复制字节
newstr 函数 在堆栈上分配新的字符串
ondeactivate 事件 用户切换到另一应用程序触发
ondragover 事件 有其他控件从他上面移过触发
ord 函数 返回序数类的序数
outofmemoryerror 命令 引起outofmemory 意外
pageindex 属性 页索引
paramcount 函数 返回在命令行上传递给程序的参数数量
paramstr 函数 返回指定的命令行参数
pictureclosed 属性 设置closed 位图
pictureleaf 属性 设置leaf 位图
pictureminus 属性 设置minus 位图
pictureopen 属性 设置open 位图
pictureplus 属性 设置plus 位图
pos 函数 在字符串中搜索子串
pred 函数 返回先前的参数
random 函数 返回一随机函数
randomize 函数 用一随机数初始化内置的随机数生成器
read 函数 对有格式的文件,读一文件组件到变量中;
对文本文件,读一个或多个值到一个或多个变量中
readln 函数 执行read 过程,然后跳到文件下一行
readonly 属性 只读属性
reallocmem 函数 分配一动态变量
rename 函数 重命名外部文件
renamefile 函数 对文件重命名
reset 函数 打开已有的文件
rewrite 函数 建立并打开一新的文件
rmdir 函数 删除空的子目录
round 函数 将实数值舍入为整型值
runerror 函数 停止程序的执行
seek 函数 将文件的当前指针移动到指定的组件上
seekeof 函数 返回文件的文件结束状态
seekeoln 函数 返回文件的行结束状态
setmemorymanager 函数 设置内存管理器的入口点
settextbuf 函数 给文本文件指定i/o 缓冲区
showexception 函数 显示意外消息与地址
sizeof 函数 返回参数所占的字节数
smallchange 属性 最小改变值
str 函数 将数值转换为字符串
stralloc 函数 给以null 结束的字符串分配最大长度-1 的缓冲区
strbufsize 函数 返回存储在由stralloc 分配的字符缓冲区的最大字符数
strcomp 函数 比较两个字符串
strcopy 函数 将一个字符串复制到另一个字符串中
strdispose 函数 释放堆栈上的字符串
strecopy 函数 将一字符串复制到另一个字符串并返回结果字符串尾部的指针
strend 函数 返回指向字符串尾部的指针
stretch 属性 自动适应控件的大小
strfmt 函数 格式化一系列的参数
stricomp 函数 比较两个字符串(不区分大小写)
stringtowidechar 函数 将ansi 字符串转换为unicode 字符串
strlcat 函数 将一字符串中的字符附加到另一字符串尾并返回合并的字符串
strlcomp 函数 以最大长度比较两个字符串
strlcopy 函数 将一个字符串中的字符复制到另一个字符串中
strlen 函数 返回字符串中的字符数
strlfmt 函数 格式化一系列的参数,其结果中包含有指向目标缓冲区的指针
strlicomp 函数 以最大长度比较两个字符串(不区分大小写)
strlower 函数 将字符串中的字符转换为小写
strmove 函数 将一个字符串中的字符复制到另一个字符串中
strnew 函数 在堆栈上分配一个字符串
strpas 函数 将以null 结束的字符串转换为pascal 类的字符串
strpcopy 函数 将pascal 类的字符串复制为以null 结束的字符串
strplcopy 函数 从pascal 类的最大长度字符串复制为以null 结束的字符串
strpos 函数 返回一个字符串在另一个字符串中首次出现指针
strrscan 函数 返回字符串中最后出现字符的指针
strscan 函数 返回字符串中出现首字符的指针
strtodate 函数 将字符串转换为日期格式
strtodatetime 函数 将字符串转换为日期/时间格式
strtofloat 函数 将给定的字符串转换为浮点数
strtoint 函数 将字符串转换为整型
strtointdef 函数 将字符串转换为整型或默认值
strtotime 函数 将字符串转换为时间格式
strupper 函数 将字符串中的字符转换为大写
suce 函数 返回后继的参数
swap 函数 交换参数的高低地址位
texttofloat 函数 将字符串(以null 结束的格式)转换为浮点数
timetostr 函数 将时间格式转换为字符串
trunc 函数 将实型值截取为整型值
truncate 函数 截去当前文件位置后的内容
unselectedcolor 属性 未选中颜色
upcase 函数 将字符转换为大写
uppercase 函数 将给定的字符串变为大写
val 函数 将字符串转换为整型值
vararraycreate 函数 以给定的界限和维数建立变体数组
vararraydimcount 函数 返回给定变体的维数
vararrayhighbound 函数 返回给定变体数组维数的上界
vararraylock 函数 锁定给定的变体数组
vararraylowbound 函数 返回给定变体数组维数的下界
vararrayof 函数 返回指定变体的数组元素
vararrayredim 函数 通过改变上限来调整变体的大小
vararrayunlock 函数 解锁指定的变体数组
varastype 函数 将变体转换为指定的类型
varcase 函数 将变体转换为指定的类型并保存他
varclear 函数 清除指定的变体
varcopy 函数 将指定的变体复制为指定的变体
varformdatetime 函数 返回包含日期时间的变体
varisarray 函数 测试变体是否为数组
varisempty 函数 测试变体是否为unassigned
varisnull 函数 测试变体是否为null
vartodatetime 函数 将给定的变体转换为日期时间
vartype 函数 将变体转换为指定的类型并保存他
wantreturns 属性 为true 时,按回车键产生一个回车符;
为false 时,按下ctrl+enter 才产生回车符 write 命令 对有格式的文件,写一变量 到文件组件中; 对文本文件,写一个或多个值到文件中
writeln 命令 执行write 过程,然后输出一行结束标志
widecharlentostring 函数 将ansi 字符串转换为unicode 字符串
widecharlentostrwar 函数 将unicode 字符串转换为ansi 字符串变量
widechartostring 函数 将unicode 字符串转换为ansi 字符串
widechartostrvar 函数 将unicode 字符串转换为ansi 字符串变量 |
|
|