Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2013.03.22;
Скачать: CL | DM;

Вниз

TThread копирование файла в 2-х потоках   Найти похожие ветки 

 
PacMan ©   (2012-05-01 20:31) [0]

Помогите кто-нибудь.
Нужно в 2-х потоках копировать файл.
1-й поток читает файл и пишет данные в буфер1, затем содержимое буфера 1 передает в буфер 2.
2-й поток пишет из буфера 2 в файл.
бьюсь уже давно, ни как не могу понять как работают потоки.. как 1 заставить ждать, пока не выполнится что-либо во-2м...

листинг:
unit Unit1;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, unit2, StdCtrls, SyncObjs, Gauges;

type
 TForm1 = class(TForm)
   Label1: TLabel;
   Label2: TLabel;
   Button1: TButton;
   Edit1: TEdit;
   Edit2: TEdit;
   OpenDialog1: TOpenDialog;
   SaveDialog1: TSaveDialog;
   Label3: TLabel;
   Gauge1: TGauge;
   procedure Button1Click(Sender: TObject);
   procedure Edit1Click(Sender: TObject);
   procedure Edit2Click(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
 end;

var
 Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
 CriticalSection:=TCriticalSection.Create;
 stream1.Create(false);
 stream2.Create(false);
end;

procedure TForm1.Edit1Click(Sender: TObject);
begin
 if OpenDialog1.Execute then
   Edit1.Text := OpenDialog1.FileName;
end;

procedure TForm1.Edit2Click(Sender: TObject);
begin
 if SaveDialog1.Execute then
   Edit2.Text := SaveDialog1.FileName;
end;

end.

unit Unit2;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls, SyncObjs;

type
 stream1 = class(TThread)

 protected

 public
   Procedure Podgot;
   Procedure ZabivBuf;
   Procedure Zakon;
   procedure Execute; override;
 end;

 stream2 = class(TThread)

 protected

 public
   procedure Execute; override;
   procedure FWrite;
 end;

 var
 f1, f2: File; // первый и второй файл
 buf1: array [1..8192] of Char; //буфер 1
 buf2: array [1..8192] of Char; //буфер 2
 sizefile, sizeread: Int64; //размер файла и размер прочитанного
 colRead, colWrite : Integer; //прочитано и записано
 fOtkuda, fKuda : String; //адреса и имена файлов
 CriticalSection: TCriticalSection;
 flag: Boolean;

 implementation
 uses unit1;

procedure stream1.Execute;
begin
 Podgot;
 while colRead = colWrite do
 begin
   if flag=false then   //если буфер 2 уже пустой
   begin
     CriticalSection.Enter;
     ZabivBuf;

   end;
 end;
 Zakon;
end;

procedure stream2.Execute;
begin
 while colRead = colWrite do
 begin
   if flag=True then   //если буфер 2 уже пустой
   begin
     CriticalSection.Enter;
     Fwrite;
   end;
 end;
end;

Procedure stream1.Podgot;       // подготовка
var i:integer;
begin
 {i-} //даем компилятору директиву, чтобы не отслеживал ошибки ввода-вывода:
 if (Form1.Edit1.Text="") or (Form1.Edit2.Text="") then   //проверяем, указаны ли файлы. если нет - выходим
 begin
   ShowMessage("Вы не указали данные для копирования");
   Exit;
 end;
 //Try
   AssignFile(f1, Form1.Edit1.Text);  //связываем файловые переменные:
   AssignFile(f2, Form1.Edit2.Text);
   Reset(F1, 1);  //связываем файловые переменные:
   sizefile := FileSize(f1); //определяем его размер в переменную:
   Form1.Label3.Caption := "Размер файла: "+IntToStr(Round(sizefile / 8192)) + " Кб.";     //отображаем размер файла в килобайтах:
   Rewrite(f2, 1);  //создаем или перезаписываем второй файл:
   colRead := 0;    //делаем, пока не достигнут конец исходного файла
   colWrite := 0;
   sizeread := 0;
   Screen.Cursor := crHourGlass; //песочные часы
   {i+}
end;

Procedure Stream1.ZabivBuf;
var
   i:integer;    //while colRead = colWrite do
begin
 {i-}
 BlockRead(f1, Buf1, SizeOf(Buf1), colRead);
 //if colRead = 0 then break;

   for I := 1 to 8192 do
     Buf2[i]:=Buf1[i];  //Буфер1 в Буфер2

 Form1.Gauge1.Progress := Round(100*sizeread/sizefile);
 Flag:=True;
 CriticalSection.Leave;
 {i+}
end;

Procedure Stream1.Zakon;
begin
 {i-}
   Screen.Cursor := crDefault; //обычный вид курсора
 //Finally
   CloseFile(f1);
   CloseFile(f2);
 //end; //try
 fOtkuda := Form1.Edit1.Text;  //исправляем дату
 fKuda := Form1.Edit2.Text;
 if IOResult <> 0 then
   Application.MessageBox("Ошибка при копировании файла!", "Внимание!!!",
     MB_OK+MB_ICONERROR)
 else ShowMessage("Копирование  завершено успешно");
 //включаем обработчик компилятором ошибок
 {i+}
End;

procedure stream2.FWrite;
var
   i: Integer;
begin
   BlockWrite(f2, Buf2, colRead, colWrite);
   sizeread := sizeread + colRead;
   Flag:=False;
   CriticalSection.Leave;

end;

end.


 
sniknik ©   (2012-05-01 20:44) [1]

> как 1 заставить ждать, пока не выполнится что-либо во-2м...
а то что по сути это станет одним потоком, просто с разнесенным кодом/логикой на два тебя не смущает?

получается типа, "хочу нести воду в 2х ведрах, но так чтобы пол пути в одном, пол в другом. как на полпути воду в ведрах местами поменять? емкостей то больше нет".


 
PacMan ©   (2012-05-01 20:52) [2]

меня это не смущает, т.к. по заданию мне нужно копировать файл в 2-х потоках, и препод ничего не может мне подсказать по этому поводу.
Если у кого-то есть "знание" как сделать хотя бы это, буду благодарен.
Если кто-то может показать как сделать оптимальнее, также буду благодарен.


 
sniknik ©   (2012-05-01 21:27) [3]

> как 1 заставить ждать, пока не выполнится что-либо во-2м...
уже используется - CriticalSection.Enter; во втором заставит первый ждать, не скажу насчет правильности, логики его использования у тебя, но это вот оно.

и кстати у тебя куча всего чего нельзя в потоках... типа прямого обращения к vcl.


 
PacMan ©   (2012-05-01 21:36) [4]

ну синхронайз пока не трогаю...  программа запускается и так.

при запуске я выбираю исходный файл, выбираю куда и под каким именем его скопировать...
по кнопке "что-то происходит" и в конце выводится "копирование успешно завершено".
открываю директорию назначения и вижу файлик с нужным именем... !НО! его размер 0 байт.
смотрел пошаговое выполнение, у меня запускается 1-й поток, проходит круг... ииииии..... "копирование завершено".

чтобы было понятнее что и как, могу на почту скинуть прогу.


 
QAZ   (2012-05-01 22:16) [5]


> sniknik ©   (01.05.12 20:44) [1]

там на самом деле гинеальная идея заложена :)
типа пока один поток пишет первый кусок,другой читает второй и тд
но она имеет смысл только при наличии 2х жестких дисков , если источник на одном а приемник на другом


 
Сергей М. ©   (2012-05-01 22:18) [6]


