Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Delphi Новый топик    Ответить
Топик располагается на нескольких страницах: 1 2      [все]
 Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
Есть поле класса
FMessage: string

к нему идет обращение из разных потоков на чтение и запись.

Нужна ли мне при этом синхронизация? Или когда я записываю новое значение в строку, Delphi всегда выделяет новый кусок памяти и я никогда не прочитаю часть строки до присвоения и часть после,


С уважением, Vasilisk
12 авг 19, 17:56    [21947440]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Мимопроходящий
Member

Откуда: бурятский тундрюк, эсквайр
Сообщений: 30556

12.08.2019 17:56, _Vasilisk_ пишет:
> Delphi всегда выделяет новый кусок памяти и я никогда не прочитаю часть строки до присвоения и часть после,

интересно послушать, что скажут монстры рока.

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

Posted via ActualForum NNTP Server 1.5

12 авг 19, 18:12    [21947451]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Kazantsev Alexey
Member

Откуда:
Сообщений: 3884
_Vasilisk_
Нужна ли мне при этом синхронизация?

Нужна.
12 авг 19, 18:13    [21947452]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
Kazantsev Alexey
_Vasilisk_
Нужна ли мне при этом синхронизация?
Нужна.
Тогда вопрос. Такой код
function GetMessage: string;
begin
  Lock;
  try
    Result := FMessage;
  finally
    Unlock;
  end;
end;
имеет право на существование? Или я должен писать так?
function GetMessage: string;
begin
  Lock;
  try
    Result := FMessage;
    UniqueString(Result);
  finally
    Unlock;
  end;
end;

По идее без UniqueString синхронизация не имеет смысла
12 авг 19, 18:21    [21947460]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
X-Cite
Member

Откуда: Минск
Сообщений: 1602
function GetMessage: string;
begin
  Lock;
  try
    Result := FMessage;
  finally
    Unlock;
  end;
end;
12 авг 19, 18:35    [21947471]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
X-Cite,

Это сейчас о чем?
12 авг 19, 18:39    [21947473]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
DmSer
Member

Откуда: Пенза
Сообщений: 915
_Vasilisk_
Или я должен писать так?
function GetMessage: string;
begin
  Lock;
  try
    Result := FMessage;
    UniqueString(Result);
  finally
    Unlock;
  end;
end;

По идее без UniqueString синхронизация не имеет смысла


Я всегда так пишу. Одной лишь CS недостаточно (вроде). В принципе, ошибки со строками довольно быстро отыскиваются, если сделать нагрузочный тест с несколькими потоками. Чем больше ядер у процессора, тем быстрее ошибка воспроизводится. От длины строки также может зависеть.
12 авг 19, 18:51    [21947479]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Kazantsev Alexey
Member

Откуда:
Сообщений: 3884
_Vasilisk_,

Насколько я помню, только чтение синхронизировать не требуется (счётчик и так синхронизируется). Синхронизация нужна в сценариях где присутствует запись.
12 авг 19, 18:56    [21947480]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
Kazantsev Alexey
только чтение синхронизировать не требуется (счётчик и так синхронизируется). Синхронизация нужна в сценариях где присутствует запись.
А какой смысл синхронизации записи при несинхронизированном чтении?
12 авг 19, 19:00    [21947482]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Kazantsev Alexey
Member

Откуда:
Сообщений: 3884
_Vasilisk_
А какой смысл синхронизации записи при несинхронизированном чтении?


Kazantsev Alexey
Синхронизация нужна в сценариях где присутствует запись.
12 авг 19, 19:08    [21947485]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Kazantsev Alexey
Member

Откуда:
Сообщений: 3884
Вот, нашёл разъяснение от создателя: https://blog.therealoracleatdelphi.com/2015/11/friendly-reminder.html
12 авг 19, 19:30    [21947492]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
ARGB32
Member

Откуда: Мск
Сообщений: 37
_Vasilisk_
Нужна ли мне при этом синхронизация? Или когда я записываю новое значение в строку, Delphi всегда выделяет новый кусок памяти и я никогда не прочитаю часть строки до присвоения и часть после


Так со строками получиться не может благодаря copy-on-write, но синхронизация доступа к самой переменной таки нужна.
Иначе, в отличии от случая с Integer кстати, геттер может получить из другого потока строку, которая будет вот-вот разрушена и дальнейшее обращение к этой переменной приведет к AV.
UniqueString() не нужен.
13 авг 19, 00:48    [21947603]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Голландец
Member

Откуда:
Сообщений: 123
_Vasilisk_,

Синхронизация нужна, unique не нужен
Можно заморочиться с лок фри, но это сложно
13 авг 19, 06:18    [21947637]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
DmSer
Member

Откуда: Пенза
Сообщений: 915
UniqueString() не нужен

