Миграция на Unicode старых Delphi-проектов в большинстве компаний является необходимым шагом, поскольку старые приложения иначе начинают сталкиваться с ограничениями при работе с международными данными, современными операционными системами, интеграциями и новыми интерфейсами. На практике это редко сводится к «перекомпиляции и готово». Delphi с момента внедрения Unicode (начиная с Delphi 2009) внесли фундаментальные изменения в стандартные строковые типы. Это сместило предположения о кодировке символов, layout памяти и сигнатурах API. Если это недооценивать, появляются постепенные ошибки данных, сломанные экспорты, неясные обращения в поддержку и риски безопасности.
В этой статье изложен технически обоснованный подход: как проанализировать существующий код, разумно ограничить объём работ, снизить риски в горячих точках (базы данных, файлы, Windows-API, COM, REST-сервисы) и защитить миграцию так, чтобы эксплуатация и дальнейшая разработка могли идти параллельно. Фокус на типичных подводных камнях Delphi в VCL-приложениях, службах и интерфейсах — с прицелом на пути модернизации, в которые позже можно встроить такие темы, как BDE-замещение с нативным подключением, REST-сервера или мультиплатформенность.
Почему переход на Unicode в Delphi часто оказывается «больше, чем ожидалось»
В классических версиях Delphi тип string был ANSI-строкой (в зависимости от системной codepage). Начиная с Delphi 2009 string по умолчанию — это UnicodeString (UTF-16). Одновременно многие библиотеки и VCL-классы были переведены на Wide-APIs. Это в общем случае положительно, потому что надёжно поддерживает международные символы. Но унаследованный код зачастую годами строился вокруг допущений «1 символ = 1 байт», «PChar — это PAnsiChar» или «Length() равен количеству байт».
Типичные причины, почему миграции становятся более трудоёмкими:
- Неявные конверсии проходят, но меняют данные (особенно при работе с файлами, интерфейсами или BLOB/Text-полями в БД).
- Код, ориентированный на байты (потоки, буферы, хеширование, шифрование) становится незаметно неверным, если содержимое строк интерпретируется как байты.
- Сторонние компоненты частично поддерживают только ANSI или используют собственные строковые типы и callback’и.
- Внешняя среда (Windows-API, COM, печать/отчёты, EDI, CSV, XML/JSON) ожидает определённые кодировки.
Цель не должна сводиться к «как можно меньше менять», а к целенаправленному изменению там, где нужно однозначно определить потоки данных и кодировки. Корректная миграция на Unicode — это также возможность документировать и протестировать неочевидные границы кодирования.
Технические основы: строковые типы Delphi, кодировки и их побочные эффекты
string, UnicodeString, AnsiString, WideString — что действительно важно в проекте
Для миграции критично, какие типы используются на интерфейсах и в ключевых функциях:
- string: с Delphi 2009 это UnicodeString (UTF-16, reference-counted, семантика immutable через Copy-on-Write).
- AnsiString: байтовая строка с привязкой к codepage (в зависимости от версии Delphi codepage может храниться). Подходит, если внешняя интерфейс явно требует конкретной 8-битной кодировки.
- UTF8String: в новых версиях Delphi часто встречается как алиас/AnsiString с UTF-8 codepage; практичен для REST/JSON и многих протоколов.
- WideString: BSTR (COM), управление памятью через SysAllocString; сейчас чаще требуется только для отдельных COM-interop случаев.
- PChar: с Unicode-Delphi это PWideChar. Это одна из самых частых точек разрыва при Windows-вызовах.
При смешении этих типов происходят конверсии. Некоторые корректны, некоторые неожиданны: конверсия «правильна» только если вы знаете, какая codepage применялась у источника и какая ожидается у приёмника.
Внутри UTF-16, снаружи UTF-8: практическое руководство
В VCL-приложениях на Delphi часто выгодно внутри системы последовательно работать со string (UTF-16). Снаружи (в REST, файлах, messaging) в реальности доминирует UTF-8. Надёжная установка правил может выглядеть так:
- Внутри: string/UnicodeString как стандарт.
- Границы: при вводе/выводе явно конвертировать через TEncoding.UTF8 (или через определённые ANSI-codepages).
- Байтовая обработка: использовать TBytes вместо строк.
Это снижает количество неявных конверсий и делает ответственность проверяемой: «Где байты превращаются в текст и в какой кодировке?»
Инвентаризация: где Unicode в старых Delphi-проектах типично ломается
Прежде чем менять код, имеет смысл выполнить структурированный аудит. В миграциях на Unicode источники ошибок обычно сосредоточены не равномерно, а в нескольких горячих точках.
1) Доступ к базе данных и типы полей (BDE, ADO, FireDAC)
Многие старые проекты до сих пор используют BDE или более старые уровни доступа к данным. Здесь типичные проблемы:
- Соответствие charset базы данных и строк Delphi (ANSI против Unicode-полей).
- «Текст» в BLOB-ах или Memo-полях без определённой кодировки.
- SQL-операторы как строки, которые при наличии умляутов/Unicode-символов интерпретируются по-разному.
Если уже планируется модернизация, миграцию на Unicode удобно связать с ревизией доступа к данным, например в сторону BDE-Ablosung mit nativer Anbindung и явной настройки charset (как в PostgreSQL или MariaDB). Важно: миграция не обязательно должна автоматически влечь перенос БД, но интерфейс между БД и Delphi должен быть однозначным.
2) Файловый и потоковый I/O: CSV, INI, проприетарные форматы, импорт/экспорт
Классический случай: файлы ранее читались/писались через AssignFile/ReadLn, TFileStream или TStringList.LoadFromFile без явного указания кодировки. В Unicode-Delphi поведение определяется heuristisch (BOM) или используются кодировки по умолчанию. Это приводит к:
- неправильно интерпретированным умляутам (ä, ö) в CSV/логах,
- ошибочным значениям длины в проприетарных форматах,
- несовместимостям с внешними партнёрами, ожидающими ISO-8859-1 или Windows-1252.
Чистое решение — для каждого формата файла задать фиксированную кодировку и зафиксировать это в коде и документации. Для CSV/JSON обычно правильным стандартом является UTF-8, для старых интерфейсов иногда — Windows-1252. Ключевое — явность.
3) Windows-API, PChar, размеры буферов и обработка сообщений
Многие Delphi-приложения вызывают WinAPI-функции или работают с буферами. Частые точки разрыва:
- Использование PChar вместе с функциями, имеющими ANSI- и Wide-варианты (…A/…W).
- Размеры буферов считаются в байтах, но Char в UTF-16 занимает 2 байта.
- Указательная арифметика и layout Record-ов, рассчитанные на 1-байтовые Char.
Требуется точное рефакторинг: либо последовательно использовать Wide-API, либо сознательно вызывать ANSI-вариант и работать через AnsiString/соответствующую codepage. «Просто компилируется» — не критерий качества.
4) COM, ActiveX, Office-Automation и сторонние библиотеки
COM-интерфейсы часто работают через BSTR (WideString). В старых версиях Delphi поведение по умолчанию было иным, поэтому код «случайно» работал. В Unicode-версиях часто возникают двойные конверсии или неверные предположения о типах в обёртках. Сторонние библиотеки тоже критичны: некоторые возвращают callback’и как PAnsiChar, другие ожидают нуль-терминированные байтовые строки.
Здесь имеет смысл классифицировать зависимости: какая библиотека готова к Unicode, какая нет, и какие можно заменить или инкапсулировать. Инкапсуляция часто оказывается самым быстрым способом локализовать Unicode-унаследованность в чётко ограниченной области.
Стратегия: миграция старых Delphi-проектов как контролируемая программа модернизации
Наиболее безопасный путь — многоэтапная программа, которая делает риски видимыми на ранних стадиях и при этом сохраняет работоспособность приложения.
Шаг 1: определить объём работ и приоритизировать горячие участки кода
Не весь исходный код требует немедленных изменений. Приоритизируйте по потокам данных и риску:
- Интерфейсы наружу (REST-API, TCP/IP, файлы, e‑mail, печать/отчёты).
- Доступ к данным (SQL, ORM/Datamodule, BDE/FireDAC-слои).
- Утилиты, тесно работающие со строками (парсеры, форматтеры, encoder/decoder).
- Интеграции (COM, DLL-imports, подключение оборудования).
Результатом должна стать перечень мест, где «кодировка — это спецификация». Эти места затем делаются тестируемыми.
Шаг 2: жёстко включить опции компилятора и предупреждения
Во многих проектах предупреждения годами отключались. Для миграции на Unicode это контрпродуктивно. Рекомендуется вновь включить предупреждения и серьёзно относиться к предупреждениям о конверсиях. Дополнительно полезно ввести проектные правила, например: никаких неявных AnsiString-конверсий на границах I/O, использовать TEncoding при файловых операциях, никаких «PChar‑трюков» без ясного контекста.
Шаг 3: ввести «границы кодировок» как технический слой
Практичный архитектурный приём — добавить небольшие адаптеры/хелперы, которые чётко определяют, как внешние данные входят и выходят. Примеры:
- CSV-ридер/-райтер: всегда с TEncoding.UTF8 (или определённой codepage) и ясными правилами разделителей.
- REST-клиент/сервер: JSON всегда как UTF-8-байты, заголовки корректно выставлены, тело не стримится «как строка».
- Windows-API-обёртки: центральные функции, которые чисто инкапсулируют Wide/Ansi.
Так вы не допустите, чтобы «решения о кодировке» распределялись по всей кодовой базе.
Типичные кодовые ловушки и как их корректно исправлять
Length, SizeOf, ByteLength: когда длина в символах и в байтах расходятся
Во времена ANSI Length(s) часто использовали как число байт. В UTF-16 это неверно. Если нужны массивы байт, конвертируйте явно:
- Для UTF-8: TEncoding.UTF8.GetBytes(s)
- Для определённой ANSI-codepage: TEncoding.GetEncoding(1252).GetBytes(s) (только если это семантически верно)
Для размеров буферов при вызовах API проверьте, ожидает ли функция единицы измерения в символах или в байтах. Многие Wide-API ожидают количество символов, а не байт. Руководящими являются документация и сигнатура, а не интуиция.
PAnsiChar vs. PWideChar: DLL-imports и внешние протоколы
При DLL-imports высок риск, что сигнатуры в коде Delphi перестанут соответствовать. Установите, чего именно ожидает DLL:
- Ожидает ли DLL UTF-8? Тогда распространённая практика — передача PAnsiChar(UTF8String), но при этом нужно контролировать время жизни и нуль-терминирование.
- Ожидает ли она UTF-16? Тогда используйте PWideChar и Wide-строки.
Во всех случаях полезно инкапсулировать импорты в отдельной Unit, чтобы политика обращения со строками не размывалась по проекту.
Форматирование, изменение регистра, сравнение: локаль и нормализация
Unicode добавляет и семантические вопросы: приведение к верхнему/нижнему регистру не тривиально во всех языках, символы могут существовать в разных формах нормализации. Для корпоративных приложений это обычно менее критично, чем для текстовых редакторов, но затрагивает:
- сортировки и фильтрацию (например, в гридах или поиске),
- регистронезависимые сравнения ключевых значений,
- генерацию имён файлов или идентификаторов.
Важна ясная политика: какие поля являются «ключами» (например, артикулы, коды клиентов), которые должны оставаться ASCII-близкими, и какие — «тексты», требующие полной поддержки Unicode. Такое разделение снижает последующие ошибки.
GUI/Reporting: шрифты, печать, PDF и поведение компонентов
VCL с Unicode-версиями в целом поддерживает Unicode, но на практике всё сводится к компонентам и путям вывода. Риски возникают при:
- старых генераторах отчётов или PDF-генераторах, предполагающих ANSI,
- печати штрихкодов/этикеток, требующей конкретных codepage,
- жёстко заданных шрифтах или наборах символов.
Планируйте ранние тесты на реальных примерах (имена, места, спецсимволы, нелатинские алфавиты, если они релевантны). Ценность не в том, что компонент «умеет Unicode», а в подтверждении: «Этот вывод в нашем контексте корректен.»
Данные и персистентность: Unicode не заканчивается в коде
Чёткое задание Charset и Collation в БД
Миграция на Unicode стабильна только если базы данных и драйверы настроены правильно. Примеры:
- В PostgreSQL UTF-8 обычно является стандартом; тем не менее нужно проверить client-encoding и поведение драйвера.
- В SQL Server важно различие между VARCHAR и NVARCHAR; неверный выбор столбца может привести к потере символов.
- В MariaDB/MySQL charset/collation (например, utf8mb4) критичны, чтобы 4-байтовые символы не обрезались.
В Delphi-коде параметры и типы полей должны использоваться так, чтобы Unicode не «конвертировался обратно» по пути. FireDAC обычно даёт лучшую контрольность по сравнению с очень старыми слоями доступа.
Legacy-файловые форматы: правила миграции вместо тихой конверсии
Если ваше приложение годами генерировало файлы (экспортные форматы, архивы, проприетарные структуры), нужно определить:
- Какие существующие файлы остаются «как есть» и будут корректно интерпретироваться при чтении?
- Какие форматы будут переведены на UTF-8?
- Есть ли поля версии/заголовки, чтобы однозначно отличать новые и старые файлы?
Тихая конверсия без маркировки рискованна, потому что ошибки часто проявляются поздно. Лучше: версионировать, распознавать явно и мигрировать целенаправленно.
Контроль качества: тесты, которые действительно находят Unicode-проблемы
Ошибки Unicode часто зависят от данных. Поэтому «happy path» тесты недостаточны. Полезен тестовый набор, покрывающий проблемные зоны:
- Roundtrip-тесты: импорт → обработка → экспорт с последующим пословным/байтовым сравнением (для определённых форматов).
- DB-Roundtrip: запись/чтение текстов с умляутами, акцентами и, при необходимости, нелатинскими символами; проверка эквивалентности.
- Интерфейсные тесты: REST-запросы как UTF-8, корректные заголовки, JSON-эскейпинг, логирование.
- Регрессии: воспроизведение старых данных и типовых пользовательских сценариев, особенно поиск, фильтрация, сортировка.
Для B2B-систем также важно, чтобы ошибки были наблюдаемы: логирование не должно разрушать кодировки. Если логи пишутся в ANSI, в случае ошибки вы теряете именно ту информацию, которая нужна.
Планирование и объём работ: что действительно увеличивает сложность
Объём работ по миграции старых Delphi-проектов зависит не столько от количества строк, сколько от связей и внешних зависимостей:
- Много интеграций (DLL, COM, устройства, ERP/DMS/CRM) увеличивают объём проверок, потому что кодировки важны на каждой границе.
- Исторические форматы (старые экспорты, кастомные CSV) требуют правил миграции и стратегий совместимости.
- Смешанные версии Delphi или несколько продуктов из одного кодового ствола увеличивают координационные усилия.
- Старые слои доступа к данным (например, BDE) могут косвенно блокировать Unicode и накладывать необходимость модернизации.
На практике эффективна стратегия: сначала стабилизировать Unicode в ядре и в самых критичных потоках данных. Затем постепенно приводить остальные модули. Это снижает риски и предотвращает долгие «Big Bang» этапы без релиза.
Встраивание в пути модернизации: REST, сервисы, мультиплатформа
Unicode часто является фундаментом, когда речь идёт о модернизации существующего ПО. Типичные сопутствующие вопросы:
- REST-сервера или REST-API — добавить (обрабатывать JSON/UTF-8 корректно).
- Стабильно эксплуатировать Windows-сервисы или Linux-сервисы (логирование, конфиги, протоколы).
- Пошаговая модернизация UI в VCL, позднее — возможные мультиплатформенные клиенты.
Порядок важен: если вы создаёте новые интерфейсы, правила по кодировкам должны быть утверждены заранее. Миграция на Unicode «в процессе» разработки интерфейсов приведёт к трудноотслеживаемым ошибкам, где причина и следствие смешиваются.
Для внутренних ссылок в журнале имеет смысл разместить смежные темы, такие как модернизация Delphi, доступ к данным через FireDAC или архитектура REST-серверов как углубляющие статьи, чтобы читатели могли перейти к следующему техническому шагу.
Вывод: миграция на Unicode — это риск, но с правильным методом он становится планируемым
Миграция на Unicode старых Delphi-проектов — не косметическое обновление, а исправление базовых допущений о тексте, байтах и интерфейсах. При структурированном подходе вы получите больше, чем «умляуты снова работают»: потоки данных станут однозначными, интеграции — более надёжными, а последующая модернизация (например, REST-сервера, сервисы, очистка БД) упростится, поскольку кодировки перестанут происходить неявно «где‑то там».
Если вам нужен конкретный план миграции для вашей Delphi-системы, анализ рисков по горячим точкам или помощь в реализации — самым быстрым следующим шагом будет техническая предварительная встреча по вашим рамкам и зависимостям: свяжитесь с нами.
В профессиональном контексте также важны темы Delphi Unicode Migration и Delphi Ansi Zu Unicode, когда интеграции, потоки данных и дальнейшая разработка должны работать согласованно.