Форум: "Прочее";
Текущий архив: 2008.05.11;
Скачать: [xml.tar.bz2];
Внизоцените пожалуйста код Найти похожие ветки
← →
@!!ex © (2008-03-25 21:19) [0]Сейчас встала задача распаралеливания вычислений. Она уже несколько раз вставала, но раньше удавалось ее обходить. Теперь вопрос встал ребром...
Написал вот модуль, для работы с потоками. Но так как в теме еще понимаю мало, хочеться узнать, насколько правильно я его написал.unit uThread;
interface
uses
Windows;
type
TCriticalSection = class
private
FSection:_RTL_CRITICAL_SECTION;
public
Constructor Create;
Destructor Destroy; override;
Procedure Enter;
Procedure Leave;
end;
TSemaphore = class
private
FSemaphore:Cardinal;
public
Constructor Create;
Destructor Destroy; override;
Procedure Enter;
Function TryEnter:boolean;
Procedure Leave(Count:integer = 1);
end;
TThread = class
protected
FStateTHRead:integer;
FIsTerminated:boolean;
FThread:integer;
FLock:TCriticalSection;
Function StateThread:integer;
Function StartThread(IsSuspend:boolean = true):boolean;
public
Constructor Create(IsSuspend:boolean = true);
Destructor Destroy; override;
Procedure Execute; virtual; abstract;
Procedure Wait;
Procedure Suspend;
Procedure Resume;
Procedure Terminate;
Function Terminated:boolean;
property Lock:TCriticalSection read FLock;
end;
implementation
{ TCriticalSection }
constructor TCriticalSection.Create;
begin
InitializeCriticalSection(FSection);
end;
destructor TCriticalSection.Destroy;
begin
DeleteCriticalSection(FSection);
inherited;
end;
procedure TCriticalSection.Enter;
begin
EnterCriticalSection(FSection);
end;
procedure TCriticalSection.Leave;
begin
LeaveCriticalSection(FSection);
end;
{ TSemaphore }
constructor TSemaphore.Create;
begin
FSemaphore := CreateSemaphore(nil, 0, 100000000, nil);
end;
destructor TSemaphore.Destroy;
begin
CloseHandle(FSemaphore);
inherited;
end;
procedure TSemaphore.Enter;
begin
WaitForSingleObject(FSemaphore, INFINITE);
end;
procedure TSemaphore.Leave(Count: integer);
begin
ReleaseSemaphore(FSemaphore, Count, nil);
end;
function TSemaphore.TryEnter: boolean;
begin
Result:=not WaitForSingleObject(FSemaphore, 0)=WAIT_TIMEOUT;
end;
const
ST_NONE = 0;
ST_SUSPEND = 1;
ST_RESUME = 2;
ST_STOP = 3;
function ObjectNewTHRead(lpThreadParameter: TThread): Integer; stdcall;
begin
lpThreadParameter.Execute();
lpThreadParameter.Lock.Enter();
lpThreadParameter.FStateTHRead := ST_STOP;
lpThreadParameter.Lock.Leave();
Result:=0;
end;
{ TThread }
constructor TThread.Create(IsSuspend: boolean);
begin
FIsTerminated := false;
if IsSuspend then
FStateTHRead:=ST_SUSPEND
else
FStateTHRead:=ST_RESUME;
if not StartThRead(IsSuspend) then
FStateTHRead:=ST_NONE;
end;
destructor TThread.Destroy;
begin
Terminate();
Wait();
inherited;
end;
procedure TThread.Resume;
begin
Lock.Enter();
if FStateTHRead = ST_SUSPEND then begin
ResumeThread(FTHRead);
FStateTHRead := ST_RESUME;
end;
Lock.Leave();
end;
function TThread.StartThread(IsSuspend: boolean): boolean;
var
dwTHID:cardinal;
begin
if IsSuspend then
FThread:=CreateThread(nil,100000,@ObjectNewTHRead,self,CREATE_SUSPENDED,dwTHID)
else
FThread:=CreateThread(nil,100000,@ObjectNewTHRead,self,0,dwTHID);
Result:=FThread<>0;
end;
function TThread.StateThread: integer;
begin
Lock.Enter();
Result := FStateTHRead;
Lock.Leave();
end;
procedure TThread.Suspend;
begin
Lock.Enter();
if FStateTHRead=ST_RESUME then begin
SuspendThread(FTHRead);
FStateTHRead := ST_SUSPEND;
end;
Lock.Leave();
end;
procedure TThread.Terminate;
begin
Lock.Enter();
FIsTerminated := TRUE;
Lock.Leave();
Resume();
end;
function TThread.Terminated: boolean;
begin
Lock.Enter();
Result := FIsTerminated;
Lock.Leave();
end;
procedure TThread.Wait;
begin
if StateTHRead()<>ST_NONE then begin
WaitForSingleObject(FTHRead,INFINITE);
CloseHandle(FTHRead);
end;
FStateTHRead := ST_NONE;
end;
end.
← →
Джо © (2008-03-25 21:20) [1]Даже не глядя в код, спрошу — а зачем создавать свой TCriticalSection, если оный уже есть в VCL?
← →
Джо © (2008-03-25 21:21) [2]И остальные тоже...
← →
@!!ex © (2008-03-25 21:23) [3]> а зачем создавать свой TCriticalSection, если оный уже есть
> в VCL?
Ну во-первых я не знал что в VCL это уже есть. :)
А во-вторых - использование VCL нельзя использовать, т.к. проект планируется компилять под FPC, и возможно переписывать под C++.
← →
Informer (2008-03-25 21:31) [4]> @!!ex © (25.03.08 21:23) [3]
А почему бы сразу не писАть на С++?
← →
DVM © (2008-03-25 21:32) [5]
> procedure TThread.Terminate;
> begin
> Lock.Enter();
> FIsTerminated := TRUE;
> Lock.Leave();
> Resume();
> end;
ИМХО критические секции тут вообще лишние.
Вот предложу такой вариант:
unit Threads;
interface
uses Windows;
////////////////////////////////////////////////////////////////////////////////
// TThread
////////////////////////////////////////////////////////////////////////////////
type
TThreadMethod = procedure of object;
TThreadPriority = (tpIdle, tpLowest, tpLower, tpNormal, tpHigher, tpHighest,
tpTimeCritical);
const
Priorities: array [TThreadPriority] of Integer =
(THREAD_PRIORITY_IDLE,
THREAD_PRIORITY_LOWEST,
THREAD_PRIORITY_BELOW_NORMAL,
THREAD_PRIORITY_NORMAL,
THREAD_PRIORITY_ABOVE_NORMAL,
THREAD_PRIORITY_HIGHEST,
THREAD_PRIORITY_TIME_CRITICAL);
type
TThread = class
private
FHandle: THandle;
FThreadID: THandle;
FTerminated: Boolean;
FSuspended: Boolean;
FFreeOnTerminate: Boolean;
FFinished: Boolean;
FReturnValue: DWORD;
function GetPriority: TThreadPriority;
procedure SetPriority(Value: TThreadPriority);
procedure SetSuspended(Value: Boolean);
protected
procedure Execute; virtual; abstract;
property ReturnValue: DWORD read FReturnValue write FReturnValue;
property Terminated: Boolean read FTerminated;
public
constructor Create(CreateSuspended: Boolean);
destructor Destroy; override;
procedure Resume;
procedure Suspend;
procedure Terminate;
procedure WaitFor;
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;
end;
implementation
////////////////////////////////////////////////////////////////////////////////
// TThread
////////////////////////////////////////////////////////////////////////////////
function ThreadProc(Thread: TThread): DWORD;
var
FreeThread: Boolean;
begin
Thread.Execute;
FreeThread := Thread.FFreeOnTerminate;
Result := Thread.FReturnValue;
Thread.FFinished := True;
if FreeThread then Thread.Free;
EndThread(Result);
end;
//------------------------------------------------------------------------------
constructor TThread.Create(CreateSuspended: Boolean);
var
Flags: DWORD;
begin
inherited Create;
FSuspended := CreateSuspended;
Flags := 0;
if CreateSuspended then Flags := CREATE_SUSPENDED;
FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), Flags, FThreadID);
end;
//------------------------------------------------------------------------------
destructor TThread.Destroy;
begin
if FHandle <> 0 then CloseHandle(FHandle);
inherited Destroy;
end;
//------------------------------------------------------------------------------
function TThread.GetPriority: TThreadPriority;
var
P: Integer;
I: TThreadPriority;
begin
P := GetThreadPriority(FHandle);
Result := tpNormal;
for I := Low(TThreadPriority) to High(TThreadPriority) do
if Priorities[I] = P then Result := I;
end;
//------------------------------------------------------------------------------
procedure TThread.SetPriority(Value: TThreadPriority);
begin
SetThreadPriority(FHandle, Priorities[Value]);
end;
//------------------------------------------------------------------------------
procedure TThread.SetSuspended(Value: Boolean);
begin
if Value <> FSuspended then
if Value then
Suspend
else
Resume;
end;
//------------------------------------------------------------------------------
procedure TThread.Suspend;
begin
FSuspended := True;
SuspendThread(FHandle);
end;
//------------------------------------------------------------------------------
procedure TThread.Resume;
begin
if ResumeThread(FHandle) = 1 then FSuspended := False;
end;
//------------------------------------------------------------------------------
procedure TThread.Terminate;
begin
FTerminated := True;
end;
//------------------------------------------------------------------------------
procedure TThread.WaitFor;
begin
WaitForSingleObject(FHandle, INFINITE);
end;
//------------------------------------------------------------------------------
end.
← →
ага (2008-03-25 21:33) [6]
> constructor TThread.Create(IsSuspend: boolean);
> begin
> FIsTerminated := false;
> procedure TThread.Terminate;
> begin
> Lock.Enter();
> FIsTerminated := TRUE;
Нафига??
← →
ага (2008-03-25 21:36) [7]
> DVM © (25.03.08 21:32) [5]
> procedure TThread.Terminate;
> begin
> FTerminated := True;
> end;
Аналогично - нафига?
← →
@!!ex © (2008-03-25 21:37) [8]> [6] ага (25.03.08 21:33)
В Execute следишь за состоянием FIsTerminated, если true - выходим.
> ИМХО критические секции тут вообще лишние.
А вдруг присвоение не атомарное? :)
← →
DVM © (2008-03-25 21:40) [9]
> ага (25.03.08 21:36) [7]
> Аналогично - нафига?
Не понимаю вопроса. Что смущает? Выставляется флаг завершения, процедура, которая будет крутиться в методе Execute должна проверять состояние флага.
> А вдруг присвоение не атомарное? :)
Даже если не атомарное ничего не случится.
← →
@!!ex © (2008-03-25 21:40) [10]> [4] Informer (25.03.08 21:31)
А смысл?
Я на дельфи быстрее пишу и качесвтенней.
← →
DVM © (2008-03-25 21:43) [11]
> Я на дельфи быстрее пишу и качесвтенней.
На Delphi вообще все пишут качественнее. Я вот тут месяц с лишним писал на си (без плюсов) для некоего девайса (даже на неком подмножестве турбо си). Отсутствие типа строка просто убивало. Мало того, что неудобно, так еще и очень очень внимательным надо быть, чтобы не дай бог не вылезти за границы массива или \0 не забыть где.
← →
ага (2008-03-25 21:46) [12]
> @!!ex © (25.03.08 21:37) [8]
> DVM © (25.03.08 21:40) [9]
У tthread уже есть terminated. Зачем самодеятельность?
← →
DVM © (2008-03-25 21:49) [13]
> У tthread уже есть terminated. Зачем самодеятельность?
У кого есть? Этот код рассматривается как раз как альтернатива классу TThread из Classes. К тому же terminated стандартного класса TThread это не то же самое, что terminate того же класса.
← →
ага (2008-03-25 21:54) [14]
> DVM © (25.03.08 21:49) [13]
>
>
> > У tthread уже есть terminated. Зачем самодеятельность?
>
>
> У кого есть? Этот код рассматривается как раз как альтернатива
> классу TThread из Classes. К тому же terminated стандартного
> класса TThread это не то же самое, что terminate того же
> класса.
terminated - тот же флаг. Terminate тоже выставляет значение этого флага, но никто не
мешает вручную безболезненно выставлять этот флаг аналогично [0] и [5].
И зачем вообще велосипеды изобретать?
← →
Игорь Шевченко © (2008-03-25 21:57) [15]
> Написал вот модуль, для работы с потоками. Но так как в
> теме еще понимаю мало
А TThread использовать религия не велит ?
← →
DVM © (2008-03-25 21:59) [16]
> но никто не
> мешает вручную безболезненно выставлять этот флаг аналогично
> [0] и [5].
Мешает, ибо:
property Terminated: Boolean read FTerminated;
> И зачем вообще велосипеды изобретать?
Затем, чтобы использовать FreePascal и не использовать модуль Classes например. К тому же можно создать более гибкую и более удобную реализацию обертки под поток, чем в Classes.
← →
ага (2008-03-25 22:09) [17]
> DVM © (25.03.08 21:59) [16]
>
>
> > но никто не
> > мешает вручную безболезненно выставлять этот флаг аналогично
> > [0] и [5].
>
> Мешает, ибо:
Не мешает.
И, к стати, в FCL тоже есть tthread.
← →
Mystic © (2008-03-25 22:14) [18]
> А во-вторых - использование VCL нельзя использовать, т.к.
> проект планируется компилять под FPC, и возможно переписывать
> под C++.
Насколько я знаю, под FPC есть классы, аналогичные VCL. Причем реализованные как под Win, так и под *nix. В крайнем случае можно скопировать реализацию из VCL, или взять ее за основу :)
Во-вторых, есть много различных библиотек под C++, где это все уже реализовано, да еще в кроссплатформенном виде :)
В-третьих, мало кому интересны обвертки над VCL. Вот если бы ты описал свою задачу и с помочу чего и как т ыхочешь распараллелить.. А так... Это техника...
В четвертых, Lock.Enter и Lock.Leave надо оборачивать в try..finally
В пятых, функцией CreateThread пользоваться низзя, надо использовать BeginThread. Основная причина в том, что BeginThread устанавливает переменную IsMultyThread в True. А она используется, например, в менеджере памяти (надо или не надо входить в критическую секцию). Поэтому могут быть труднонаходимые проблемы. Конечно, если код потока не испольуется строк и других объектов с автоматическим управлением памятью, то можно и CreateThread вызвать...
В шестых, константы состояния потока закрыты (implementation), а функция StateThread обкрыта (protected).
В седьмых, CloseHandle для потока лучше перенести в деструктор. Теоретически (1) поток можно ждать и после его завершения, (2) Wait могут вызвать разные потоки
Ну и далее общее пожелание:
>> DVM © (25.03.08 21:32) [5]
Очень часто поток валится из-за исключения. В этом случае он просто убивается и все. В твоем случае ты об этом так и не узнаешь. Посему правильнее было бы написать:
function ThreadProc(Thread: TThread): DWORD;
var
FreeThread: Boolean;
begin
try
Thread.Execute;
except
on E: Exceptions do
Windows.MessageBox(...); // Хотя-бы вывести отладочное сообщение
// Другая нужная обработка
end;
try
FreeThread := Thread.FFreeOnTerminate;
Result := Thread.FReturnValue;
Thread.FFinished := True;
if FreeThread then Thread.Free;
EndThread(Result);
except
on E: Exceptions do
// А сюда мы можем попасть если некто сделал Free() данному потоку в момент его работы.
// Тоже желательно провести диагностику
end;
end;
← →
DVM © (2008-03-25 22:15) [19]
> Не мешает.
Я тебе привел строчку из класса TTread модуля Classes.
property Terminated: Boolean read FTerminated;
Из нее следует, что свойство Terminated только для чтения. И создав экземпляр этого класса, ты не сможешь извне менять состояние свойства напрямую, а только через метод Terminate. Неужели непонятно?
Ясен пень, что можно написать и по другому и вооюще несколькими способами. Но так уж повелось, что некоторые объекты имеют свойства и методы меняющие эти свойства. Иногда даже если свойства не только для чтеня. Например свойство Active и Connect у оберток под сокеты.
> И, к стати, в FCL тоже есть tthread.
Ты это не мне доказывай, а автору вопроса, меня вообще и стандартный TThread устраивает.
← →
Игорь Шевченко © (2008-03-25 22:22) [20]
> и не использовать модуль Classes например
А что, использование модуля Classes considered harmful ?
← →
ага (2008-03-25 22:24) [21]
> DVM © (25.03.08 22:15) [19]
>
>
> > Не мешает.
>
> Я тебе привел строчку из класса TTread модуля Classes.
А нет у меня D. По памяти пишу. И, на сколько помню - в terminate ничего кроме
выставления terminated=true не делается. Этого достаточно.
> Ты это не мне доказывай, а автору вопроса
Я никому ничего не доказываю. Просто дискуссия.
← →
DVM © (2008-03-25 22:29) [22]
> Игорь Шевченко © (25.03.08 22:22) [20]
>
> > и не использовать модуль Classes например
>
>
> А что, использование модуля Classes considered harmful
Какой Classes может быть под FreePascal? Даже, если он там и скомпилируется, то все равно это нарушение лицензии.
← →
Игорь Шевченко © (2008-03-25 22:31) [23]DVM © (25.03.08 22:29) [22]
Я снес FreePascal вместе с lazarus, так что ничего не могу сказать на этот предмет. А что, и модуля SysUtils там нету ? :)
← →
DVM © (2008-03-25 22:34) [24]
> А что, и модуля SysUtils там нету ? :)
Я понятия не имею, не использовал FreePascal толком. Lazarus же вообще оставил впечатление жуткой недоработки. Но тем не менее они есть и, в отличие от Delphi могут компилировать под разные платформы, в том числе и под 64 bit.
← →
Mystic © (2008-03-25 22:38) [25]
>
> Какой Classes может быть под FreePascal? Даже, если он
> там и скомпилируется, то все равно это нарушение лицензии.
Свой собственный, немного расширенный, портированный под Linux.
← →
ага (2008-03-25 22:39) [26]
> DVM © (25.03.08 22:29) [22]
>
> Какой Classes может быть под FreePascal?
Поставь уже FPC и посмотри какой.
← →
DVM © (2008-03-25 22:42) [27]
> Свой собственный
Свой понятно, что там есть. Но речь, насколько я понял, изначально шла о том, чтобы одно и то же приложение могло с одинаковым успехом компилироваться и в Delphi и во FreePascal. Такое наверняка проще обеспечить, если создать свою обертку под поток, а не использовать возможно сильно отличающиеся классы из разных модулей Classes.
← →
DVM © (2008-03-25 22:44) [28]
> Поставь уже FPC и посмотри какой.
Зачем мне на него смотреть. Ты просто не понял моей фразы. Надо читать как "Какой модуль Classes из Delphi может быть во FreePascal".
← →
ага (2008-03-25 22:52) [29]
> DVM © (25.03.08 22:44) [28]
Из D? портированный... Можно сказать самодельный.
Там есть и tstream и tstrings и tstringlist и почти все, что есть в classes delphi.
Почти-это имхо, потому что я не особо интересуюсь и всех тонкостей не знаю.
← →
DiamondShark © (2008-03-25 23:53) [30]
> К тому же можно создать более гибкую и более удобную реализацию
> обертки под поток
Обёртки для таких примитивных сущностей -- это верх маразма.
А использование слова-паразита "гибкую" -- тревожный симптом повышенной толерантности к маркетоложеству.
Попытка создания "гибкости" вокруг примитивной вещи приводит к нагромождению кода, к "протекающим абстракциям" и, в конце концов, к потере той самой "гибкости", за которую боролись.
← →
DVM © (2008-03-26 00:01) [31]
> Обёртки для таких примитивных сущностей -- это верх маразма.
Значит в борланде одни маразматики.
> Попытка создания "гибкости" вокруг примитивной вещи
Насчет примитивности задачи создания обертки для потока не согласен категорически.
> Попытка создания "гибкости" вокруг примитивной вещи приводит
> к нагромождению кода
Гибкость и размер кода никак не связаны между собой.
> А использование слова-паразита "гибкую"
Первый раз слышу о таком слове "паразите".
← →
DVM © (2008-03-26 00:05) [32]
> Обёртки для таких примитивных сущностей -- это верх маразма.
У меня почему то складывается такое ощущение, что вы никогда не работали над многопоточными риложениями. Странные рассуждения какие то.
← →
Anatoly Podgoretsky © (2008-03-26 00:40) [33]> DVM (25.03.2008 22:15:19) [19]
> Из нее следует, что свойство Terminated только для чтения. И создав экземпляр этого класса, ты не сможешь извне менять состояние свойства напрямую, а только через метод Terminate.
Что бы не выстрелить себе в ногу.
← →
Anatoly Podgoretsky © (2008-03-26 00:43) [34]> DiamondShark (25.03.2008 23:53:30) [30]
Красиво излагаешь.
← →
DiamondShark © (2008-03-26 12:29) [35]
> DVM © (26.03.08 00:01) [31]
>
> > Обёртки для таких примитивных сущностей -- это верх маразма.
>
> Значит в борланде одни маразматики.
Странный вывод. Ви, таки, намекаете, что Борланд не написал ничего, кроме обёрток над простыми вещами?
> > Попытка создания "гибкости" вокруг примитивной вещи
>
> Насчет примитивности задачи создания обертки для потока
> не согласен категорически.
Будете смеяться, но я тоже. Задачу можно себе наворотить как угодно, было бы желание. Кто ж тут спорит?
Но, видите ли, я нигде не говорил о примитивности задачи создания обертки, я говорил о примитивной вещи. Поток -- вещь примитивная (говорим, конечно, не о потрохах ядра, а об уровне прикладного ПО). Есть возражения?
> Гибкость и размер кода никак не связаны между собой.
Увы, связаны. Даже у очень простой вещи может быть множество сценариев использования. Гибкая обёртка должна предусматривать их либо все, либо большинство, либо, хотя бы, значительную часть, иначе она уже не будет гибкой, или не будет обёрткой. Если обёртка не прусматривает какого-либо интересного сценария, ей будет невозможно пользоваться, потому что хорошая непротекающая обёртка тщательно скрывает детали реализации. Или обёртка вынуждена предоставлять какие-нибудь хуки-хаки, вроде выставления public-свойством Handle оборачиваемого объекта. И то и другое оборачивается мусорным кодом: либо неиспользуемым библиотечным, либо пользовательскими костылями.
А если оборачиваемая сущность достаточно проста (как в случае потока), то обёртка, в наименее патологическом случае, вырождается в пересказ низлежащего API другими словами. Но в этом случае теряется сам смысл обертки: никакая новая абстракция не вводится, ни объём, ни сложность прикладного кода не меняются (хе-хе;) если не увеличиваются). Посмотрите, чего полезного вносит использование класса TThread вместо BeginThread? Да ничего.
> Первый раз слышу о таком слове "паразите".
Да ладно! Сплошь и рядом слышатся уверения, что-де такой-то программный продукт "гибкий". Как правило, в переводе на русский это означает что продукт монстроузный, с мутной логикой и требующий обработки напильником перед сколь-нибудь продуктивным использованием.
> У меня почему то складывается такое ощущение, что вы никогда
> не работали над многопоточными риложениями. Странные рассуждения
> какие то.
У меня, почему-то, SKLадывается ощущение, что Вы, таки, решили перейти на личности, не въехав толком в рассуждения.
← →
@!!ex © (2008-03-26 12:53) [36]> Увы, связаны. Даже у очень простой вещи может быть множество
> сценариев использования. Гибкая обёртка должна предусматривать
> их либо все, либо большинство, либо, хотя бы, значительную
> часть, иначе она уже не будет гибкой, или не будет обёрткой.
> Если обёртка не прусматривает какого-либо интересного сценария,
> ей будет невозможно пользоваться, потому что хорошая непротекающая
> обёртка тщательно скрывает детали реализации. Или обёртка
> вынуждена предоставлять какие-нибудь хуки-хаки, вроде выставления
> public-свойством Handle оборачиваемого объекта. И то и другое
> оборачивается мусорным кодом: либо неиспользуемым библиотечным,
> либо пользовательскими костылями.
> А если оборачиваемая сущность достаточно проста (как в случае
> потока), то обёртка, в наименее патологическом случае, вырождается
> в пересказ низлежащего API другими словами. Но в этом случае
> теряется сам смысл обертки: никакая новая абстракция не
> вводится, ни объём, ни сложность прикладного кода не меняются
> (хе-хе;) если не увеличиваются). Посмотрите, чего полезного
> вносит использование класса TThread вместо BeginThread?
> Да ничего.
Да ун ерунду говорите.
Интерфейс нужен хотя бы для того, чтобы избавиться от процедурного программирования.
ООП нагляднее однако. Поток легче контролировать. Нигде не надо хранить идентефикатор потока и т.д.
← →
Игорь Шевченко © (2008-03-26 13:19) [37]
> Да ун ерунду говорите.
> Интерфейс нужен хотя бы для того, чтобы избавиться от процедурного
> программирования.
> ООП нагляднее однако. Поток легче контролировать. Нигде
> не надо хранить идентефикатор потока и т.д.
Это вы ерунду говорите. Уж извините, но это так.
← →
Mystic © (2008-03-26 13:20) [38]> Но речь, насколько я понял, изначально шла о том, чтобы
> одно и то же приложение могло с одинаковым успехом компилироваться
> и в Delphi и во FreePascal.
Classes во FreePascal совместим с Delphi. Обратное, вообще говоря, не верно.
> Обёртки для таких примитивных сущностей -- это верх маразма.
Цели обверток:
(1) Придать программе единообразную структуру. Если у нас используются только объекты, то обращения к нативному API немного режут глаз.
(2) Завязка на кроссплатформенность. Если остальной код никак не завязан на особенности OS, то потратил вечер на написание обверток, то в будущем для портирования под другую OS мне понадобится тоже один вечер :)
(3) Type mapping. Постоянные приведения от string к PChar захламляют код. Точно так же и возвращаемые значения
(4) В обвертках также реализовываются наиболее часто встречающиеся сценарии использования объектов. Например, ты сравнивал TThread и BeginThread. Хорошо, отвечу на вопрос, какие преимущества дает стандартный TThread:
(a) Контекст потока. Внутри потомка класса TThread мы можем объявить кучу полей, которые будут в дальнейшем легко доступны во всех вызовах методов TThread. В случае BeginThread контекст прийдется создавать самому: определять структуру, объявлять переменную ctx типа указанной структуры, при всех вызовах передавать эту переменную, любое обращение к переменной контекста предварятьctx.
и т. п.
(b) Метод Synchronize
(c) Стандартный механизм для прерывания вычислений потока. Конечно, я бы предпочел возможность передачи различных сигналов потоку (например, terminate), после чего поток мог бы возбудить исключение, которое я бы обработал в Execute. Или написать функцию вроде RaiseExceptionInThread(Thread: THandle; E: Exception). Но все равно стандартный часто используемый механизм есть.
← →
Eraser © (2008-03-26 13:22) [39]
> DiamondShark © (26.03.08 12:29) [35]
да ООП это вообще зло, а C# - недоязык )
← →
Игорь Шевченко © (2008-03-26 13:27) [40]"ОО-языки упрощают абстракцию, возможно, даже слишком ее упрощают. Они поддерживают создание структур с большим количеством связующего кода и сложными уровнями.
Это может оказаться полезным в случае, если предметная область является
действительно сложной и требует множества абстракций, и вместе с тем такой подход может обернуться неприятностями, если программисты реализуют простые вещи сложными способами, просто потому что им известны эти способы и они умеют ими пользоваться.
Все ОО-языки несколько сколнны "втягивать" программистов в ловушку избыточной иерархии. Чрезмерное количество уровней разрушает прозрачность: крайне затрудняется их просмотр и анализ ментальной модели, которую по существу реализует код. Всецело нарушаются правила простоты, ясности и прозрачности, а в результате код наполняется скрытыми ошибками и создает постоянные проблемы при сопровождении.
Данная тенденция, вероятно, усугубляется тем, что множество курсов по
программированию преподают громоздкую иерархию как способ удовлетворения правила представления. С этой точки зрения множество классов приравнивается к внедрению знаний в данные. Проблема данного подхода заключается в том, что слишком часто "развитые данные" в связующих уровнях фактически не относятся у какому-либо естественному объекту в области действия программы - они предназначены только для связующего уровня.
Одной из причин того, что ОО-языки преуспели в большинстве характерных для них предметных областей (GUI-интерфейсы, моделирование, графические средства), возможно, является то, что в этих областях относительно трудно неправильно определить онтологию типов. Например, в GUI-интерфейсах и графических средствах присутствует довольно естественное соотвествие между манипулируемыми визуальными объектами и классами. Если выясняется, что создается большое количество классов, которые не имеют очевидного соответствия с тем, что
происходит на экране, то, соотвественно, легко заметить, что связующий уровень стал слишком большим."
(с) Эрик Реймонд, "Искусство программирования для Unix"
← →
Mystic © (2008-03-26 13:29) [41]> да ООП это вообще зло
Иногда, глядя на некоторые ООП архитектуры, я тоже думаю, что ООП это зло :) Возникает такое ООП-спагетти, что никакое goto с ним не может сравнится :)
← →
@!!ex © (2008-03-26 13:44) [42]
> [37] Игорь Шевченко © (26.03.08 13:19)
В данном случае обертка упрощает работу с потоками.
ИМХО солянка из процедурно-ООП программирования - это гадость та еще...
> [40] Игорь Шевченко © (26.03.08 13:27)
Это скорее относится не к непосредственно использованию ООП, а к наследованию множества потомков, чатсь из которых не несут функционала, а лишь служат прослойкой.
← →
Игорь Шевченко © (2008-03-26 13:46) [43]@!!ex © (26.03.08 13:44) [42]
Я специально процитирую еще раз:
> вместе с тем такой подход может обернуться неприятностями,
> если программисты реализуют простые вещи сложными способами,
> просто потому что им известны эти способы и они умеют ими
> пользоваться
← →
DVM © (2008-03-26 13:46) [44]
> DiamondShark © (26.03.08 12:29) [35]
> Странный вывод. Ви, таки, намекаете, что Борланд не написал
> ничего, кроме обёрток над простыми вещами?
Но и оберток над простыми вещами он написал множество. А так как по Вашему те, кто пишет обертки над простыми вещами = маразматики, то в борланде работают маразматики. Да и в MS тоже. А то, что маразматики написали и более сложные вещи это вообще к рассуждениям не относится, т.к. никто не утверждает, что маразматик не способен к созданию сложных вещей. Логично?
> Поток -- вещь примитивная (говорим, конечно, не о потрохах
> ядра, а об уровне прикладного ПО). Есть возражения?
Есть. Непонятно вообще как измерить примитивность данной вещи. О потрохах Вы предпочитаете не упоминать, об далеко не примитивном правильном применении потоков похоже тоже. Да, создать поток элементарно (и описание функции, создающей поток тоже примитивно). Но это ли главное?
Так что, похоже, о примитивности или нет потоков можно говорить только в свете их использования в программе. А это уже не так тривиально, как кажется на первый взгляд. Соответственно и написание грамотной обертки для этого дела тоже задача не совсем тривиальная.
> Да ладно! Сплошь и рядом слышатся уверения, что-де такой-
> то программный продукт "гибкий".
Если Вам не нравится слово гибкий, то давайте его заменим на удобный.
Использовать класс для контроля над потоком все же удобнее в Delphi, чем вызывать чистые функции WinAPI. Или не так?
> У меня, почему-то, SKLадывается ощущение, что Вы, таки,
> решили перейти на личности, не въехав толком в рассуждения.
>
Без обид. Мне так показалось.
← →
@!!ex © (2008-03-26 14:10) [45]> [43] Игорь Шевченко © (26.03.08 13:46)
Наличие своего интерфейса для потока НЕ УСЛОЖНЯЕТ, а упрощает.
Позволяет не заморачиваться с хэндлом потока, позволяет реализовать стандартные, общие вещи, типа Wait, Resume, Terminate, позволяет не заботиться о Callback процедуре, поскольку на уровне наследника TThread и неизвестно ни о какой процедуре.
Где усложнение?
Когда по коду разбросаны вызовы WinAPI кода, да еще в перемешку с *nix кодом, с кучей дефайнов - вот это - усложнение.
А вот если делать кучу промежуточных интерфейсов, просто потому что это можно, которые не используются - это и будет то, о чем писал Реймонд.
← →
DVM © (2008-03-26 14:11) [46]
> Это скорее относится не к непосредственно использованию
> ООП, а к наследованию множества потомков, чатсь из которых
> не несут функционала, а лишь служат прослойкой.
Прослойки нужны и важны все же. Только их надо к месту создавать. Там где может появиться что-то общее для всех потомков и то, чего не должно быть в предке, всегда легче подправить прослойку, чем добавлять ее и потом править всех потомков.
← →
Mystic © (2008-03-26 14:19) [47]> Позволяет не заморачиваться с хэндлом потока, позволяет
> реализовать стандартные, общие вещи, типа Wait, Resume,
> Terminate, позволяет не заботиться о Callback процедуре,
> поскольку на уровне наследника TThread и неизвестно ни
> о какой процедуре.
В данном конкретном случае ты сам определил, что тебе нужно. В то же время твоя библиотека менее мощная, чем использование напрямую API. Сообще, я бы Handle потока (кстати, использовал бы тип THandle) сделал readonly public хотя бы для того, чтобы можно было его использовать в функции WaitForMultiplyObjects. Вообще, скрывать доступ к самому низкому уровню не очень хорошая идея. Лучше оставить возможность для конечного пользователя поковыряться в ухе отверкой, если этого будут требовать обстоятельства.
← →
DiamondShark © (2008-03-26 14:19) [48]
> @!!ex © (26.03.08 12:53) [36]
> Интерфейс нужен хотя бы для того, чтобы избавиться от процедурного
> программирования.
А что оно мне такого плохого сделало, что надо от него избавляться?
Да и не избавиться от него ООПом, там призрак ПП тебя постоянно преследовать будет ;)
Избавляться надо не от процедурного программирования, а от низкоуровневых сущностей, а здесь ООП даёт только иллюзию избавления, постоянно подкладывая грабли сокрытой сложности и дырявых абстракций. Избавление нам дадуд неимперативные языки.
> ООП нагляднее однако. Поток легче контролировать.
Ой-вэй.
Берём первую попавшуюся "наглядность" -- TThread.Terminate. Ух ты! Как его легко контролировать! А вот дулечки. Успешность контролирования потока с помощью "наглядного" Terminate оказывается густо замешаной на особенностях реализации Execute: проверяется там Terminated, али на него известную крепёжную деталь забили... "Опаньки! Менты!" Здравствуй, дырявая абстракция.
> Нигде не надо хранить идентефикатор потока и т.д.
Детсад. См. далее.
> Mystic © (26.03.08 13:20) [38]
> Цели обверток:
> (1) Придать программе единообразную структуру. Если у нас
> используются только объекты, то обращения к нативному API
> немного режут глаз.
> (2) Завязка на кроссплатформенность. Если остальной код
> никак не завязан на особенности OS, то потратил вечер на
> написание обверток, то в будущем для портирования под другую
> OS мне понадобится тоже один вечер :)
> (3) Type mapping. Постоянные приведения от string к PChar
> захламляют код. Точно так же и возвращаемые значения
Это резонно, если удалось избежать проблем.
> (4) В обвертках также реализовываются наиболее часто встречающиеся
> сценарии использования объектов.
Примечание: по мнению разработчиков обёртки ;)
Я вот ни разу не пользовался ни Synchronize, ни шаблоном while not Terminated do.
У меня были другие сценарии.
> Например, ты сравнивал
> TThread и BeginThread. Хорошо, отвечу на вопрос, какие преимущества
> дает стандартный TThread:
> (a) Контекст потока. Внутри потомка класса TThread мы
> можем объявить кучу полей, которые будут в дальнейшем легко
> доступны во всех вызовах методов TThread. В случае BeginThread
> контекст прийдется создавать самому: определять структуру,
> объявлять переменную ctx типа указанной структуры, при
> всех вызовах передавать эту переменную, любое обращение
> к переменной контекста предварять ctx. и т. п.
Сомнительное преимущество. Какая разница, напишу я класс контекста с нуля, или забабахаю поля контекста в наследника TThread?
> (b) Метод Synchronize
Чтоб он сдох.
Предъявляет требования к окружению и вносит нафиг не нужные зависимости. Кроме того, документация про него врёт:
"Synchronize causes the call specified by Method to be executed using the main VCL thread"
А на самом деле (в реализации Дельфи 5), Method исполняется не в main VCL thread, а в потоке, вызвавшем коструктор данного экземпляра TThread, да и то при условии, что в нём есть message loop.
А в Дельфи 7 (насколько я помню) Synchronize вообще представляет собой жуткий ахтунг, завязаный на поддерку от Forms. Бр-рр...
> (c) Стандартный механизм для прерывания вычислений потока.
Который
а) фиговый
б) накладывает ограничения на Execute
в) в силу б) непригоден для оперирования абстрактными TThread, т.к. требует эзотерического знания о деталях реализации.
> Eraser © (26.03.08 13:22) [39]
>
> да ООП это вообще зло, а C# - недоязык )
Не перевирай классиков ;) Это C++ -- недоязык.
А про ООП, применённое без фанатизма, ничего плохого сказать не могу.
← →
mephasm (2008-03-26 14:32) [49]DiamondShark © (26.03.08 14:19) [48]
>Избавление нам дадуд неимперативные языки.
Да, понимание этого приходит после знакомства с first class functions и closures. Но вот незадача. К примеру, нравится мне платформа Windows, так как лучше всего я ее знаю и распространена она весьма. Однако видели ли вы где-нибудь доступную реализацию Lisp или Scheme для windows с native threads?
А доступную реализацию C++ для windows найти не проблема. И посему приходится именно этот адский переязык использовать для реальных end-user приложений, и другого выхода не предвидится. Поэтому не в этой жизни они нам дадут.
← →
@!!ex © (2008-03-26 14:34) [50]> [48] DiamondShark © (26.03.08 14:19)
Ага. Давайте везде писать вот так:{$IFDEF WINDOWS}
if stNONE<>StateTHRead then begin
WaitForSingleObject(THRead,INFINITE);
CloseHandle(THRead);
end;
{$ENDIF}
{$IFDEF UNIX}
if stNONE<>StateTHRead then
pthread_join(THRead,nil);
{$ENDIF}
Вместо:
Thread1.Wait;
Thread2.Wait;
Замечательное упрощение.
← →
Mystic © (2008-03-26 14:35) [51]> Который
> а) фиговый
> б) накладывает ограничения на Execute
> в) в силу б) непригоден для оперирования абстрактными TThread,
> т.к. требует эзотерического знания о деталях реализации.
Предложи лучше. Например, у меня есть сложное математическое вычисление, которое я хочу прервать в любой момент.
> Сомнительное преимущество. Какая разница, напишу я класс
> контекста с нуля, или забабахаю поля контекста в наследника
> TThread?
Почти никакой, кроме дополнительного кода.
← →
Mystic © (2008-03-26 14:37) [52]> Однако видели ли вы где-нибудь доступную реализацию Lisp
> или Scheme для windows с native threads?
Функциональные языки распараллеливаются автоматически ;)
← →
DiamondShark © (2008-03-26 14:39) [53]
> DVM © (26.03.08 13:46) [44]
>
> Логично?
Логично. Только это не опровергает исходный тезис. Вот если бы верно было утверждение: "В Борланде принципиально не может быть маразматиков", тогда да ;)
В принципе, упрёк за резкозть принимаю.
> Есть. Непонятно вообще как измерить примитивность данной
> вещи. О потрохах Вы предпочитаете не упоминать, об далеко
> не примитивном правильном применении потоков похоже тоже.
> Да, создать поток элементарно (и описание функции, создающей
> поток тоже примитивно). Но это ли главное?
Именно это. Никаких других задач обсуждаемая обёртка не решает, значит и говорить можно только об элементарных задачах: создание, завершение/пауза, хранение контекста.
Примитивность измеряется очень просто: в количестве сущностей, в сложности структур/данных/типов, в количестве операций.
Имеем: количество сущностей = 1, сложность структур = два атомарных значения, количество операций = пяток примерно.
Более примитивные вещи очень редко встречаются.
> Так что, похоже, о примитивности или нет потоков можно говорить
> только в свете их использования в программе. А это уже не
> так тривиально, как кажется на первый взгляд. Соответственно
> и написание грамотной обертки для этого дела тоже задача
> не совсем тривиальная.
Если не сказать: неразрешимая.
Во всяком случае, решается она немножко иначе, чем пересказом API от третьего лица.
> Использовать класс для контроля над потоком все же удобнее
> в Delphi, чем вызывать чистые функции WinAPI. Или не так?
(*пожимает плечами*)
Кому как.
Тут говорили про чисто текстуальные красивости. Я нахожу ценность этого сомнительной.
А про проблемы я уже написал, повторяться не буду.
← →
Eraser © (2008-03-26 14:42) [54]
> Mystic © (26.03.08 14:37) [52]
> Функциональные языки распараллеливаются автоматически ;)
думаю году к 2013-15 в Делфи появится языковая поддержка многопоточности.. правда как оно будет выглядеть не предполагаю..
← →
DiamondShark © (2008-03-26 14:42) [55]
> Однако видели ли вы где-нибудь доступную реализацию Lisp
> или Scheme для windows с native threads?
А нафига козе баян?
← →
mephasm (2008-03-26 14:46) [56]DiamondShark © (26.03.08 14:42) [55]
Попробуйте написать достаточно функциональный планировщик на Lisp.
Скажем, есть неизменяемый код самого планировщика, которому вы передаете некоторый алгоритм, по которому он передает сообщения или делает асинхронные вызовы. При этом, вы должны обеспечить определенное временное разрешение.
Как сделать это без многопоточности?
← →
DiamondShark © (2008-03-26 14:53) [57]
> Mystic © (26.03.08 14:35) [51]
>
> Предложи лучше. Например, у меня есть сложное математическое
> вычисление, которое я хочу прервать в любой момент.
У меня пупок развяжется искать универсальное решение для абсолютно неопределённого множества сценариев ;)
Наверное, лучше никак не делать, и это будет наиболее гибко.
Впрочем, неплохой вариант ты сам же и предложил, и в .Net Thread.Abort() именно так и действует. Однако, для native-кода это не пройдёт, я не знаю, как вклиниться в произвольный момент времени в контекст другого потока.
> Почти никакой, кроме дополнительного кода.
По количеству строчек то же самое выходит.
← →
Mystic © (2008-03-26 14:58) [58]> думаю году к 2013-15 в Делфи появится языковая поддержка
> многопоточности.. правда как оно будет выглядеть не предполагаю.
Например как в Ada :)
Но я не о том. Дело в том, что в силу особенностей функциональные языки распараллеливаются без всякой языковой поддержки. В любом месте, где вызывается функция двух аргументов, один аргумент может считать один поток, второй---другой. Это делается автоматом. Т. е. если у нас есть некий алгоритм, написанный на ФЯ, то он будет исполнятся в стольки потоках, в скольки ты скажешь при настройке.
> mephasm (26.03.08 14:46) [56]
У каждого языка своя ниша. Не уверен на 100% относительно Lisp, но для языка Haskell планировщики не имеют никакого смысла, язык и так распараллеливается без всяких усилий со стороны разработчик. Любые два аргумента функции могут вычисляться асинхронно.
← →
DiamondShark © (2008-03-26 15:02) [59]
> mephasm (26.03.08 14:46) [56]
А я не буду его на Лиспе писать, я его на Эрланге буду писать. ;)
А серьёзно -- озадачил. Пойду обрадую знакомых функциональщиков.
← →
Mystic © (2008-03-26 15:04) [60]> По количеству строчек то же самое выходит.
За исключением того, что эти строки тебе придется копировать из проекта в проект.
> Однако, для native-кода это не пройдёт, я не знаю, как вклиниться
> в произвольный момент времени в контекст другого потока.
Я хотел это исследовать... Но времени не было. Поэтому пока приходится постоянно вставлять строкиprocedure TThread.CheckTerminated();
begin
if Terminated then raise EArbotThread;
end;
← →
@!!ex © (2008-03-26 15:16) [61]> [48] DiamondShark © (26.03.08 14:19)
>
> > @!!ex © (26.03.08 12:53) [36]
> > Интерфейс нужен хотя бы для того, чтобы избавиться от
> процедурного
> > программирования.
>
> А что оно мне такого плохого сделало, что надо от него избавляться?
>
> Да и не избавиться от него ООПом, там призрак ПП тебя постоянно
> преследовать будет ;)
> Избавляться надо не от процедурного программирования, а
> от низкоуровневых сущностей, а здесь ООП даёт только иллюзию
> избавления, постоянно подкладывая грабли сокрытой сложности
> и дырявых абстракций. Избавление нам дадуд неимперативные
> языки.
>
>
> > ООП нагляднее однако. Поток легче контролировать.
>
> Ой-вэй.
> Берём первую попавшуюся "наглядность" -- TThread.Terminate.
> Ух ты! Как его легко контролировать! А вот дулечки. Успешность
> контролирования потока с помощью "наглядного" Terminate
> оказывается густо замешаной на особенностях реализации Execute:
> проверяется там Terminated, али на него известную крепёжную
> деталь забили... "Опаньки! Менты!" Здравствуй, дырявая абстракция.
Это скорее моя недоработка, чем проблема создания интерфейса.
Делаешь ExecuteWithTerminate:
while not FIsTerminated do
Execute;
Execute - уже перекрывается наследниками.
← →
Eraser © (2008-03-26 15:22) [62]
> Mystic © (26.03.08 15:04) [60]
> Я хотел это исследовать...
на сколько я знаю - только методом жесткого хака, т.е. непосредственно перезаписав код процесса и поменяв eip в контексте нужного потока. у MS-REM"а в его примерах внедрения в другие процессы без использования CreateRemoteThread есть реализация..
← →
Mystic © (2008-03-26 15:31) [63]> Eraser © (26.03.08 15:22) [62]
Поток может ждать результата WaitForSingleObject... Ну и смотреть надо что есть среди Zw функций такого... Или драйвер надо писать... Короче надо смотреть...
← →
Anatoly Podgoretsky © (2008-03-26 15:39) [64]> DiamondShark (26.03.2008 14:39:53) [53]
У Борланда встречаются, например вспомни обертку под названием IncDay
Если в АДА это естественно, поскольку там описываются операции над типом, например обязательно есть
+, Add и это естественно и обращаться можно так Day + 1, IncDay(Day, 1), "+"(Day,1) и не сможешь присвоить если это определено для TDate переменной типа TDateTime, то здесь это искуственно, просто пошли навстречу ламерам в Д7 и наплодили кучу искуственный функциек, наряду с действительно полезными.
Например наглядный пример
a, b: LinearMeasure; типа Integer
c: SqureMeasure; типа Integer
d: CubicalMeasure; типа Integer
C := A * B; не допустимо
← →
Anatoly Podgoretsky © (2008-03-26 15:42) [65]> Mystic (26.03.2008 14:58:58) [58]
> Например как в Ada :)
Хороший язык для создания особо надежных предложений.
Не знаю как сейчас, но в начале 90 требовалось не только сертифицировать каждую версию компилятора, но к тому же делать это для разных аппаратных платформ. Иначе это не считалось компилятором АДА.
Кстати а он жив сейчас?
← →
Mystic © (2008-03-26 16:21) [66]> Кстати а он жив сейчас?
Да, жив. Последний стандарт языка 2005.
http://www.adacore.com/home/
← →
DiamondShark © (2008-03-26 17:09) [67]
> Eraser © (26.03.08 15:22) [62]
> на сколько я знаю - только методом жесткого хака, т.е. непосредственно
> перезаписав код процесса и поменяв eip в контексте нужного
> потока
Ну, не такого уж жёсткого, даже код перезаписывать не надо:
type
EThreadAbort = class(Exception);
function ThreadFunc(Param: Pointer): Integer;
begin
try
while true do Sleep(1);
except
on EThreadAbort do
Result := 1234;
on Exception do
Result := 555;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
label
ExTh;
var
hThread: THandle;
ThreadId: DWORD;
context: _CONTEXT;
ExitCode: DWORD;
Address: Cardinal;
begin
hThread := BeginThread(nil, 0, ThreadFunc, nil, 0, ThreadId);
ShowMessage("Thread started");
SuspendThread(hThread);
context.ContextFlags := CONTEXT_CONTROL;
GetThreadContext(hThread, context);
asm
mov Address, offset ExTh
end;
context.Eip := Address;
SetThreadContext(hThread, context);
ResumeThread(hThread);
WaitForSingleObject(hThread, INFINITE);
GetExitCodeThread(hThread, ExitCode);
ShowMessageFmt("%d", [ExitCode]);
CloseHandle(hThread);
Exit;
ExTh:
raise EThreadAbort.Create("");
end;
← →
DiamondShark © (2008-03-26 17:29) [68]
> DiamondShark © (26.03.08 17:09) [67]
Но так делать всё равно нельзя.
Обычно, установка SEH выглядит примерно так:
xor eax,eax
push ebp
push $00443165
push dword ptr fs:[eax]
mov fs:[eax],esp
и если SuspendThread и изменение EIP случается где-то в середине этого, то приходит белый северный зверёк.
← →
Eraser © (2008-03-26 17:37) [69]
> DiamondShark © (26.03.08 17:09) [67]
а, ну да, если в своем процессе эти заниматься, то не нужно )))
если в чужом, то нужно где-то ж размещать код, которому передавать управление, но это конечно не наш случай..
Страницы: 1 2 вся ветка
Форум: "Прочее";
Текущий архив: 2008.05.11;
Скачать: [xml.tar.bz2];
Память: 0.74 MB
Время: 0.01 c