Лучше пусть будет. Мы же не знаем, какая работа со строкой будет в дальнейшем. А cs больше не будет.
13 авг 19, 07:03    [21947640]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 5329
Голландец
_Vasilisk_,

Синхронизация нужна, unique не нужен
+1

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

TSync<t> = record
private
  l: TSpinLock;
  v: t;
  function GetValue(): t;
  procedure SetValue(const A: t);
public
  property Value read GetValue write SetValue;
end;


но мазохисты конечно встречаются везде...
13 авг 19, 07:17    [21947641]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Василий 2
Member

Откуда:
Сообщений: 799
Присвоение строк просто копирует указатель на один и тот же фрагмент памяти. Лок-анлок достаточно, если ты прям железно уверен, что сама строка не меняется. Но лучше добавить и копирование, если это не станет узким местом.
13 авг 19, 09:54    [21947720]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
X-Cite
Member

Откуда: Минск
Сообщений: 1602
Зачем копирование, если есть лок?
13 авг 19, 10:48    [21947778]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Василий 2
Member

Откуда:
Сообщений: 799
Может меняться содержимое строки.
13 авг 19, 12:05    [21947904]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
X-Cite
Member

Откуда: Минск
Сообщений: 1602
Логично же что может...
Пока у вас обращение к переменной чтение/запись через блокировку то изменения строк управляемые..
Копирование тогда зачем?
13 авг 19, 12:16    [21947923]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
DmSer
Member

Откуда: Пенза
Сообщений: 915
X-Cite
Копирование тогда зачем?


Чтобы не писать в дальнейшем при обращении к это строке Lock / Unlock
13 авг 19, 12:38    [21947964]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
X-Cite
Member

Откуда: Минск
Сообщений: 1602
DmSer
X-Cite
Копирование тогда зачем?


Чтобы не писать в дальнейшем при обращении к это строке Lock / Unlock

Вы не к строке обращаетесь, а к переменной.
Выделите абстракцию (класс/ы), перенесите туда логику связанную со строкой.. в этой абстракции обеспечьте потокобезопасность и везде используйте ее.. Методы абстракции (классов) будут потокобезопасны, вызывайте где хотите...
13 авг 19, 13:23    [21948033]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 5329
DmSer
X-Cite
Копирование тогда зачем?


Чтобы не писать в дальнейшем при обращении к это строке Lock / Unlock
а смысл?
как уже написали выше, проблема при взятии строки
увеличение инкремента может не успеть и строку удалят в другом потоке, по-этому операции с "хранилищем" надо лочить

а дальше строка защищена механизмом CopyOnWrite, если напрямую не лезть в память, то проблем с копией строки нет
13 авг 19, 13:54    [21948106]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
Kazantsev Alexey
Вот, нашёл разъяснение от создателя: https://blog.therealoracleatdelphi.com/2015/11/friendly-reminder.html
автор
Just like any other variable that is being read from or written to from many threads, string variables require the same level of protection as you would give to a simple Integer variable.
Так извини, меня, если я Integer в одном потоке только читаю, а в другом только пишу, то и синхронизация мне не нужна. Мне не критично прочитать старое или новое значение

ARGB32
геттер может получить из другого потока строку, которая будет вот-вот разрушена и дальнейшее обращение к этой переменной приведет к AV.
kealon(Ruslan)
увеличение инкремента может не успеть и строку удалят в другом потоке, по-этому операции с "хранилищем" надо лочить
Такого быть не может. Изменение счетчика ссылок атомарная операция
13 авг 19, 14:30    [21948152]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 5329
_Vasilisk_
ARGB32
геттер может получить из другого потока строку, которая будет вот-вот разрушена и дальнейшее обращение к этой переменной приведет к AV.
kealon(Ruslan)
увеличение инкремента может не успеть и строку удалят в другом потоке, по-этому операции с "хранилищем" надо лочить
Такого быть не может. Изменение счетчика ссылок атомарная операция
группа атомарных операций не атомарна

mov eax, [v]
<-  если здесь произведут декремент в другом потоке, то в eax будет ссылка на убитый кусок
call _straddref


т.е. при работе со строкой, interlocked функциями не обойтись
13 авг 19, 15:13    [21948209]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
kealon(Ruslan)
т.е. при работе со строкой, interlocked функциями не обойтись
Они там и есть
function _UStrClr(var S): Pointer;
var
  P: PStrRec;
begin
  if Pointer(S) <> nil then
  begin
    P := Pointer(PByte(S) - SizeOf(StrRec));
    Pointer(S) := nil;
    if P.refCnt > 0 then
    begin
      if AtomicDecrement(P.refCnt) = 0 then
        FreeMem(P);
    end;
  end;
  Result := @S;
