Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Круглый стол
  
Правила КС
>> Настройки

Фильтр вопросов
>> Новые вопросы
отслеживать по
>> Новые ответы

Избранное

Страница вопросов
Поиск по КС


Специальные проекты:
>> К л ю к в а
>> Г о л о в о л о м к и

Вопрос №

Задать вопрос
Off-topic вопросы

Помощь

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  13:18[Войти] | [Зарегистрироваться]
Ответ на вопрос № 38104

27-11-2005 09:56
Здравствуйте всем.
Если вопрос глупый, не обижайтесь, я вроде искал - не нашел.
Нужна функция поиска подстроки в буфере, типа POS, но "подстрока" не символьная а произволный набор байт.
Естесс-но ведь, что обычный POS на первом же #0 байте в буфере остановиться, и дальше искать не будет.
Т.Е., что-то типа:
Searsh(ppp, bbb, L, k): integer;
где ppp - array[] of byte с "подстрокой" поиска,
    bbb - array[] of byte с буфером где искать,
    L - длина реальных данных в буфере,
    k - длина "подстроки" в ppp.

Или подскажите, чем воспользоваться.

Я, конечно, могу написать сам, да вдруг велосипед уже существует.

[+] Добавить в избранные вопросы

Отслеживать ответы на этот вопрос по RSS

Ответы:


Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице.
Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.

12-10-2006 09:56 | Комментарий к предыдущим ответам
стала на немного опережатьСоврал невольно. Там была ошибка, исправил, в этом варианте стало в 1.5 - 2 раза на разных примерах медленней, чем MemoryStrPos (у меня тоже простой поиск).

12-10-2006 07:59 | Комментарий к предыдущим ответам
Этот вопрос упомянули сегодня в другом обсуждении. По случаю добавлю: DRON писал:

Вариант на Паскале работает в несколько раз медленнее.


Мы потом обменялись сообщениями на эту тему. Может быть, если считать такты, то действительно ASM будет эффективней. Но мои измерения времени не показали такого превосходства.  Измерял скорость поиска в большой строке (мегабайты), в цикле несколько (последний раз 100) раз, т.к.измеряемые времена очень малы. Даже наоборот написанная на паскале функция после одной оптимизации вместо отставания на процентов 30%-50% (точно не помню) стала на немного опережать приведенную здесь функцию на ассемблере. Но существенный вывод, что поиск в  строке, находящейся в памяти настолько быстр, что во многих ситуациях решающую роль, как и писал DRON, играют совсем другие моменты, например скорость чтения с диска.

01-01-2006 09:09 | Комментарий к предыдущим ответам
>>> Будь Delphi поумнее
а будь программист поумнее - не писал бы такой ахинеи.

17-12-2005 07:45 | Сообщение от автора вопроса
Большое спасибо всем еще раз.
Я уж было и ветку потерял.
Пытаюсь использовать ваши примеры в программе.
Если не в этой, то в следующей - теперь уж точно не пропадет.

10-12-2005 16:05 | Комментарий к предыдущим ответам
Я измерения только в памяти производил 
Да я тоже. А с ассемблером слабо знаком. Я все-таки самоучка и руки до него как-то не дошли.

10-12-2005 15:47 | Комментарий к предыдущим ответам
Правда для БМХ использовал свой вариант, написанный на паскале, на который давал ссылку.
Собственно из него я свой "asm" и делал, неужели не узнали :)
Я измерения только в памяти производил (при работе с диском мы с большой вероятностью будем мерить производительность кэша, а не то, что нам надо), на объёмах в несколько мегабайт и мерил не время, а число тактов процессора. Вариант на Паскале работает в несколько раз медленнее. В принципе из асма можно ещё что-то выжать оптимизирую под конкретные конвейеры (попробуйте например переставить "mov al,[esi+edx]" и "dec ebx", скорость упадёт процентов на 10). Вообще, для данной задачи оптимизация по скорости ни к чему, скорость чтения с винта всяко меньше, надо просто файлы читать асинхронно, так чтобы пока новая порция читается, в старой происходил бы поиск.

