739 lines
16 KiB
Plaintext
739 lines
16 KiB
Plaintext
{*******************************************************}
|
|
{ }
|
|
{ Tocsg.Thread }
|
|
{ }
|
|
{ Copyright (C) 2022 kkuzil }
|
|
{ }
|
|
{*******************************************************}
|
|
|
|
unit Tocsg.Thread;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.Classes, System.SysUtils, System.SyncObjs, Winapi.Windows,
|
|
Winapi.Messages, System.Generics.Collections;
|
|
|
|
type
|
|
TTgThreadState = (tsInit, tsWorking, tsPause, tsStop, tsCompleted, tsFail);
|
|
TTgThread = class(TThread)
|
|
private
|
|
CS_: TCriticalSection;
|
|
protected
|
|
nLastError_: Integer;
|
|
bWorkStop_,
|
|
bWorkCancel_: Boolean; // 사용자가 수동으로 작업을 중지한건지 판단하는게.. bWorkStop_ 만으로는 부족해서 추가 13_1213 13:51:54 kku
|
|
WorkState_: TTgThreadState; // 범용적으로 사용될 스레드 상태값. 이전부터 쓰던건 스레드마다 직접 지정해서 사용했다... 기본으로 추가함 18_0416 22:50:16 kku
|
|
|
|
procedure _Trace(const sLog: String; nLevel: Integer = 0); overload;
|
|
procedure _Trace(const sFormat: String; const aArgs: array of const; nLevel: Integer = 0); overload;
|
|
|
|
property LastError: Integer read nLastError_;
|
|
|
|
procedure Lock;
|
|
procedure Unlock;
|
|
procedure SetWorkStop(bVal: Boolean); virtual;
|
|
function GetWorkStop: Boolean;
|
|
function GetWorkCancel: Boolean;
|
|
|
|
procedure SetWorkState(aKind: TTgThreadState);
|
|
function GetWorkState: TTgThreadState;
|
|
|
|
// {$IF CompilerVersion > 21}
|
|
procedure TerminatedSet; override;
|
|
// {$IFEND}
|
|
public
|
|
Constructor Create;
|
|
Destructor Destroy; override;
|
|
|
|
procedure TerminateWaitUntilEnd;
|
|
|
|
procedure StartThread; virtual;
|
|
procedure PauseThread; virtual;
|
|
procedure StopThread; virtual;
|
|
procedure CancelWork;
|
|
|
|
property IsCancel: Boolean read GetWorkCancel;
|
|
property WorkStop: Boolean read GetWorkStop;
|
|
end;
|
|
|
|
TTgEventThread = class(TTgThread)
|
|
private
|
|
hEvent_: THandle;
|
|
bSync_: Boolean;
|
|
sEvtName_: String;
|
|
protected
|
|
bUseActiveX_: Boolean; // 스레드 내부에서 ConInit..쓸일이 있다.
|
|
procedure DoEvent;
|
|
procedure Execute; override;
|
|
procedure ProcessWorkEvent; virtual; abstract;
|
|
public
|
|
Constructor Create(const sEventName: String = 'TTgEventThread'; bSync: Boolean = false);
|
|
|
|
procedure StartThread; override;
|
|
procedure StopThread; override;
|
|
|
|
property EventName: String read sEvtName_;
|
|
property EventHandle: THandle read hEvent_;
|
|
end;
|
|
|
|
PCloseFormEnt = ^TCloseFormEnt;
|
|
TCloseFormEnt = record
|
|
sClassName,
|
|
sCaption: String;
|
|
end;
|
|
|
|
TThdCloseForm = class(TTgThread)
|
|
private
|
|
CloseFromEnt_: TList<PCloseFormEnt>;
|
|
procedure OnEntryNotify(Sender: TObject; const Item: PCloseFormEnt; Action: TCollectionNotification);
|
|
protected
|
|
procedure Execute; override;
|
|
public
|
|
Constructor Create;
|
|
Destructor Destroy; override;
|
|
|
|
procedure ClearEntry;
|
|
|
|
procedure AddEntry(const sClassName, sCaption: String);
|
|
end;
|
|
|
|
TTaskTimerEnt = class(TObject)
|
|
private
|
|
fnEvent_: TNotifyEvent;
|
|
fnEvent2_: TThreadMethod;
|
|
dwTick_,
|
|
dwInterval_: DWORD;
|
|
bDefaultActive_: Boolean;
|
|
OwnerThread_: TTgThread;
|
|
public
|
|
Constructor Create(fnEvent: TNotifyEvent; dwInterval: DWORD; bDefaultActive: Boolean); overload;
|
|
Constructor Create(aOwnerThread: TTgThread; fnEvent: TThreadMethod; dwInterval: DWORD; bDefaultActive: Boolean); overload; // 추가 15_1022 15:57:14 sunk
|
|
Destructor Destroy; override;
|
|
|
|
procedure InitTask(dwTick: DWORD);
|
|
procedure ProcessTask(dwTick: DWORD);
|
|
|
|
property DefaultActive: Boolean read bDefaultActive_;
|
|
property Tick: DWORD read dwTick_;
|
|
property Interval: DWORD read dwInterval_;
|
|
property Event: TNotifyEvent read fnEvent_;
|
|
end;
|
|
|
|
TTimerEntEnumerator = TEnumerator<TTaskTimerEnt>;
|
|
|
|
TThdTaskTimer = class(TTgThread)
|
|
private
|
|
bTimerOn_,
|
|
bUseActiveX_: Boolean;
|
|
dwSleep_: DWORD;
|
|
TimerEntList_: TList<TTaskTimerEnt>;
|
|
|
|
procedure OnEventNotifyTaskEntry(Sender: TObject; const Item: TTaskTimerEnt;
|
|
Action: TCollectionNotification);
|
|
protected
|
|
procedure Execute; override;
|
|
public
|
|
Constructor Create(dwSleep: DWORD = 1000; bUseActiveX: Boolean = false);
|
|
Destructor Destroy; override;
|
|
|
|
procedure AddTask(fnEvent: TNotifyEvent; dwInterval: DWORD; bActive: Boolean;
|
|
bInitProcess: Boolean = false{인터벌 전에 최초실행 할건지}); overload;
|
|
procedure AddTask(fnEvent: TThreadMethod; dwInterval: DWORD; bActive: Boolean;
|
|
bInitProcess: Boolean = false{인터벌 전에 최초실행 할건지}); overload;
|
|
procedure SetTask(fnEvent: TNotifyEvent; dwInterval: DWORD; bActive: Boolean);
|
|
function GetTaskActiveState(fnEvent: TNotifyEvent): Boolean;
|
|
|
|
procedure StartTimerThread;
|
|
procedure StopTimerThread;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Tocsg.Trace, Winapi.ActiveX;
|
|
|
|
{ TTgThread }
|
|
|
|
Constructor TTgThread.Create;
|
|
begin
|
|
Inherited Create(true);
|
|
|
|
WorkState_ := tsInit;
|
|
|
|
CS_ := TCriticalSection.Create;
|
|
|
|
nLastError_ := ERROR_SUCCESS;
|
|
bWorkStop_ := false;
|
|
bWorkCancel_ := false;
|
|
end;
|
|
|
|
Destructor TTgThread.Destroy;
|
|
begin
|
|
StopThread;
|
|
Inherited;
|
|
FreeAndNil(CS_);
|
|
end;
|
|
|
|
procedure TTgThread.Lock;
|
|
begin
|
|
CS_.Acquire;
|
|
end;
|
|
|
|
procedure TTgThread.Unlock;
|
|
begin
|
|
CS_.Release;
|
|
end;
|
|
|
|
procedure TTgThread.SetWorkStop(bVal: Boolean);
|
|
begin
|
|
Lock;
|
|
try
|
|
bWorkStop_ := bVal;
|
|
finally
|
|
Unlock;
|
|
end;
|
|
end;
|
|
|
|
function TTgThread.GetWorkStop: Boolean;
|
|
begin
|
|
Lock;
|
|
try
|
|
Result := bWorkStop_;
|
|
finally
|
|
Unlock;
|
|
end;
|
|
end;
|
|
|
|
function TTgThread.GetWorkCancel: Boolean;
|
|
begin
|
|
Lock;
|
|
try
|
|
Result := bWorkCancel_;
|
|
finally
|
|
Unlock;
|
|
end;
|
|
end;
|
|
|
|
procedure TTgThread.SetWorkState(aKind: TTgThreadState);
|
|
begin
|
|
Lock;
|
|
try
|
|
WorkState_ := aKind;
|
|
finally
|
|
Unlock;
|
|
end;
|
|
end;
|
|
|
|
function TTgThread.GetWorkState: TTgThreadState;
|
|
begin
|
|
Lock;
|
|
try
|
|
Result := WorkState_;
|
|
finally
|
|
Unlock;
|
|
end;
|
|
end;
|
|
|
|
procedure TTgThread.TerminatedSet;
|
|
begin
|
|
if Suspended then
|
|
Suspended := false; // 이게 true로 되어 있으면 제대로 Terminate가 되지 않아서 일케 체크 13_0911 14:56:28 kku
|
|
|
|
SetWorkStop(true);
|
|
end;
|
|
|
|
procedure TTgThread._Trace(const sLog: String; nLevel: Integer = 0);
|
|
begin
|
|
{$IFDEF TRACE_OBJ}
|
|
if Self <> nil then
|
|
TTgTrace.T(Format('%s :: %s', [ClassName, sLog]), nLevel);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TTgThread._Trace(const sFormat: String; const aArgs: array of const; nLevel: Integer = 0);
|
|
begin
|
|
{$IFDEF TRACE_OBJ}
|
|
TTgTrace.T(Format('%s :: %s', [ClassName, sFormat]), aArgs, nLevel);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TTgThread.TerminateWaitUntilEnd;
|
|
var
|
|
nTimeOut: Integer;
|
|
begin
|
|
if not FreeOnTerminate then
|
|
exit;
|
|
|
|
Terminate;
|
|
|
|
nTimeOut := 0;
|
|
while nTimeOut < 300 do
|
|
begin
|
|
Inc(nTimeOut);
|
|
Sleep(100);
|
|
if Terminated then
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
procedure TTgThread.StartThread;
|
|
begin
|
|
SetWorkStop(false);
|
|
|
|
if Suspended then
|
|
Suspended := false;
|
|
end;
|
|
|
|
procedure TTgThread.PauseThread;
|
|
begin
|
|
if not Suspended then
|
|
Suspended := true;
|
|
end;
|
|
|
|
procedure TTgThread.StopThread;
|
|
begin
|
|
SetWorkStop(true);
|
|
end;
|
|
|
|
procedure TTgThread.CancelWork;
|
|
begin
|
|
Lock;
|
|
try
|
|
bWorkCancel_ := true;
|
|
finally
|
|
Unlock;
|
|
end;
|
|
StopThread;
|
|
end;
|
|
|
|
{ TTgEventThread }
|
|
|
|
Constructor TTgEventThread.Create(const sEventName: String = 'TTgEventThread'; bSync: Boolean = false);
|
|
begin
|
|
Inherited Create;
|
|
|
|
hEvent_ := 0;
|
|
|
|
// sEventName_ := sEventName;
|
|
// 기존 단순 이벤트 이름 계속 사용하면...
|
|
// 이벤트 핸들이 제대로 닫히지 않거나 중복되면 GetLastError = 5(액세스 거부) 가 일어날 수 있다.
|
|
// 이런 현상을 최대한 줄이기 위해서 이벤트 이름을 유니크 하게 추가 보완 17_0330 10:25:24 sunk
|
|
sEvtName_ := sEventName + IntToStr(GetCurrentProcessId) + IntToStr(LONGLONG(Self));
|
|
|
|
bSync_ := bSync;
|
|
bUseActiveX_ := false;
|
|
end;
|
|
|
|
procedure TTgEventThread.DoEvent;
|
|
begin
|
|
if not GetWorkStop and (hEvent_ <> 0) then
|
|
begin
|
|
SetEvent(hEvent_);
|
|
end;
|
|
end;
|
|
|
|
procedure TTgEventThread.StartThread;
|
|
begin
|
|
if hEvent_ = 0 then
|
|
begin
|
|
hEvent_ := CreateEvent(nil, true, false, PChar(sEvtName_));
|
|
if hEvent_ = 0 then
|
|
begin
|
|
_Trace('StartThread() .. hEvent_ is 0 .., sEventName_ = %s, LastError = %d', [sEvtName_, GetLastError]);
|
|
end;
|
|
end;
|
|
|
|
Inherited;
|
|
end;
|
|
|
|
procedure TTgEventThread.StopThread;
|
|
begin
|
|
Inherited;
|
|
|
|
if hEvent_ <> 0 then
|
|
begin
|
|
SetEvent(hEvent_);
|
|
CloseHandle(hEvent_);
|
|
hEvent_ := 0;
|
|
end;
|
|
end;
|
|
|
|
procedure TTgEventThread.Execute;
|
|
begin
|
|
if bUseActiveX_ then CoInitialize(nil);
|
|
try
|
|
while not Terminated do
|
|
begin
|
|
if hEvent_ <> 0 then
|
|
begin
|
|
case WaitForSingleObject(hEvent_, INFINITE) of
|
|
WAIT_TIMEOUT : ;
|
|
WAIT_OBJECT_0 :
|
|
begin
|
|
ResetEvent(hEvent_);
|
|
if not GetWorkStop then
|
|
if bSync_ then
|
|
Synchronize(ProcessWorkEvent)
|
|
else
|
|
ProcessWorkEvent;
|
|
end;
|
|
end;
|
|
end;
|
|
Sleep(50);
|
|
end;
|
|
finally
|
|
if bUseActiveX_ then CoUninitialize;
|
|
end;
|
|
end;
|
|
|
|
{ TThdCloseForm }
|
|
|
|
Constructor TThdCloseForm.Create;
|
|
begin
|
|
Inherited Create;
|
|
CloseFromEnt_ := TList<PCloseFormEnt>.Create;
|
|
CloseFromEnt_.OnNotify := OnEntryNotify;
|
|
StartThread;
|
|
end;
|
|
|
|
Destructor TThdCloseForm.Destroy;
|
|
begin
|
|
FreeAndNil(CloseFromEnt_);
|
|
Inherited;
|
|
end;
|
|
|
|
procedure TThdCloseForm.OnEntryNotify(Sender: TObject; const Item: PCloseFormEnt; Action: TCollectionNotification);
|
|
begin
|
|
case Action of
|
|
cnAdded: ;
|
|
cnRemoved:
|
|
begin
|
|
// Finalize(Item^);
|
|
Dispose(Item);
|
|
end;
|
|
cnExtracted: ;
|
|
end;
|
|
end;
|
|
|
|
procedure TThdCloseForm.ClearEntry;
|
|
begin
|
|
CloseFromEnt_.Clear;
|
|
end;
|
|
|
|
procedure TThdCloseForm.AddEntry(const sClassName, sCaption: String);
|
|
var
|
|
p: PCloseFormEnt;
|
|
begin
|
|
New(p);
|
|
p.sClassName := sClassName;
|
|
p.sCaption := sCaption;
|
|
|
|
Lock;
|
|
try
|
|
CloseFromEnt_.Add(p);
|
|
finally
|
|
Unlock;
|
|
end;
|
|
end;
|
|
|
|
procedure TThdCloseForm.Execute;
|
|
var
|
|
hWindow: HWND;
|
|
enum: TEnumerator<PCloseFormEnt>;
|
|
begin
|
|
while not Terminated do
|
|
begin
|
|
Lock;
|
|
try
|
|
enum := CloseFromEnt_.GetEnumerator;
|
|
finally
|
|
Unlock;
|
|
end;
|
|
|
|
try
|
|
while enum.MoveNext do
|
|
begin
|
|
hWindow := FindWindow(PChar(enum.Current.sClassName), PChar(enum.Current.sCaption));
|
|
if hWindow <> 0 then
|
|
begin
|
|
// ShowWindow(hWindow, SW_HIDE);
|
|
SendMessage(hWindow, WM_CLOSE, 0, 0);
|
|
end;
|
|
end;
|
|
finally
|
|
enum.Free;
|
|
end;
|
|
Sleep(50);
|
|
end;
|
|
end;
|
|
|
|
{ TTaskTimerEnt }
|
|
|
|
Constructor TTaskTimerEnt.Create(fnEvent: TNotifyEvent; dwInterval: DWORD; bDefaultActive: Boolean);
|
|
begin
|
|
Inherited Create;
|
|
|
|
OwnerThread_ := nil;
|
|
fnEvent_ := fnEvent;
|
|
fnEvent2_ := nil;
|
|
dwTick_ := 0;
|
|
dwInterval_ := dwInterval;
|
|
bDefaultActive_ := bDefaultActive;
|
|
end;
|
|
|
|
Constructor TTaskTimerEnt.Create(aOwnerThread: TTgThread; fnEvent: TThreadMethod; dwInterval: DWORD; bDefaultActive: Boolean);
|
|
begin
|
|
Inherited Create;
|
|
|
|
OwnerThread_ := aOwnerThread;
|
|
fnEvent_ := nil;
|
|
fnEvent2_ := fnEvent;
|
|
dwTick_ := 0;
|
|
dwInterval_ := dwInterval;
|
|
bDefaultActive_ := bDefaultActive;
|
|
end;
|
|
|
|
Destructor TTaskTimerEnt.Destroy;
|
|
begin
|
|
Inherited;
|
|
end;
|
|
|
|
procedure TTaskTimerEnt.InitTask(dwTick: DWORD);
|
|
begin
|
|
if dwTick_ <> dwTick then
|
|
dwTick_ := dwTick;
|
|
end;
|
|
|
|
procedure TTaskTimerEnt.ProcessTask(dwTick: DWORD);
|
|
var
|
|
dwMilliSec: DWORD;
|
|
begin
|
|
if dwTick_ = 0 then
|
|
exit;
|
|
|
|
dwMilliSec := dwTick - dwTick_;
|
|
if dwMilliSec > dwInterval_ then
|
|
begin
|
|
if Assigned(fnEvent_) then
|
|
fnEvent_(Self);
|
|
if Assigned(fnEvent2_) and Assigned(OwnerThread_) then
|
|
OwnerThread_.Synchronize(fnEvent2_);
|
|
// fnEvent_ 수행중 dwTick_ 이걸 0으로 초기화 할 경우가 있다. (내부에서 타이머 끄기등)
|
|
if dwTick_ > 0 then
|
|
dwTick_ := dwTick;
|
|
end;
|
|
end;
|
|
|
|
{ TThdTaskTimer }
|
|
|
|
Constructor TThdTaskTimer.Create(dwSleep: DWORD = 1000; bUseActiveX: Boolean = false);
|
|
begin
|
|
Inherited Create;
|
|
|
|
bTimerOn_ := false;
|
|
bUseActiveX_ := bUseActiveX;
|
|
|
|
TimerEntList_ := TList<TTaskTimerEnt>.Create;
|
|
TimerEntList_.OnNotify := OnEventNotifyTaskEntry;
|
|
|
|
dwSleep_ := dwSleep;
|
|
end;
|
|
|
|
Destructor TThdTaskTimer.Destroy;
|
|
begin
|
|
StopTimerThread;
|
|
Inherited;
|
|
TimerEntList_.Clear;
|
|
FreeAndNil(TimerEntList_);
|
|
end;
|
|
|
|
procedure TThdTaskTimer.OnEventNotifyTaskEntry(Sender: TObject; const Item: TTaskTimerEnt;
|
|
Action: TCollectionNotification);
|
|
begin
|
|
case Action of
|
|
cnAdded : ;
|
|
cnRemoved : Item.Free;
|
|
cnExtracted : ;
|
|
end;
|
|
end;
|
|
|
|
procedure TThdTaskTimer.AddTask(fnEvent: TNotifyEvent; dwInterval: DWORD; bActive: Boolean; bInitProcess: Boolean = false);
|
|
var
|
|
TaskTimerEntry: TTaskTimerEnt;
|
|
begin
|
|
TaskTimerEntry := TTaskTimerEnt.Create(fnEvent, dwInterval, bActive);
|
|
Lock;
|
|
try
|
|
TimerEntList_.Add(TaskTimerEntry);
|
|
finally
|
|
Unlock;
|
|
end;
|
|
if bInitProcess and Assigned(fnEvent) then
|
|
fnEvent(TaskTimerEntry);
|
|
end;
|
|
|
|
procedure TThdTaskTimer.AddTask(fnEvent: TThreadMethod; dwInterval: DWORD; bActive: Boolean; bInitProcess: Boolean = false);
|
|
var
|
|
TaskTimerEntry: TTaskTimerEnt;
|
|
begin
|
|
TaskTimerEntry := TTaskTimerEnt.Create(Self, fnEvent, dwInterval, bActive);
|
|
Lock;
|
|
try
|
|
TimerEntList_.Add(TaskTimerEntry);
|
|
finally
|
|
Unlock;
|
|
end;
|
|
if bInitProcess and Assigned(fnEvent) then
|
|
Synchronize(fnEvent);
|
|
end;
|
|
|
|
procedure TThdTaskTimer.SetTask(fnEvent: TNotifyEvent; dwInterval: DWORD; bActive: Boolean);
|
|
var
|
|
enum: TTimerEntEnumerator;
|
|
begin
|
|
Lock;
|
|
try
|
|
enum := TimerEntList_.GetEnumerator;
|
|
finally
|
|
Unlock;
|
|
end;
|
|
|
|
try
|
|
while enum.MoveNext do
|
|
if @enum.Current.Event = @fnEvent then
|
|
begin
|
|
if bActive then
|
|
enum.Current.InitTask(GetTickCount)
|
|
else
|
|
enum.Current.InitTask(0);
|
|
enum.Current.dwInterval_ := dwInterval;
|
|
exit;
|
|
end;
|
|
finally
|
|
enum.Free;
|
|
end;
|
|
end;
|
|
|
|
function TThdTaskTimer.GetTaskActiveState(fnEvent: TNotifyEvent): Boolean;
|
|
var
|
|
enum: TTimerEntEnumerator;
|
|
begin
|
|
Result := false;
|
|
Lock;
|
|
try
|
|
enum := TimerEntList_.GetEnumerator;
|
|
finally
|
|
Unlock;
|
|
end;
|
|
|
|
try
|
|
while enum.MoveNext do
|
|
if @enum.Current.Event = @fnEvent then
|
|
begin
|
|
Result := enum.Current.Tick <> 0;
|
|
exit;
|
|
end;
|
|
finally
|
|
enum.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TThdTaskTimer.StartTimerThread;
|
|
var
|
|
enum: TTimerEntEnumerator;
|
|
dwTick: DWORD;
|
|
begin
|
|
if bTimerOn_ then
|
|
exit;
|
|
|
|
bTimerOn_ := true;
|
|
|
|
Lock;
|
|
try
|
|
enum := TimerEntList_.GetEnumerator;
|
|
finally
|
|
Unlock;
|
|
end;
|
|
|
|
try
|
|
dwTick := GetTickCount;
|
|
while enum.MoveNext do
|
|
if enum.Current.DefaultActive then
|
|
enum.Current.InitTask(dwTick);
|
|
finally
|
|
enum.Free;
|
|
end;
|
|
|
|
StartThread;
|
|
end;
|
|
|
|
procedure TThdTaskTimer.StopTimerThread;
|
|
var
|
|
enum: TTimerEntEnumerator;
|
|
begin
|
|
if not bTimerOn_ then
|
|
exit;
|
|
|
|
bTimerOn_ := false;
|
|
|
|
StopThread;
|
|
|
|
Lock;
|
|
try
|
|
enum := TimerEntList_.GetEnumerator;
|
|
finally
|
|
Unlock;
|
|
end;
|
|
|
|
try
|
|
while enum.MoveNext do
|
|
enum.Current.InitTask(0);
|
|
finally
|
|
enum.Free;
|
|
end;
|
|
|
|
PauseThread;
|
|
end;
|
|
|
|
procedure TThdTaskTimer.Execute;
|
|
var
|
|
dwTick: DWORD;
|
|
enum: TTimerEntEnumerator;
|
|
begin
|
|
if bUseActiveX_ then CoInitialize(nil);
|
|
try
|
|
while not Terminated do
|
|
begin
|
|
Sleep(dwSleep_);
|
|
|
|
try
|
|
if not GetWorkStop then
|
|
begin
|
|
dwTick := GetTickCount;
|
|
|
|
Lock;
|
|
try
|
|
enum := TimerEntList_.GetEnumerator;
|
|
finally
|
|
Unlock;
|
|
end;
|
|
|
|
try
|
|
while enum.MoveNext do
|
|
enum.Current.ProcessTask(dwTick);
|
|
finally
|
|
enum.Free;
|
|
end;
|
|
end;
|
|
except
|
|
//
|
|
end;
|
|
end;
|
|
finally
|
|
if bUseActiveX_ then CoUninitialize;
|
|
end;
|
|
end;
|
|
|
|
end.
|