end;
13 авг 19, 15:16    [21948216]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 5329
_Vasilisk_,

thred1:
mov eax, [v]

thred2:
 _UStrClr [v]

thred1:
call _straddref


что будет?
13 авг 19, 15:19    [21948221]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
kealon(Ruslan)
mov eax, [v]
Это что?
13 авг 19, 15:21    [21948224]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Dimitry Sibiryakov
Member

Откуда:
Сообщений: 48611

_Vasilisk_
там и есть

Руки бы оторвать тому, что ЭТО написал...

Posted via ActualForum NNTP Server 1.5

13 авг 19, 15:29    [21948242]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Kazantsev Alexey
Member

Откуда:
Сообщений: 3884
_Vasilisk_
если я Integer в одном потоке только читаю, а в другом только пишу, то и синхронизация мне не нужна. Мне не критично прочитать старое или новое значение

Для Integer не нужна, но изменение строковой переменной, как уже сказали, операция не атомарная. Может получится так, что прочитаешь указатель на освобождённый блок памяти. Тест просто пишется, проверь.
13 авг 19, 16:12    [21948290]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Dimitry Sibiryakov
Member

Откуда:
Сообщений: 48611

Kazantsev Alexey
Может получится так, что прочитаешь указатель на освобождённый блок памяти.

Это бы ещё ладно, а вот прочитать указатель на освобождённый и заново выделенный блок даст
реально забавные спецэффекты и снос крыши при попытке отладки.

Posted via ActualForum NNTP Server 1.5

13 авг 19, 16:20    [21948303]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
Kazantsev Alexey
изменение строковой переменной, как уже сказали, операция не атомарная. Может получится так, что прочитаешь указатель на освобождённый блок памяти
Т.е. кроме лока нужна еще и UniqueString?
13 авг 19, 17:25    [21948419]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
Dimitry Sibiryakov
Руки бы оторвать тому, что ЭТО написал...
Что-то не вижу. В чем криминал?
13 авг 19, 17:25    [21948421]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 5329
_Vasilisk_
Kazantsev Alexey
изменение строковой переменной, как уже сказали, операция не атомарная. Может получится так, что прочитаешь указатель на освобождённый блок памяти
Т.е. кроме лока нужна еще и UniqueString?
нет, если у тебя уже есть валидная строка, неважно сколько на неё "копий", работа с ней будет корректна


_Vasilisk_
Это что?
копирование в регистр из участка памяти, куда идёт совместный доступ
13 авг 19, 17:32    [21948435]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Kazantsev Alexey
Member

Откуда:
Сообщений: 3884
_Vasilisk_
Kazantsev Alexey
Т.е. кроме лока нужна еще и UniqueString?

Нет.
13 авг 19, 17:39    [21948440]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Dimitry Sibiryakov
Member

Откуда:
Сообщений: 48611

_Vasilisk_
В чем криминал?

Строка "if P.refCnt > 0 then" совершенно тут бесполезна и только вводит в заблуждение. К
моменту её выполнения refCnt в многопоточном приложении должен быть либо больше единицы,
либо приходит пушной зверёк. (Приложение в котором только один поток использует эту
переменную считается за однопоточное.)

Таким образом эта процедура построена на предположении, что вся остальная compiler magic
работает корректно, а на уровне приложения обращение к переменной таки синхронизировано.

Posted via ActualForum NNTP Server 1.5

13 авг 19, 17:41    [21948445]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
Dimitry Sibiryakov
Строка "if P.refCnt > 0 then" совершенно тут бесполезна и только вводит в заблуждение. К моменту её выполнения refCnt в многопоточном приложении должен быть либо больше единицы,
Нет. Для констант refCnt -1 и не изменяется
procedure _UStrAsg(var Dest: UnicodeString; const Source: UnicodeString); // globals (need copy)
{$IFDEF PUREPASCAL}
var
  S, D: Pointer;
  P: PStrRec;
  Len: LongInt;
begin
  S := Pointer(Source);
  if S <> nil then
  begin
    if __StringRefCnt(Source) < 0 then   // make copy of string literal
    begin
      Len := __StringLength(Source);
      S := _NewUnicodeString(Len);
      Move(Pointer(Source)^, S^, Len * SizeOf(WideChar));
    end else
    begin
      P := PStrRec(PByte(S) - SizeOf(StrRec));
      AtomicIncrement(P.refCnt);
    end;
  end;
  D := Pointer(Dest);
  Pointer(Dest) := S;
  _UStrClr(D);
end;
13 авг 19, 18:22    [21948476]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
Что-то я совсем потерялся. Давайте еще раз. string это указатель на структуру
  StrRec = packed record
  {$IF defined(CPUX64)}
    _Padding: LongInt; // Make 16 byte align for payload..
  {$ENDIF}
    codePage: Word;
    elemSize: Word;
    refCnt: Longint;
    length: Longint;
    data: array[0...] of Char;
  end;