10-12-2005 13:53 | Комментарий к предыдущим ответам
Что ж, если опыт показал, что при определенных, достаточно  широких, условиях БМХ выигрыша не дает, то извиняюсь перед RSSDEW.

Я тоже попытался сравнить скорости. Правда для БМХ использовал свой вариант, написанный на паскале, на который давал ссылку. Измерял через GetTickCount на файлах до 22 МБ. Проверял для небольших подстрок размером примерно со слово: '[test]', '[rest]', потом удлинил до '[restaurant]' :). Для последней, более длинной строки БМХ несколько быстрее, а для первых разницы практически нет.

Похоже, что для многих практических применений оба способа достаточно хороши. На файле 22 мб время было ~70 ms. (Athlon 1800, 256 MB)

На меньших файлах у меня вообще сложно время измерить: есть сомнения в том, что за время измеряется, почему-то оно скачет "квантами" (по ~15 ms), если нажиматть кнопку теста несколько раз подряд, похоже на sliсe процесса, пришлось пускать поиск по 10 раз в цикле, чтобы что-то померить. Разбираться с GetProcessTimes не стал.
Надо признаться, по крайней мере в одном случае простой перебор был даже чуть быстрее, но это - практически в пределах погрешности. С учетом "квантов" поиск '[rest]' давал в обоих случаях чаще 235 мс, но для перебора иногда было ~218, а для БМХ иногда 250. Правда статистики я особо не набирал.

Так прошел субботний вечер...

10-12-2005 05:59 | Комментарий к предыдущим ответам
DRON:
Ты бы оформил свой ответ в отдельный юнит и поместил бы в сокровищницу. Иначе такая полезная весчь на 80 процентов останется невостребованной(не найденной). Там в юните выведи отдельные функции для поиска как по stream'у так и в буфере (pointer/string).

09-12-2005 14:51
От теории перейдём к практике:

type
  TBMTable=record
    Skips:array[Char] of Integer;
    SubStr:string;
    SubLen:Integer;
  end;

function InitBMTable(const SubStr:string):TBMTable;
var
  A,SubLen:Integer;
begin
  SubLen:=Length(SubStr);
  Result.SubStr:=SubStr;
  Result.SubLen:=SubLen;
  for A:=0 to 255 do
    Result.Skips[Char(A)]:=SubLen;
  for A:=1 to SubLen-1 do
    Result.Skips[SubStr[A]]:=SubLen-A;
end;

function MemoryStrBMPosAsm(const Table:TBMTable;const Buffer;Size:Integer):Integer; register;
asm
  push edi
  push esi
  push ebx
  push edx
  mov edi,eax
  xor eax,eax
  mov ebx,edx
  add ecx,edx
  mov esi,TBMTable.SubStr[edi]
  mov edx,TBMTable.SubLen[edi]
  lea ebx,[ebx+edx-1]
@1:
  cmp ebx,ecx
  jae @5
  push ebx
  push edx
@2:
  dec edx
  js  @4
  mov al,[esi+edx]
  dec ebx
  cmp al,[ebx+1]
  je @2
  pop edx
  pop ebx
  mov al,[ebx]
  add ebx,dword ptr TBMTable.Skips[edi+eax*4]
  jmp @1
@4:
  pop edx
  pop eax
  sub eax,[esp]
  sub eax,edx
  inc eax
  jmp @_End
@5:
  xor eax,eax
  dec eax
@_End:
  pop edx
  pop ebx
  pop esi
  pop edi
end;

function StrBMScan(Param:Pointer;const Buffer;Size:Integer):Integer;
begin
  Profiler.Start;
  Result:=MemoryStrBMPosAsm(TBMTable(Param^),Buffer,Size);
  if Result<0 then Result:=-TBMTable(Param^).SubLen+1;
  Profiler.Stop;
end;


Использование:

  BMTable:=InitBMTable(SubStr);
  Position:=ScanStream(Stream,StrBMScan,Pointer(@BMTable),1024*1024);