> 1-й поток читает файл и пишет данные в буфер1, затем содержимое
> буфера 1 передает в буфер 2.
> 2-й поток пишет из буфера 2 в файл.


Это ты сам придумал или препод навязал тебе именно такую логику ?


 
sniknik ©   (2012-05-01 22:50) [7]

оптимальностью не пахнет, сама логика кривая, но... более... не знаю даже как сказать, но работоспособно это точно. -
unit Unit1;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, Gauges, StdCtrls;

const
 WM_WRITE = WM_USER + 101;

type
 TForm1 = class(TForm)
   Edit1: TEdit;
   Edit2: TEdit;
   Button1: TButton;
   Gauge1: TGauge;
   procedure Button1Click(Sender: TObject);
 private
   procedure WMWrite(var Msg: TMessage); message WM_WRITE;
 public
 end;

var
 Form1: TForm1;

implementation

{$R *.dfm}

const
 BufSize  = 4096;
 WM_BUF   = WM_USER + 102;

type
 TFileRead = class(TThread)
 protected
   F: File;
   FName: string;
   ThreadID2: integer;
   procedure Execute; override;
 public
   constructor Create(ID2: integer; const Name: string);
 end;

 TFileWrite = class(TThread)
 protected
   F: File;
   FName: string;
   MainHandle: THandle;
   procedure Execute; override;
 public
   constructor Create(Handle: THandle; const Name: string);
 end;

