Міграція в Unicode старих Delphi-проектів в багатьох компаніях — необхідний крок, інакше існуючі застосунки з часом досягають обмежень при роботі з міжнародними даними, сучасними ОС, інтеграціями й новими інтерфейсами. На практиці це рідко буває «Recompile und fertig». Delphi з часів версій Unicode (починаючи з Delphi 2009) вніс фундаментальні зміни у стандартні типи рядків. Це зміщує припущення про кодування символів, макет пам’яті та сигнатури API. Хто це недооцінює, ризикує отримати поступово накопичувані помилки даних, зламані експортні файли, незрозумілі інциденти підтримки і проблеми безпеки.
У цьому матеріалі запропоновано технічно обґрунтований підхід: як провести аналіз коду, розумно обмежити scope, зменшити ризики в «гарячих точках» (бази даних, файли, Windows-API, COM, REST-сервіси) і зафіксувати міграцію так, щоб експлуатація та подальша розробка могли йти паралельно. Фокус — на типових підводних каменях Delphi у VCL-застосунках, сервісах і інтерфейсах — з поглядом на шляхи модернізації, в які пізніше можна вбудувати теми на кшталт BDE-вилучення з нативним підключенням, REST-серверів або мультиплатформених рішень.
Чому перехід на Unicode у Delphi часто виявляється «більшим, ніж очікували»
У класичних версіях Delphi string був ANSI-рядком (залежно від системної codepage). З Delphi 2009 string за замовчуванням — UnicodeString (UTF-16). Водночас багато бібліотек і VCL-класів було переведено на Wide-API. Це в цілому позитивно, оскільки забезпечує надійну підтримку міжнародних символів. Але: у спадщині часто накопичувався код, що базується на припущеннях «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 часто як alias/AnsiString з UTF-8 codepage; практичний варіант для REST/JSON і багатьох протоколів.
- WideString: BSTR (COM), керується через SysAllocString; сьогодні потрібен переважно для специфічних COM-interop сценаріїв.
- PChar: з часу Unicode-Delphi це здебільшого PWideChar. Це одна з найпоширеніших точок руйнування при викликах Windows-API.
Якщо ці типи змішуються, виникають конвертації. Деякі з них коректні, інші — несподівані: конвертація є «правильною» лише тоді, коли ви знаєте, яка codepage використовується в джерелі й яка очікується на приймаючому боці.
UTF-16 внутрішньо, UTF-8 зовні: практичний принцип
У VCL-застосунках Delphi часто доцільно послідовно працювати внутрішньо з string (UTF-16). Зовні (REST, файли, messaging) у реальності домінує UTF-8. Робоча політика може бути такою:
- Внутрішньо: string/UnicodeString як стандарт.
- На межах: при вводі/виводі конвертація явно через TEncoding.UTF8 (або визначені ANSI-codepage).
- Байтові операції: TBytes замість рядків.
Це зменшує неявні перетворення і робить відповідальності тестованими: «Де байти стають текстом, і в якому кодуванні?»
Інвентаризація: де Unicode у старих Delphi-проектах зазвичай дає збої
Перш ніж торкатися коду, варто провести структуровану інвентаризацію. У міграції Unicode старих Delphi-проєктів джерела помилок зазвичай не розподілені рівномірно, а концентруються в кількох «гарячих точках».
1) Доступ до баз даних і типи полів (BDE, ADO, FireDAC)
Багато старих проектів досі використовують BDE або застарілі рівні доступу до даних. Тут проблеми найчастіше такі:
- Відповідність charset бази даних та Delphi-рядків (ANSI vs. Unicode-типи полів).
- «Текст» у BLOB чи Memo-полях без визначеного кодування.
- SQL-вирази як рядки, які при наявності умляутів/Unicode-символів інтерпретуються по-різному.
Якщо все одно планується модернізація, міграцію в Unicode доцільно поєднати з очищенням доступу до даних, наприклад у бік BDE-Ablosung mit nativer Anbindung і чіткої конфігурації Charset (наприклад, для PostgreSQL або MariaDB). Важливо: міграція не повинна автоматично означати примусову зміну СУБД, але інтерфейс між БД і Delphi має бути однозначним.
2) Файловий та Stream-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, яка ні, і яку можна замінити або інкапсулювати? Інкапсуляція часто є найшвидшим шляхом, щоб перемістити спадщину ANSI у чітко окреслену область.
Стратегія: міграція Unicode старих Delphi-проєктів як контрольована програма модернізації
Найбільш безпечний підхід — багатоступенева програма, яка робить ризики помітними на ранніх етапах і при цьому зберігає застосунок працездатним.
Крок 1: визначити scope і пріоритизувати «гарячі точки» коду
Не кожен файл коду потребує негайних змін. Пріоритезуйте за потоками даних і ризиком:
- Інтерфейси назовні (REST-API, TCP/IP, файли, e‑mail, друк/репортинг).
- Доступ до даних (SQL, ORM/Datamodule, BDE/FireDAC-шари).
- Утиліти, що працюють із рядками (парсери, форматувальники, енкодери/декодери).
- Інтеграції (COM, DLL‑imports, підключення обладнання).
Результат має бути списком місць, де «кодування — це специфікація». Ці точки пізніше роблять тестованими.
Крок 2: свідомо загострити попередження компілятора/проекту
У багатьох проєктах протягом років вимикалися попередження. Для Unicode-міграції це контрпродуктивно. Варто знову ввімкнути попередження і серйозно ставитися до попереджень про конвертації. Додатково корисно впровадити проектні правила, наприклад: жодних неявних AnsiString-конвертацій на межах I/O, використання TEncoding при файлових операціях, жодних «PChar-трюків» без чіткого контексту.
Крок 3: ввести «границі кодування» як технічний шар
Практичний архітектурний прийом — додати невеликі адаптери/хелпери, які точно визначають, як зовнішні дані входять і виходять. Приклади:
- CSV-Reader/-Writer: завжди з 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-імпорти і зовнішні протоколи
У DLL-імпортах велика ймовірність того, що сигнатури в коді Delphi більше не підходять. Визначте, чого очікує DLL:
- Якщо DLL очікує UTF-8, тоді звичайна практика — передавати PAnsiChar(UTF8String), але потрібно контролювати час життя рядка і нуль-термінування.
- Якщо вона очікує UTF-16, використовуйте PWideChar і Wide-рядки.
У будь-якому випадку імпорти краще інкапсулювати в окремій unit, щоб політика роботи з рядками не поширювалася по всьому проєкту.
Форматування, зміна регістру, порівняння: локаль і нормалізація
Unicode також вносить семантичні питання: upper/lower не тривіальні у всіх мовах, і символи можуть мати різні форми нормалізації. У типовому корпоративному ПЗ це рідше критично, ніж у текстових редакторах, але це стосується:
- сортування і фільтрації (наприклад у грідах або пошукових функціях),
- регістронезалежних порівнянь для ключових значень,
- генерації імен файлів або ідентифікаторів.
Важливо мати чітке правило: що є «ключами» (наприклад, артикульні номери, коди клієнтів), які повинні лишатися 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 зазвичай дає кращий контроль, ніж дуже старі рівні доступу.
Старі формати файлів: правила міграції замість тихої конвертації
Якщо ваш застосунок роками створював файли (експортні формати, архіви, пропрієтарні структури), потрібно визначити:
- Які наявні файли залишаються «як є» і як їх правильно інтерпретувати при читанні?
- Які формати піднімаються до UTF-8?
- Чи є в заголовках/версіях поля, що однозначно відрізняють нові й старі файли?
Тиха конвертація без маркування ризикова, бо помилки частіше виявляються пізно. Краще: версіонувати, чітко визначати і здійснювати цілеспрямовану міграцію.
Забезпечення якості: тести, які дійсно знаходять Unicode-проблеми
Unicode-помилки часто залежать від даних. Тому «Happy Path»-тестів недостатньо. Доцільний набір тестів має покривати проблемні місця:
- Roundtrip-тести: імпорт → обробка → експорт, потім побайтове порівняння (для визначених форматів).
- DB-Roundtrip: запис/читання текстів з умляутами, акцентами і за потреби нелатинськими символами; перевірка на тотожність.
- Тести інтерфейсів: REST-запити як UTF-8, перевірка заголовків, JSON-escape, логування.
- Регресія: відтворення старих даних і типових користувацьких сценаріїв, особливо у пошуку, фільтрах, сортуванні.
Для B2B-систем також важливо, щоб помилки були спостережувані: логування не має псувати кодування. Якщо логи пишуться в ANSI, у разі помилки ви втрачаєте саме ту інформацію, яка потрібна для діагностики.
Планування і обсяг робіт: що справді визначає складність
Обсяг робіт з міграції Unicode у старих Delphi-проектах залежить менше від «кількості рядків» і більше від зчеплень і зовнішніх залежностей:
- Багато інтеграцій (DLL, COM, пристрої, ERP/DMS/CRM) збільшують обсяг перевірок, оскільки кодування важливе на кожній межі.
- Історичні формати (старі експорти, кастомні CSV) потребують правил міграції і стратегій сумісності.
- Змішані версії Delphi або декілька продуктів з одного code‑base підвищують потребу в координації.
- Старі рівні доступу до даних (наприклад BDE) можуть опосередковано блокувати Unicode і вимагати модернізації.
На практиці працює підхід: спочатку стабілізувати Unicode у «серці» і в найкритичніших потоках даних. Далі поетапно переносити модулі. Це зменшує ризик і запобігає тривалим «big bang» фазам без релізів.
Вбудування в шляхи модернізації: REST, сервіси, мультиплатформа
Unicode часто є базовим елементом, коли потрібно модернізувати існуюче ПЗ. Типові питання далі:
- REST-сервери або REST-API — додати (JSON/UTF-8 має оброблятися коректно).
- Стабільна експлуатація Windows-сервісів або Linux-сервісів (логування, конфіг-файли, протоколи).
- Поступова модернізація UI у VCL, з можливим подальшим переходом на мультиплатформенні клієнти.
Важливий порядок: якщо ви будуєте нові інтерфейси, правила кодування мають бути визначені завчасно. Міграція Unicode «паралельно» зі створенням інтерфейсів дає важкопрозорі помилки, бо причина і наслідок перемішуються.
Для внутрішньої перелінковки в NB Magazin доцільно розмістити суміжні теми, як-от модернізацію Delphi, доступ до даних через FireDAC або архітектуру REST-серверів як поглиблені статті, щоб читачі могли цілеспрямовано переходити до наступного технічного кроку.
Висновок: міграція Unicode — це питання ризику, яке з правильною методикою стає планованим
Міграція в Unicode старих Delphi-проектів — це не косметичне оновлення, а виправлення базових припущень про текст, байти і інтерфейси. При структурованому підході ви отримаєте більше, ніж «умляути знову працюють»: потоки даних стануть однозначними, інтеграції — стійкішими, а подальша модернізація (наприклад REST-сервери, сервіси, очищення баз даних) стане простішою, оскільки кодування більше не відбуваються неявно «деінде».
Якщо вам потрібен конкретний план міграції для вашого Delphi-застосунку, аналіз ризиків по гарячих точках або допомога з реалізацією, найшвидший наступний крок — технічна вступна розмова про ваші рамкові умови і залежності: зв