Проверка на разных данных показывает, что БМ поиск начинает давать лучшие результаты, только для подстрок длиной более 6-ти символов. Двукратное ускорение начинается где-то после 128-и символов.

09-12-2005 03:03
Конечно, скорость зависит от условий задачи. Но в БМХ нет "сравнения каждого байта на все имеющиеся в подстроке значения". В начале поиска генерируется массив сдвигов (пускай skips[]) для каждого из 256 символов, и смещение для символа buf[i] (buf - массив байтов, в котором ищем) определяется как skips[buf[i]].
http://www.delphikingdom.ru/asp/answer.asp?IDAnswer=27588
А обращение к элементу массива - операция быстрая.
Вообще хорошо известно, и DRON про это тоже написал, что БМХ - хороший алгоритм.
Вот здесь
http://algolist.manual.ru/search/esearch/index.php
среднее время поиска применяемого Вами алгоритма "грубой силы" оценивается как O(2*n), где n - длина строки, в которой ищут. Для БМХ там же - O(n+m), m - длина подстроки. То есть для примера, где текст - кило-мега-гигабайты, а длина подстроки - до десятков байт, БМХ дает средний выигрыш в 2 раза.

08-12-2005 19:51 | Сообщение от автора вопроса
>> ... и в просматриваемой строке дошли до символа "п", например, то ясно, что можно сразу сдвигаться на длину строки "арбуз" (т.к. в ней "п" нет), а не двигаться по 1 байту.

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

06-12-2005 12:05
Вот, может пригодится. Универсальная функция сканирования потока:

type
  //Result>=0 - строка найдена, начало в Buffer[Result]
  //Result<0 - строка не найдена, кусок размером в -Result требует повторной обработки
  TScanProc=function(Param:Pointer;const Buffer;Size:Integer):Integer;

function ScanStream(Stream:TStream;ScanProc:TScanProc;Param:Pointer=nil;BufferSize:Integer=65536):Int64;
var
  Buffer:TByteDynArray;
  Res,First,Readed:Integer;
begin
  Result:=0;
  SetLength(Buffer,BufferSize);
  First:=0;
  repeat
    Readed:=Stream.Read(Buffer[First],BufferSize-First)+First;
    if Readed=First then Break;
    Res:=ScanProc(Param,Buffer[0],Readed);
    if Abs(Res)>=Readed then Break;
    Inc(Result,Res-First);
    if Res>=0 then Exit;
    Move(Buffer[Readed+Res],Buffer[0],-Res);
    Inc(Result,Readed-Res);
    First:=-Res;
  until False;
  Result:=-1;
end;

//Сканирование с использованием MemoryStrPos
//Но для длинных строк БМ-поиск действительно намного быстрее
function StrScan(Param:Pointer;const Buffer;Size:Integer):Integer;
begin
  Result:=MemoryStrPos(string(Param),Buffer,Size);
  if Result<0 then Result:=-Length(string(Param))+1;
end;


использовать как-то так:

  Position:=ScanStream(Stream,StrScan,Pointer(SubStr),1024*1024)


06-12-2005 09:44
RSSDEW, все-таки советую посмотреть алгоритм БМХ или другой. Если совсем грубо, то если ищем подстроку "арбуз" и в просматриваемой строке дошли до символа "п", например, то ясно, что можно сразу сдвигаться на длину строки "арбуз" (т.к. в ней "п" нет), а не двигаться по 1 байту. Алгоритм это использует.

06-12-2005 06:31 | Сообщение от автора вопроса
В принципе, задача уже работает.
Спасибо всем за внимание, код на асме от DRON обязательно использую.

06-12-2005 06:26 | Сообщение от автора вопроса
Сейчас так и ищу.
Сначала читал из файла в буфер по 8 КБ, затем переделал на отображение в память. Только все равно эффективнее оказалось обрабатывать куски из него "побуферно" (если так можно выразиться).

Задача именно такая: Имеются входные файлы, размером от десятков КБ до нескольких Гб. В них в потоковом режиме нужно находить другую последовательность (даже несколько), которые храняться в файлах настройки. При нахождении - с данной позиции первого файла делаем некоторую обработку, потом идем дальше.