constructor TFileRead.Create(ID2: integer; const Name: string);
begin
 ThreadID2:= ID2;
 FName    := Name;
 inherited Create(false);
end;

procedure TFileRead.Execute;
var
 Buf: PChar;
 NumRead: integer;
begin
 AssignFile(F, FName);
 Reset(F, 1);

 repeat
   GetMem(Buf, BufSize);
   BlockRead(F, Buf^, BufSize, NumRead);
   PostThreadMessage(ThreadID2, WM_BUF, integer(Buf), NumRead);
 until (NumRead = 0);

 CloseFile(F);
end;

constructor TFileWrite.Create(Handle: THandle; const Name: string);
begin
 MainHandle:= Handle;
 FName     := Name;
 inherited Create(false);
end;

procedure TFileWrite.Execute;
var
 Buf: PChar;
 NumWritten, Count: integer;
 Msg: TMsg;
begin
 AssignFile(F, FName);
 Rewrite(F, 1);

 Count:= 0;
 while GetMessage(Msg, 0, 0, 0) do begin
   DispatchMessage(Msg);

   Buf  := Pointer(Msg.wParam);
   Count:= Count + Msg.lParam;
   BlockWrite(F, Buf^, Msg.lParam, NumWritten);
   FreeMem(Buf);

   PostMessage(MainHandle, WM_WRITE, Count, 0);
 end;

 CloseFile(F);
end;

procedure TForm1.WMWrite(var Msg: TMessage);
begin
 Gauge1.Progress:= Msg.WParam;
 Button1.Enabled:= Gauge1.Progress = Gauge1.MaxValue;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
 FileWrite: TFileWrite;
begin
 with TFileStream.Create(Edit1.Text, fmOpenRead) do
 try
   Gauge1.MaxValue:= Size;
 finally
   Free;
 end;
 Button1.Enabled:= false;

 FileWrite:= TFileWrite.Create(Handle, Edit2.Text);
 TFileRead.Create(FileWrite.ThreadID, Edit1.Text);
end;

end.


 
sniknik ©   (2012-05-01 23:05) [8]

кстати в цикле второго потока (или вообще) ошибка... он не завершается. исправь.


 
Sha ©   (2012-05-02 02:08) [9]

http://www.delphimaster.net/view/2-1202729073


 
PacMan ©   (2012-05-02 10:24) [10]


> Это ты сам придумал или препод навязал тебе именно такую
> логику ?


на самом деле задание звучит так:
"Разработать компонент, реализующий копирование файлов, при помощи тройной буферизации. Программа должна использовать потоки. Первый поток считывает блок данных из указанного файла. Второй поток записывает этот блок данных в файл. Буферизацию реализовать из 2-х массивов, состоящих из 3-х элементов. Каждый элемент - это буфер. Первый массив отвечает за чтение из файла, а второй за запись. Первый поток получает имя файла, берет пустой элемент из первого массива, записывает в него блок данных и передает второму массиву этот блок данных. При этом буфер, в котором считали блок данных, обнуляется. Второй поток, видя, что во втором массиве есть полный буфер, записывает этот буфер в файл и обнуляет его. При этом, в первом массиве выделяется новый буфер. И так пока весь файл не будет скопирован"

!НО! я даже малейшего представления не имею как это реализовать. Вот и решил упростить "слегка".

если у кого-нибудь есть идеи как это сделать, или еще лучше исходники... буду просто счастлив))
надеюсь не сильно запутал.


 
PacMan ©   (2012-05-02 10:30) [11]

Есть коммерческое предложение, т.к. сам это все равно не напишу.
Если кто желает заморочиться по заданию, договоримся о цене.
Кто надумает, пишите в мыло yrungxay@mail.ru


 
Sha ©   (2012-05-02 11:36) [12]

> PacMan ©   (02.05.12 10:24) [10]