codePage и elemSize модифицироваться не могут refCnt модифицируется атомарно length и data не модифицируются логикой программы. Новая строка только присваивается. Без всякого сложений и модификаций символов.

Т.е. из всей структуры модифицируется только одно поле refCnt и то происходит атомарно. Зачем здесь синхронизация?

Далее, допустим, синхронизация нужна, но не нужна UniqueString. Тогда я вообще не понимаю. Код
_Vasilisk_
function GetMessage: string;
begin
  Lock;
  try
    Result := FMessage;
  finally
    Unlock;
  end;
end;
по сути идентичен такому
FMessage: Pointer;
....
function GetMessage: Pointer;
begin
  Lock;
  try
    AddRef(FMessage)
    Result := FMessage;
  finally
    Unlock;
  end;
end;
Но тогда любой код, вызвавший GetMessage получает тот же указатель, что и оригинальный FMessage, но уже ни о какой синхронизации не подозревает. Тогда в чем смысл был Lock/Unlock, если AddRef атомарна?
13 авг 19, 18:39    [21948486]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 5329
_Vasilisk_,

потому что

Result := FMessage;
это две операции: нужно сначала получить указатель на структура StrRec, а потом уже вызвать инкремент, и наоборот это принципиально не сделать
13 авг 19, 18:43    [21948489]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Dimitry Sibiryakov
Member

Откуда:
Сообщений: 48611

_Vasilisk_
Для констант refCnt -1 и не изменяется

Эва какие костыли...

В любом случае, когда этот код параллельно выполняется для refCnt = 1, наступает "Бум!"
поскольку один поток затормозил перед проверкой, второй в это время полностью выполнил
процедуру и освободил память, первый прочухался и проверяет refCnt в уже освобождённом
мусоре. А для глобальной переменной счётчик будет 1 без вариантов.

Posted via ActualForum NNTP Server 1.5

13 авг 19, 18:55    [21948497]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
kealon(Ruslan)
это две операции: нужно сначала получить указатель на структура StrRec, а потом уже вызвать инкремент
Все. Уразумел. Извините за торможение
13 авг 19, 19:33    [21948532]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
Dimitry Sibiryakov
поскольку один поток затормозил перед проверкой, второй в это время полностью выполнил процедуру и освободил память,
Нет. Декремент вызывается только при потере видимости. Если мы говорим о доступе к одной переменной из разных потоков, значит эта переменная не локальная, а, как минимум, поле класса. А значит видимость она потеряет только при вызове деструктора.

Если же у нас два указателя на одну строку, то уже refCnt >= 2
13 авг 19, 19:40    [21948534]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 5329
_Vasilisk_
Dimitry Sibiryakov
поскольку один поток затормозил перед проверкой, второй в это время полностью выполнил процедуру и освободил память,
Нет. Декремент вызывается только при потере видимости. Если мы говорим о доступе к одной переменной из разных потоков, значит эта переменная не локальная, а, как минимум, поле класса. А значит видимость она потеряет только при вызове деструктора.

Если же у нас два указателя на одну строку, то уже refCnt >= 2
только читать наверное не особо интересно, надо наверное и писать - 21948221
13 авг 19, 23:57    [21948714]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
kealon(Ruslan)
только читать наверное не особо интересно, надо наверное и писать - 21948221
Так мы же договорились, что доступ к переменной только через синхронизацию
14 авг 19, 15:25    [21949359]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
kealon(Ruslan)
Member

Откуда: Нижневартовск
Сообщений: 5329
_Vasilisk_,

Dimitry Sibiryakov тебе объяснял, что будет без лока
14 авг 19, 16:08    [21949399]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11257
kealon(Ruslan)
Dimitry Sibiryakov тебе объяснял, что будет без лока
Дмитрий возмущался кривизной кода RTL. Я попросил показать в чем кривизна.

Кроме того, обсуждаемый код RTL влияет на объекты "строка", а не на строковые переменные (которые указатели на эти объекты)
14 авг 19, 16:34    [21949434]     Ответить | Цитировать Сообщить модератору
 Re: Синхронизация при доступе к строке  [new]
Dimitry Sibiryakov
Member

Откуда:
Сообщений: 48611

_Vasilisk_
обсуждаемый код RTL влияет на объекты "строка", а не на строковые переменные (которые
указатели на эти объекты)

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

Posted via ActualForum NNTP Server 1.5

14 авг 19, 16:44    [21949444]     Ответить | Цитировать Сообщить модератору
Топик располагается на нескольких страницах: 1 2      [все]
Все форумы / Delphi Ответить