Первый вариант функции поиска написал очень быстро: ищем в цикле по индексу от 0 до (N-L) первый байт из "подстроки" (длиной L), как нашли - сравниваем следующие L-1 байт из буфера с остатком "подстроки".
Скорость работы не замерял, но интуитивно ясно, что неоптимальная - этоже все-таки for, а не SCASB. Как дойдут руки - перепишу на асме.

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

29-11-2005 10:33
Невозможно объявить с некоторой позиции отображения фиктивную String-переменную, поскольку тогда у нее по отрицательному смещению [-4] не будет реального счетчика длины.
А разве сложно взять функцию _LStrPos из System.pas и поменять в ней ДВЕ строчки, чтобы она работала не со строками, а с памятью:

function MemoryStrPos(const SubStr:AnsiString;const Buffer;Size:Integer):Integer; register;
asm
{    ->EAX    Pointer to substr              }
{      EDX    Pointer to string              }
{    <-EAX    Position of substr in s or 0    }
        TEST    EAX,EAX
        JE      @@noWork
        TEST    EDX,EDX
        JE      @@stringEmpty
        PUSH    EBX
        PUSH    ESI
        PUSH    EDI
        MOV    ESI,EAX                        { Point ESI to substr          }
        MOV    EDI,EDX                        { Point EDI to s                }