Формулировка ужасная. Это точно оригинальный текст "от препода"?


 
PacMan ©   (2012-05-02 11:52) [13]

ага)
это сфотографировано лично)


 
Cobalt ©   (2012-05-02 11:54) [14]

Имитация обработки - один поток откуда-то получает данные, складирует их в буфер.
Второй поток - мониторит поступлени данных, и записывает их в файл, например, в базу данных.
Вполне жизненная ситуация.


 
Sha ©   (2012-05-02 12:57) [15]

> PacMan ©   (02.05.12 10:24) [10]
> при помощи тройной буферизации.

обычно говорят буферизации чего, какой операции

> Программа должна использовать потоки

ну раз надо, значит, надо

> Буферизацию реализовать из 2-х массивов, состоящих из 3-х элементов.
всего 3 или 3 в каждом массиве? элементы - это что? обычно указатели

> Первый массив отвечает за чтение из файла, а второй за запись.

переменная ни за что никому не отвечает

> Первый поток получает имя файла,
> берет пустой элемент из первого массива,
> записывает в него блок данных..

значит, элемент - не указатель?

> и передает второму массиву этот блок данных.

передать переменной? она же памятник
копирование данных или указателей?
кто об этом и как узнает?
а если некуда передавать? табличка "занято" 3 раза?

> При этом буфер, в котором считали блок данных, обнуляется.

нафига? если в нем были данные - то это бред
может, указатель, хотя это тоже не оптимал

> Второй поток, видя, что во втором массиве есть полный буфер,

просто песня! КАК он это видит?

> записывает этот буфер в файл и обнуляет его.

опять же нафига?
опять же, если видит, но не может? типа еще е все доделал

> При этом, в первом массиве выделяется новый буфер.

вроде мы не удаляли ничего,
в таком случае лимит в 3 буфера будет превышен!

> Cobalt

Конечно, можно мониторить поступление телефонного звонка,
но, вероятно, намного проще брать трубку по звонку.


 
Anatoly Podgoretsky ©   (2012-05-02 13:43) [16]

> Sha  (02.05.2012 12:57:15)  [15]

Это тест на испытание копипастеров, не выживет так не выживет.


 
PacMan ©   (2012-05-02 19:41) [17]


> Sha ©   (02.05.12 12:57) [15]


Это текст задания слово в слово.
Причем сама препод не может ни чего пояснить по нему.
Спрашиваю из элементов какого типа состоят массивы(массив байт или строки или еще что)? Отвечает, что как мне удобнее пусть так и будет, а про копирование вообще ни чего сказать не может.
как-то так.


 
Сергей М. ©   (2012-05-02 20:44) [18]


> сама


Это многое что объясняет)


 
Андреевич   (2012-05-02 21:13) [19]


> там на самом деле гинеальная идея заложена :)
> типа пока один поток пишет первый кусок,другой читает второй
> и тд
> но она имеет смысл только при наличии 2х жестких дисков
> , если источник на одном а приемник на другом

или если источники типа \\server\share\file.txt


 
Anatoly Podgoretsky ©   (2012-05-02 21:36) [20]

> PacMan  (02.05.2012 19:41:17)  [17]

А что ты ей плохого сделал?


 
Сергей М. ©   (2012-05-02 22:43) [21]


> Anatoly Podgoretsky ©   (02.05.12 21:36) [20]


А может просто не сделал, притом что должен был сделать ?
imho, сей вариант наиболее вероятен)


 
Sha ©   (2012-05-03 01:20) [22]

> PacMan ©

Почитай про кольцевой буфер клавиатуры,
прерывания 9 и 16, можно поверхностно.
Изобрази ей нечто похожее.


 
Германн ©   (2012-05-03 01:54) [23]


> Sha ©   (03.05.12 01:20) [22]

Ностальгия?


 
han_malign   (2012-05-03 09:07) [24]


> Почитай про кольцевой буфер клавиатуры

- низзя, сказано "тройная буферизация", значит так и надо делать - нормальный FIFO можно сделать в качестве факультатива...

