[Из песочницы] Передача файлов с помощью pipes и другие мелочи на Delphi
Поставили как-то передо мной задачу написать несколько юнитов на Delphi, обеспечивающие доступ к следующим возможностям Windows:
- Shared memory.
- Pipes.
Использовать компоненты сторонних разработчиков было нельзя. Shared memory предполагалось использовать для сортировки текстовых файлов больших размеров — несколько десятков или даже сотен мегабайт, на которых TStringList падал с EOutOfMemory. Pipes — для передачи файлов от одного приложения другому.
Переписать код примеров из документации Microsoft с C на Delphi — это было даже не полдела, а процентов 10. Главное было связать это все вместе, дописать, где нужно, дополнительные функции и заставить всё это работать, а не просто существовать в виде исходного кода. В результате родился такой уродливый монстр, что его не то, что показывать кому-то, а просто смотреть на него было страшно. Однако, может, кто-то найдет в его недрах что-то для себя полезное (а вдруг?), поэтому я решил его здесь выложить. На всеобщее поругание. Полный исходный код этой abomination выложен на GitHub. В тексте статьи будут даны названия файлов, где можно посмотреть тот или иной кусок реализации.
Первая задача — сортировка текстового файла — решалась так:
- Открываем файл (CreateFile).
- Используем CreateFileMapping и MapViewOfFile (common\MemoryMappedFileUnit.pas).
- Определяем кодировку файла. Здесь я ограничился, по сути, только двумя кодировками: UTF-16 и ACP — однобайтовая кодировка с кодовой страницей по умолчанию. В русскоязычной Windows обычно используется Windows-1251.
- Разбиваем текст на строки. То есть ищем маркеры конца строк и, соответственно, считаем текст от маркера до маркера одной строкой.
- Сортируем полученный массив строк (SortingTest\MergeSorterUnit.pas).
- Записываем отсортированный массив строк обратно в файл.
- PROFIT!
Сортировку заказчик обязательно хотел сделать многопоточной, поэтому в качестве алгоритма сортировки была взята Merge Sort. (Спасибо тому гуру, который создал рабочую реализацию и выложил ее на Stackoverflow.)
Вторая задача — передача файла от одного приложения к другому — оказалась куда сложнее. Получить реализацию pipes на Delphi на основе примеров Microsoft — это одно, но сделать из нее рабочую передавалку файлов — совсем другое. В качестве программы-сервера Microsoft предлагает несколько вариантов:
- Multithreaded Pipe Server.
- Named Pipe Server Using Overlapped I/O.
- Named Pipe Server Using Completion Routines.
Как выяснилось в результате двухнедельных мучений, реализация механизма передачи файла на основе первого варианта сервера (Multithreaded Pipe Server) оказывается слишком сложной. Разумнее использовать второй вариант (Named Pipe Server Using Overlapped I/O), с ним реализация получается куда проще (правда, заказчик по каким-то своим причинам очень хотел именно первый вариант сервера). Но всё равно пришлось помучиться, чтобы довести всё это дело до ума. В результате родился передатчик файлов на основе pipes TCommunicationEndpointBase (CommunicationTest\SimpleCommunicationUnit.pas).
Вкратце передача файлов работает так:
- Создаем сервер, то есть создаем named pipe.
- Создаем клиент, который подключается к этому named pipe.
- Крутим цикл «посылаем запрос — ждем ответ — читаем ответ» на клиенте и на сервере.
- Большие файлы посылаем частями и потом собираем файл из этих кусочков.
- По окончании передачи файлов закрываем соединение.
Все нюансы реализации можно посмотреть на GitHub, где выложен полный код всего вышеперечисленного. Всё оформлено в виде одного приложения (BlitzTest\BlitzTest.dproj), достаточно его скачать, собрать и запустить.
Да, еще там до кучи лежит оптимизированная реализация функции StringReplace (BlitzTest\StringReplaceCustomUnit.pas). Может, кому пригодится.
Любые комментарии (в том числе и откровенная ругань) приветствуются.
Если вдруг (ну, а вдруг?) кого-то заинтересуют другие мои репозитории, то могу и о них пару слов написать. Если, конечно, этот текст вообще опубликуют.