//!!!        MOV    ECX,[EDI-skew].StrRec.length    { ECX = Length(s)              }
        PUSH    EDI                            { remember s position to calculate index        }
        MOV    EDX,[ESI-4]                    { EDX = Length(substr)          }
        DEC    EDX                            { EDX = Length(substr) - 1              }
        JS      @@fail                          { < 0 ? return 0                        }
        MOV    AL,[ESI]                        { AL = first char of substr            }
        INC    ESI                            { Point ESI to 2'nd char of substr      }
        SUB    ECX,EDX                        { #positions in s to look at    }
                                                { = Length(s) - Length(substr) + 1      }
        JLE    @@fail
@@loop:
        REPNE  SCASB
        JNE    @@fail
        MOV    EBX,ECX                        { save outer loop counter              }
        PUSH    ESI                            { save outer loop substr pointer        }
        PUSH    EDI                            { save outer loop s pointer            }
        MOV    ECX,EDX
        REPE    CMPSB
        POP    EDI                            { restore outer loop s pointer  }
        POP    ESI                            { restore outer loop substr pointer    }
        JE      @@found
        MOV    ECX,EBX                        { restore outer loop counter    }
        JMP    @@loop
@@fail:
        POP    EDX                            { get rid of saved s pointer    }
        XOR    EAX,EAX
        JMP    @@exit
@@stringEmpty:
        XOR    EAX,EAX
        JMP    @@noWork
@@found:
        POP    EDX                            { restore pointer to first char of s    }
        MOV    EAX,EDI                        { EDI points of char after match        }
        SUB    EAX,EDX                        { the difference is the correct index  }
@@exit:
        POP    EDI
        POP    ESI
        POP    EBX
@@noWork:
        DEC    EAX  //!!!
end;


Если строка не найдена возвращает -1, если найдена, то от 0 до Size-1.

29-11-2005 10:11 | Комментарий к предыдущим ответам
Надо же я и ответ Cергея заметил только после второго отправленного сообщения...
RSSDEW: а как ты сейчас ищешь?

29-11-2005 09:57 | Комментарий к предыдущим ответам
RSSDEW: а вообще-то не сложно, но я к концу дня, только с шестого прочтения смог загрузить все пункты твоего message в свою мысль :o).
и написанное мое сообщение чуть раньше врятли подойдет в смысле скорости. Надо найти другой вариант.

29-11-2005 09:44
RSSDEW (твои 2 последних ответа):
Как ты сложно мысль формулируешь... Как я понял ты хочешь, двигаясь по файлу отображенному в память, сравнивать блоки данных (1-ый из файла, 2-ой некая переменная с информацией)? так чего сложного? бежишь сравниваешь, для скорости, только первые байты, если равны, то сравниваешь последующие (определенной длины) или CompareMem.

29-11-2005 09:17
Есть много алгоритмов поиска подстрок в строке, которые рассматривают и то, и другое просто как массив байтов. Можно почитать здесь:
http://delphi.mtu-net.ru/zip/stephen.zip
Например, Бойер-Мур-Хорспул. Здесь на КС приводили парочку реализаций его. Одна из них, моего приготовления, использует строки, но переделать ее под массив байтов, я думаю, совсем несложно. Алгоритм несложный, но шустрый, особенно при больших подстроках. Может быть это пригодится.

29-11-2005 09:08 | Сообщение от автора вопроса
И еще.
Приведенные примеры не особенно убеждают.
Точнее - не убеждают ни в чем.
Я прерасно знаю, что для строковых констант (а именно такие варианты вы приводите) Delphi генерит код, заранее совместимый с типами String, ShortString(если возможно), PChar и array[0..]of char.
Так в данном конкретном случае что функции вполне могут работать нормально.

P.S.
Вспоминая(из другой оперы) разговоры про оптимизацию, кто-то расказывал, как Watcom довольно сложный пример скомпилил в один вызов функции Format с константными параметрами.
Будь Delphi поумнее - он в данном случае тоже сгенерил бы заместо всех вычислений единственный оператор присваивания результата.

29-11-2005 08:59 | Сообщение от автора вопроса
Всем спасибо за комментарии.
В SysUtils я первым делом и искал.
И потом сам написал функцию поиска, так что это уже пройдено. Но покольку эффективность не радует, решил спросить тут.

Вопросы все равно остаются:
1) Если просматривать больший массив поблочно, то при попадании "подстроки" на границе блоков она найдена не будет. Приходится сдвиг по массиву делать не кратным блоку, а меньше на лаг, заведомо больший длины "подстроки".
2) Информация находится в файле, поэтому я читаю не через поток, а выбрал способ с отображением файла в память. Но и тут не все гладко.
Невозможно объявить с некоторой позиции отображения фиктивную String-переменную, поскольку тогда у нее по отрицательному смещению [-4] не будет реального счетчика длины.
3) Если заводить отдельную переменную String и накладывать на нее буфер байт, то Delphi не поддерживает конструкцию absolute, поскольку длинные строки - суть динамически выделяемые массивы.
4) При помещении в буфер "подстроки" произвольных данных не получается применять Length, пока самостоятельно не задать SetLength. Но я же хочу иметь SetLength фиксированным (допустим = 100), а помещать в него подстроки любой длины - от 1 байта до 100 байт. А раздельных функций SetLength и SetAllocSize для длинных строк типа String не существует.
Так что подстрока в любом случае будет лежать в статическом array of byte, а длину реальных данных в нем приходится указывать через параметр.
А если передавть его в функции строкового поиска чарез приведение к PChar, то они-то КАК РАЗ реагируют на байт #0.

29-11-2005 01:52 | Комментарий к предыдущим ответам
Доброе утро,

Алексей это мое стандартное приветствие в почте, поэтому я его и в инете
вставляю

28-11-2005 08:42 | Комментарий к предыдущим ответам
Max111: Работает нормально: ищет в строке и все находит Уф, гора с плеч :o).

P.S. Доброе утро А у вас там сколько часов в сутках? :o)

28-11-2005 07:53
Доброе утро,

Алексей:
Признаю свою неправоту.
Пересел со стендового компа (стоит 98) на рабочий (стоит 2000)
Работает нормально: ищет в строке и все находит


28-11-2005 06:41 | Комментарий к предыдущим ответам
Проверь, а...
здесь и пример DRON'а и твой пример.

var
  Str: String;