По сути(технический перевод):
1. есть пул блоков чтения и пул блоков записи фиксированной максимальной глубины...
2. пул чтения изначально заполнен указателями на блоки буфера(скажем размера 64K)
3. поток чтения выбирает блок из пула чтения, заполняет его и помещает в пул записи.
4. поток записи выбирает блок из пула записи, обрабатывает - и помещает в пул чтения.
5. соответственно, если в пуле нет блоков, а "процесс" не завершен - поток приостанавливается.

Тупой спин, кстати - тоже не возбраняется - в некоторых случаях(когда процессоров/ядер не меньше чем потоков и нагрузка на потоки сбалансирована) он увеличивает производительность за счет меньшего количества переключения контекста потоков...

А по делу - это делается двумя счетчиками прочитанных/записанных данных - раздельный доступ к частям буфера обеспечивается счетчиками:
cbR += read(pb[cbR mod sizeof(pb)], min(MAX_BLOCK,sizeof(pb) - (cbR-cbW)));
cbW += write(pb[cbW mod sizeof(pb)], min(MAX_BLOCK,cbR-cbW));


З.Ы. Кстати - в этом варианте уже посчитан последний блок неполного размера...

З.З.Ы. И никакой тройной буферизации нет, буфер в рамках одного уровня одного тракта - один, хотя и может состоять из произвольного числа блоков(т.н. "FIFO-половинки")...


 
PacMan ©   (2012-05-03 09:37) [25]


> Anatoly Podgoretsky ©   (02.05.12 21:36) [20]

хотел другое задание взять... сказала: "Нет, оно не интересное, я тебе вот это дам..."
и вот что заимел(


 
Медвежонок Пятачок ©   (2012-05-03 09:44) [26]

она на тебя просто запала. запишись на дополнительное индивидуальное занятие. вечернее.


 
sniknik ©   (2012-05-03 09:45) [27]

> и вот что заимел(
заслуженная двойка по предмету... даже видя перед собой решение, не понимаешь, что это такое.


 
PacMan ©   (2012-05-03 11:13) [28]

))) Решение вижу, понимаю алгоритм... не понимаю как реализовать.
В  моем примере 1-й поток начинает работать, но проходит только 1 круг, из-за того что 2-й поток не хочет работать.


 
sniknik ©   (2012-05-03 12:01) [29]

> ))) Решение вижу, понимаю алгоритм... не понимаю как реализовать.
угадал все буквы, не смог назвать слово... это нельзя назвать "понимаю".

> В  моем примере 1-й поток начинает работать, но проходит только 1 круг, из-за того что 2-й поток не хочет работать.
твой пример, это набор функций почти каждая из которых "не в том месте и не в то время".

вот что ты сделал по поводу
> и кстати у тебя куча всего чего нельзя в потоках... типа прямого обращения к vcl.
?
а ведь это может приводить к блокировкам, "локированию" потока. ... не думаю что ты "нарвался" именно на это (более вероятно кривая логика), но рассматривать код в котором такие очевидные глюки вряд ли кто будет (я точно нет).


 
Медвежонок Пятачок ©   (2012-05-03 12:21) [30]

стартуем два потока.
первый распределяет буфер и читает файл.
заполнив буфер, первый поток посылает средмессадж второму и сообщает адрес и длину буфера.
второй поток вынимает данные буфера в файл, затем грохает буфер и усыпает.
первый поток, закончив с одним буфером, распределяет второй и снова оповещает второй поток о готовой порции данных.
второй поток просыпается и повторяет операцию.
после того, как файл прочитан целиком, первый поток оповещает второй, что можно закругляться и идти пить пиво.


 
Sha ©   (2012-05-03 12:23) [31]

> han_malign   (03.05.12 09:07) [24]

В данном случае пул и есть кольцевой буфер,
т.к. мы имеем дело с последовательными операциями чтения и записи.

Если уж делать по теории,
то поток чтения занимается только чтением и ничего не знает о потоке записи,
а поток записи занимается только записью и ничего не знает о потоке чтения.


 
sniknik ©   (2012-05-03 12:48) [32]

> Если уж делать по теории,
> то поток чтения занимается только чтением и ничего не знает о потоке записи,
> а поток записи занимается только записью и ничего не знает о потоке чтения.
посмотри код в [7], так и сделано.
условие завершения второго потока только забыл, 1 строка, после решил оставить это автору топика.


 
Sha ©   (2012-05-03 14:01) [33]

> sniknik ©   (03.05.12 12:48) [32]