begin
  Str := 'aaaaa'+ #0 + 'ffff' + #0#0 + 'fff';
  //Str := ' ¦-&N oh-L D@O OA@O AI' + #0#0 + 'zLEn?F; `YN §a(z)Cyn!O Y^ ¤+ 6b?`¦?"E  NA Z%AL?O7ET--. gL?y' + #0#0 + 'E#! 1 + -*OAyZa AOTn C¦';
  Caption := IntToStr(Pos(#0#0, Str));
end;


28-11-2005 06:32 | Комментарий к предыдущим ответам
Доброе утро,

Алексей: это маленький кусок бинарного файла с моего компа. Честно говоря
Я его просто скопировал  открыв по F3 в командере.  И ф-ция “POS” так-же как и POSEX
С такими строками не работает. Но ведь многие программы позволяют открывать
такие файлы как текстовые.


28-11-2005 03:26
Max11:
У тебя в строке ошибка. Должно быть вот так: St1:= ' ¦-&N oh-L D@O OA@O AI#0#0zLEn?F; `YN §a(z)Cyn!O Y^ ¤+ 6b?`¦?"E  NA Z%AL?O7ET--. gL?y'#0#0'E#! 1 + -*OAyZa AOTn C¦';

28-11-2005 03:21 | Комментарий к предыдущим ответам
Доброе утро

Уважаемый DRON
“Это не чушь”
В коде который приведен ниже
Вызывается ф-ция POS

Var
St1:String;
begin
St1:= ' ¦-&N oh-L D@O OA@O AI#0#0zLEn?F; `YN §a(z)Cyn!O Y^ ¤+ 6b?`¦?"E  N
A Z%AL?O7ET--. gL?y#0#0E#! 1 + -*OAyZa AOTn C¦';

Form1.Edit2.Text:=IntToStr(Pos(#0#0,St1));

В этом случае она НЕ ВОЗВРАЩАЕТ НИЧЕГО КРОМЕ нуля.
Проблемма эта давно известна.



27-11-2005 13:01
Естесс-но ведь, что обычный POS на первом же #0 байте в буфере остановиться, и дальше искать не будет.
Кто вам такую чушь сказал?

Pos(#0#0,'aaaaa'#0'ffff'#0#0'fff');

исправно выдаёт 11.

27-11-2005 10:42
Доброе утро,

Самый тупой но дыропрочный вариант:
1)Создаешь Memorystream где будет находиться вся твоя информация
2)Cоздаешь динамический массив в который записываешь порцию своей
информации из потока
3)Создаешь второй динамический массив . В него кладешь искомую информацию
4)Создаешь цикл и просматриваешь в цикле необходимую последовательность
5)Естественно поток байтов и массивы байтов

27-11-2005 10:28
http://www.delphikingdom.com/asp/answer.asp?IDAnswer=38009

27-11-2005 10:13
Изучи функцию PosEx, возможно тебе поможет. Кстати загляни в модуль StrUtils. Там много чего полезного для таких ситуаций.

Добавьте свое cообщение

Вашe имя:  [Войти]
Ваш адрес (e-mail):На Королевстве все адреса защищаются от спам-роботов
контрольный вопрос:
Жил-был у бабушки серенький КТО?
в качестве ответа на вопрос или загадку следует давать только одно слово в именительном падеже и именно в такой форме, как оно используется в оригинале.
Надоело отвечать на странные вопросы? Зарегистрируйтесь на сайте.
Тип сообщения:
Текст:
Жирный шрифт  Наклонный шрифт  Подчеркнутый шрифт  Выравнивание по центру  Список  Заголовок  Разделительная линия  Код  Маленький шрифт  Крупный шрифт  Цитирование блока текста  Строчное цитирование
  • вопрос Круглого стола № XXX

  • вопрос № YYY в тесте № XXX Рыцарской Квинтаны

  • сообщение № YYY в теме № XXX Базарной площади
  • обсуждение темы № YYY Базарной площади
  •  
     Правила оформления сообщений на Королевстве

    Страница избранных вопросов Круглого стола.
      
    Время на сайте: GMT минус 5 часов

    Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
    Функция может не работать в некоторых версиях броузеров.

    Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
    Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

     
    © При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
    Все используемые на сайте торговые марки являются собственностью их производителей.

    Яндекс цитирования