сразу бросилось в глаза,
что код [7] легко исчерпает память при быстром чтении и медленной записи


 
han_malign   (2012-05-03 14:10) [34]


> что код [7] легко исчерпает память при быстром чтении и медленной записи

- и будет утечка, если PostThreadMessage произойдет раньше оппозитного GetMessage...


 
Медвежонок Пятачок ©   (2012-05-03 14:19) [35]

что код [7] легко исчерпает память при быстром чтении и медленной записи

какая разница, если даже это реально произойдет?
это же абстрактное дурацкое учебное задание ради самого задания.


 
Anatoly Podgoretsky ©   (2012-05-03 14:23) [36]

А может от него надо избавиться, тогда задание предстает совсем в другом свете.


 
sniknik ©   (2012-05-03 14:26) [37]

> что код [7] легко исчерпает память при быстром чтении и медленной записи
естественно. но цель примера не в "копипасте", как понял задача учебная.
а сделать счетчик/ограничитель на например 5 буферов, не проблема...

> - и будет утечка, если PostThreadMessage произойдет раньше оппозитного GetMessage...
с чего это оно произойдет раньше? первый поток только создается когда у второго уже ThreadID есть, т.е. уже "крутится".


 
sniknik ©   (2012-05-03 14:28) [38]

> это же абстрактное дурацкое учебное задание ради самого задания.
+1
на понимание. которого автор якобы достиг ([28]) но почему то все одно "не получается".


 
sniknik ©   (2012-05-03 14:35) [39]

>> - и будет утечка, если PostThreadMessage произойдет раньше оппозитного GetMessage...
> с чего это оно произойдет раньше? первый поток только создается когда у второго уже ThreadID есть, т.е. уже "крутится".
или имеется ввиду промежуток между стартом, и началом ожидания, там где пара команд открытия файла (если задержка), ну и что там дельфя своего добавляет?
в этом случае событие будет ждать в очереди, пока его явно из нее не выкинут. т.е. однозначно дождется цикла.


 
Вариант   (2012-05-03 15:03) [40]


> sniknik ©   (03.05.12 14:35) [39]


>  с чего это оно произойдет раньше? первый поток только создается
> когда у второго уже ThreadID есть, т.е. уже "крутится"

Может начать крутиться и быть остановленным системой, не дойдя до первого вызова GetMessage. В этом случае еще не будет очереди сообщений потока, тогда PostThreadNessage вернет fails. Вероятность этого не нулевая, хотя и повторяемость события не очень большая (если не использовать например отладчик для эмуляции), сам натыкался на такое, хотя первой командой в Execute потока был PeekMessage, просто поток был остановлен планировщиком. Поэтому было бы неплохо после PeekMessage  сигналить в запускающий поток, что мой поток готов принимать сообщения и только потом уже разрешать слать ему сообщения.....  ну  или вариант по MSDN сценарию...


 
Вариант   (2012-05-03 15:11) [41]

А автору поста посоветовал бы Рихтера с Кларком почитать, "Программирование серверных приложений для Windows 2000". Там готовый код на си есть копирования файлов с использованием потоков. Правда придется разбираться и читать, и пробовать и на дельфи перетаскивать. Но зато интересно.


 
PacMan ©   (2012-05-04 11:12) [42]

Ладно, всем спасибо, буду тыкаться. Посмотрим что выйдет.


 
Медвежонок Пятачок ©   (2012-05-04 11:13) [43]

смотря чем тыкаться.
если головой, то она первой и выйдет с другой стороны.



Страницы: 1 2 вся ветка

Текущий архив: 2013.03.22;
Скачать: CL | DM;

Наверх




Память: 0.62 MB
Время: 0.079 c
15-1341094198
COMMODORE-128
2012-07-01 02:09
2013.03.22
Прием символов по СОМ


2-1334343958
vegarulez
2012-04-13 23:05
2013.03.22
[Free CreateOLEObject] Как?


15-1341952202
Юрий
2012-07-11 00:30
2013.03.22
С днем рождения ! 11 июля 2012 среда


2-1327646532
TiBog
2012-01-27 10:42
2013.03.22
масштаб в chart как у других программ


2-1334607403
Непомнящий
2012-04-17 00:16
2013.03.22
Кончается память