Migracja do Unicode starych Delphi-projektów jest w wielu przedsiębiorstwach niezbędnym krokiem, ponieważ aplikacje utrzymaniowe w przeciwnym razie napotykają coraz większe ograniczenia przy obsłudze danych międzynarodowych, nowoczesnych systemów operacyjnych, integracji i nowych interfejsów. W praktyce rzadko jest to „ponowne skompilowanie i po sprawie“. Delphi od wersji Unicode (od Delphi 2009) wprowadził zasadnicze zmiany w standardowych typach string. To przesuwa założenia dotyczące kodowania znaków, układu pamięci i sygnatur API. Kto to zlekceważy, naraża się na powolne błędy danych, uszkodzone eksporty, niejasne zgłoszenia serwisowe i ryzyka bezpieczeństwa.
Artykuł dostarcza technicznie podpartą procedurę: jak przeanalizować istniejący kod, sensownie określić zakres, zredukować ryzyka w miejscach newralgicznych (bazy danych, pliki, Windows-API, COM, REST-usługi) i zabezpieczyć migrację tak, aby utrzymanie i rozwój mogły przebiegać równolegle. Fokus leży na typowych pułapkach Delphi w aplikacjach VCL, usługach i interfejsach — z perspektywą ścieżek modernizacyjnych, w które później wpiszą się też tematy takie jak BDE-zastąpienie z natywnym podłączeniem, REST-serwery czy multiplatformowość.
Dlaczego przejście na Unicode w Delphi jest tak często „większe niż się wydaje“
W klasycznych wersjach Delphi typ string był ANSI-Stringiem (w zależności od codepage systemu). Od Delphi 2009 string jest domyślnie UnicodeString (UTF-16). Równocześnie wiele bibliotek i klas VCL zostało przeniesionych na Wide-API. To ogólnie pozytywne, bo wspiera znaki międzynarodowe stabilnie. Jednak kod legacy często przez lata rozwijał się wokół założeń „1 Zeichen = 1 Byte“, „PChar ist PAnsiChar“ lub „Length() odpowiada liczbie bajtów“.
Typowe przyczyny, dlaczego migracje stają się bardziej pracochłonne:
- Implizytne konwersje faktycznie zachodzą, ale zmieniają dane (szczególnie przy plikach, interfejsach lub polach BLOB/tekst w bazach danych).
- Kod operujący na bajtach (strumienie, bufory, haszowanie, szyfrowanie) staje się niewidocznie błędny, jeśli zawartość stringów jest interpretowana jako bajty.
- Komponenty firm trzecich bywają wyłącznie ANSI lub używają własnych typów string i callbacków.
- Otoczenie zewnętrzne (Windows-API, COM, druk/raportowanie, EDI, CSV, XML/JSON) oczekuje konkretnych enkodowań.
Celem nie powinno więc być „zmienić jak najmniej“, lecz celowo zmieniać tam, gdzie trzeba zdefiniować przepływy danych i enkodowania. Czysta migracja do Unicode to też okazja, by udokumentować i przetestować niejasne granice kodowania.
Techniczne podstawy: Delphi-typy stringów, enkodowania i ich skutki uboczne
string, UnicodeString, AnsiString, WideString – co naprawdę ma znaczenie w projekcie
Dla migracji kluczowe jest, które typy są używane na interfejsach i w funkcjach rdzeniowych:
- string: Od Delphi 2009 jest UnicodeString (UTF-16, reference-counted, semantyka niemutowalna przez Copy-on-Write).
- AnsiString: string bajtowy z przypisaną codepage (w zależności od wersji Delphi codepage może być przechowywana). Przydatny, gdy zewnętrzny interfejs wyraźnie wymaga określonego kodowania 8-bitowego.
- UTF8String: W nowszych wersjach Delphi często alias/AnsiString z codepage UTF-8; praktyczny dla REST/JSON i wielu protokołów.
- WideString: BSTR (COM), zarządzany pamięciowo przez SysAllocString; dziś potrzebny głównie do określonych interopów COM.
- PChar: W wersjach Unicode Delphi PWideChar. To jeden z częstszych punktów złamania przy wywołaniach Windows-API.
Gdy typy te są mieszane, zachodzą konwersje. Niektóre są poprawne, inne zaskakujące: konwersja jest „właściwa“ tylko wtedy, gdy wiadomo, jaka codepage jest u źródła i jaka jest oczekiwana u celu.
UTF-16 wewnętrznie, UTF-8 zewnętrznie: praktyczna zasada
W aplikacjach VCL opartych na Delphi często sensowne jest konsekwentne używanie wewnętrznie string (UTF-16). Na zewnątrz (REST, pliki, messaging) w praktyce dominuje UTF-8. Solidna zasada brzmi więc:
- Wewnątrz: string/UnicodeString jako standard.
- Granice: przy wejściu/wyjściu explicite konwertować przez TEncoding.UTF8 (lub zdefiniowane codepage ANSI).
- Przetwarzanie bajtowe: TBytes zamiast stringów.
To ogranicza implizytne konwersje i sprawia, że odpowiedzialności są weryfikowalne: „Gdzie bajty stają się tekstem i jakim enkodowaniem?”.
Inwentaryzacja: gdzie Unicode w starych projektach Delphi zwykle zawodzi
Zanim dotknął Pan/Pani kodu, warto przeprowadzić uporządkowaną inwentaryzację. W migracjach Unicode starych projektów Delphi źródła błędów zwykle nie rozkładają się równomiernie, lecz koncentrują się w kilku hotspotach.
1) Dostęp do bazy danych i typy pól (BDE, ADO, FireDAC)
Wiele projektów legacy nadal korzysta z BDE lub starszych warstw dostępu do danych. Typowe problemy:
- Mapowanie charsetów bazy do stringów Delphi (ANSI vs typy pól Unicode).
- „Tekst” w BLOB-ach lub polach Memo bez zdefiniowanego kodowania.
- Zapytania SQL jako stringi, które przy umlautach/znakach Unicode są różnie interpretowane.
Jeśli i tak planowana jest modernizacja, migrację do Unicode warto połączyć z uporządkowaniem dostępu do danych, np. w kierunku BDE-Ablosung mit nativer Anbindung i jasnej konfiguracji charsetów (np. przy PostgreSQL czy MariaDB). Ważne: migracja nie musi automatycznie wymuszać migracji bazy danych, ale interfejs między DB a Delphi musi być jednoznaczny.
2) I/O plików i strumieni: CSV, INI, formaty proprietarne, import/eksport
Klasyka: pliki dawniej czytano/zapiswano przez AssignFile/ReadLn, TFileStream lub TStringList.LoadFromFile, bez ustawionego enkodowania. W Unicode-Delphi decyzję często podejmuje heurystyka (BOM) lub stosowane są domyślne enkodowania. To prowadzi do:
- niepoprawnie interpretowanych umlautów (ä, ö) w CSV/logach,
- błędnych długości w formatach proprietarnych,
- niekompatybilności z partnerami zewnętrznymi, którzy oczekują ISO-8859-1 lub Windows-1252.
Czystym rozwiązaniem jest dla każdego formatu pliku zdefiniować stałe enkodowanie i zakotwiczyć to w kodzie oraz dokumentacji. Dla CSV/JSON zazwyczaj właściwym standardem jest UTF-8, dla starych interfejsów czasami Windows-1252. Kluczowa jest explicytność.
3) Windows-API, PChar, rozmiary buforów i obsługa komunikatów
Wiele aplikacji Delphi wywołuje funkcje WinAPI lub operuje na buforach. Częste punkty złamania:
- Użycie PChar w kontekście funkcji mających warianty ANSI lub Wide (…A/…W).
- Rozmiary buforów liczone są w bajtach, podczas gdy Char w UTF-16 zajmuje 2 bajty.
- Arytmetyka wskaźników i układy rekordów opierające się na 1-bajtowych Char.
Tu potrzebne jest precyzyjne refaktoryzowanie: albo konsekwentnie używać Wide-API, albo świadomie wywoływać wariant ANSI i pracować z AnsiString/codepage. „Jakoś się kompiluje“ nie jest kryterium jakości.
4) COM, ActiveX, automatyzacja Office i biblioteki zewnętrzne
Interfejsy COM często pracują z BSTR (WideString). Starsze wersje Delphi miały inne domyślne stringi, przez co kod „przypadkowo“ działał. W Unicode-Delphi często powstają podwójne konwersje lub błędne założenia typów w wrapperach. Biblioteki stron trzecich też bywają krytyczne: jedne dostarczają callbacki jako PAnsiChar, inne oczekują zeroterminowanych stringów bajtowych.
Warto sklasyfikować zależności: która biblioteka jest gotowa na Unicode, która nie, i którą można zastąpić lub opakować. Kapsułowanie zależności jest często najszybszym sposobem, by skumulować długi ogon legacy w jasno określonym obszarze.
Strategia: migracja Unicode starych projektów Delphi jako kontrolowany program modernizacyjny
Najbezpieczniejsze podejście to wielostopniowy program, który szybko ujawnia ryzyka i jednocześnie utrzymuje aplikację w stanie działającym.
Krok 1: określić scope i priorytetyzować hot-spoty w kodzie
Nie każdy fragment kodu wymaga natychmiastowej zmiany. Priorytetyzuj według przepływu danych i ryzyka:
- Interfejsy na zewnątrz (REST-API, TCP/IP, pliki, e-mail, druk/raportowanie).
- Dostęp do danych (SQL, ORM/Datamodule, BDE/FireDAC-warstwy).
- Funkcje narzędziowe blisko stringów (parsery, formatery, enkoder/dekoder).
- Integracje (COM, importy DLL, podłączenia sprzętowe).
Wynikiem powinna być lista miejsc, gdzie „enkodowanie ma specyfikację“. Te miejsca zostaną potem objęte testami.
Krok 2: świadomie zaostrzyć opcje kompilatora/projektu i ostrzeżenia
W wielu projektach ostrzeżenia były przez lata wyłączane. Przy migracji Unicode to kontrproduktywne. Warto włączyć ostrzeżenia i traktować ostrzeżenia konwersji poważnie. Pomocne jest też ustalenie reguł projektowych, np.: brak implizytnej konwersji AnsiString na granicach I/O, używanie TEncoding przy operacjach na plikach, brak „PChar-trików“ bez klarownego kontekstu.
Krok 3: wprowadzić „granice enkodowania“ jako warstwę techniczną
Praktyczny zabieg architektoniczny to wprowadzenie małych adapterów/helperów, które jednoznacznie definiują, jak dane zewnętrzne wchodzą i wychodzą. Przykłady:
- CSV-Reader/-Writer: zawsze z TEncoding.UTF8 (lub zdefiniowaną codepage) i jasnymi regułami separatorów.
- REST-klient/serwer: JSON zawsze jako bajty UTF-8, poprawne ustawianie headerów, nie „streamować“ body jako string.
- Windows-API-wrapper: centralne funkcje, które czysto kapsułują Wide/Ansi.
To zapobiega rozproszeniu decyzji o enkodowaniu po całej bazie kodu.
Typowe pułapki w kodzie i jak je poprawnie naprawić
Length, SizeOf, ByteLength: gdy długość znaków i rozmiar bajtów się rozjeżdżają
W czasach ANSI Length(s) często nadużywano jako liczby bajtów. W UTF-16 to nieprawda. Gdy potrzebne są tablice bajtów, konwertuj explicite:
- Dla UTF-8: TEncoding.UTF8.GetBytes(s)
- Dla określonej codepage ANSI: TEncoding.GetEncoding(1252).GetBytes(s) (tylko gdy fachowo uzasadnione)
Dla rozmiarów buforów przy wywołaniach API: sprawdź, czy funkcja oczekuje jednostek znakowych czy bajtowych. Wiele Wide-API oczekuje liczby znaków, nie bajtów. Decyduje dokumentacja i sygnatura, nie intuicja.
PAnsiChar vs. PWideChar: importy DLL i protokoły zewnętrzne
Przy importach DLL duże jest ryzyko, że sygnatury w kodzie Delphi przestaną pasować. Ustal, czego DLL oczekuje:
- Oczekuje UTF-8? Wtedy przekazywanie jako PAnsiChar(UTF8String) jest powszechne, ale trzeba kontrolować żywotność i zeroterminowanie.
- Oczekuje UTF-16? Wtedy użyj PWideChar i wide-stringów.
W każdym przypadku importy warto kapsułować w oddzielnej unit, tak aby polityka stringów nie rozlewała się po całym projekcie.
Formatowanie, zmiana wielkości znaków, porównania: locale i normalizacja
Unicode wnosi też kwestie semantyczne: zmiana wielkości liter nie jest trywialna we wszystkich językach, a znaki mogą występować w różnych formach normalizacji. W typowych zastosowaniach korporacyjnych ma to mniejsze znaczenie niż w edytorach tekstu, ale dotyczy:
- sortowania i filtrowania (np. w gridach lub wyszukiwarce),
- porównań case-insensitive dla kluczy,
- generowania nazw plików lub identyfikatorów.
Ważne jest jasne rozgraniczenie: co jest „kluczem“ (np. numery artykułów, kody klienta), które powinny pozostać bliskie ASCII, a co jest „tekstem“, który musi być w pełni Unicode-compatibile. Ta separacja ogranicza błędy uboczne.
GUI/Raportowanie: fonty, druk, PDF i zachowanie komponentów
VCL jest od wersji Unicode zasadniczo zdolny do obsługi Unicode, ale praktyka zależy od komponentów i ścieżek wyjścia. Ryzyka pojawiają się przy:
- starszych silnikach raportujących lub generatorach PDF przyjmujących ANSI,
- druku etykiet/Barcode, który wymaga określonych codepage,
- hardcodowanych fontach lub zestawach znaków.
Zaplanuj wcześnie testy z realnymi danymi przykładowymi (nazwiska, miejscowości, znaki specjalne, pisma nielatynskie jeśli istotne). Wartość leży nie tyle w tym, że „może Unicode“, ile w dowodzie: „Ten output w naszym kontekście jest poprawny.“
Dane i trwałość: Unicode nie kończy się na kodzie
Ustawić charsety i collation w bazach danych
Migracja do Unicode będzie stabilna tylko wtedy, gdy bazy danych i sterowniki są poprawnie skonfigurowane. Przykłady:
- W przypadku PostgreSQL UTF-8 zwykle jest domyślny; mimo to trzeba sprawdzić client-encoding i zachowanie sterownika.
- W SQL Server istotna jest różnica między VARCHAR a NVARCHAR; niewłaściwy wybór kolumn może powodować utratę znaków.
- W MariaDB/MySQL charset/collation (np. utf8mb4) są kluczowe, by znaki 4-bajtowe nie były ucinane.
W kodzie Delphi parametry i typy pól powinny być użyte tak, aby Unicode nie był „odwracany“ po drodze. FireDAC zwykle daje lepszą kontrolę niż bardzo stare warstwy dostępu.
Formaty plików legacy: reguły migracji zamiast cichej konwersji
Jeżeli aplikacja przez lata generowała pliki (formaty eksportu, archiwa, struktury proprietarne), trzeba określić:
- które istniejące pliki pozostają „takie jak są“ i będą poprawnie interpretowane przy odczycie,
- które formaty zostaną podniesione do UTF-8,
- czy istnieją pola wersji/headery, aby rozróżnić pliki stare i nowe jednoznacznie.
Cicha konwersja bez oznaczeń jest ryzykowna, bo błędy często wychodzą dopiero późno. Lepiej: wersjonować, jednoznacznie wykrywać i migrować celowo.
Zapewnienie jakości: testy, które naprawdę wykryją problemy Unicode
Błędy Unicode zależą często od danych. Dlatego testy „happy path“ nie wystarczą. Przydatne jest zestawienie testów obejmujące problematyczne miejsca:
- Testy roundtrip: import → przetworzenie → eksport oraz porównanie bajt-po-bajcie (dla zdefiniowanych formatów).
- DB-roundtrip: zapisy/odczyty tekstów z umlautami, akcentami i ewentualnie znakami nielatynskimi; sprawdzenie równości.
- Testy interfejsów: żądania REST jako UTF-8, poprawne headery, JSON-escaping, logowanie.
- Regresja: odtwarzanie danych legacy i typowych scenariuszy użytkowników, zwłaszcza wyszukiwania, filtrowania, sortowania.
Dla systemów B2B istotne jest też, aby błędy były obserwowalne: logowanie nie powinno niszczyć enkodowania. Kto zapisuje logi jako ANSI, traci w sytuacji błędu dokładnie te informacje, które są potrzebne.
Planowanie i nakład pracy: co naprawdę determinuje złożoność
Nakład pracy przy migracji do Unicode starych projektów Delphi zależy mniej od „liczby linii kodu“, a bardziej od sprzężeń i zależności zewnętrznych:
- Wiele integracji (DLL, COM, urządzenia, ERP/DMS/CRM) zwiększa zakres weryfikacji, bo enkodowania są istotne na każdej granicy.
- Historyczne formaty (stare eksporty, formaty klienta) wymagają reguł migracji i strategii kompatybilności.
- Mieszane wersje Delphi lub kilka produktów z jednego pnia kodu zwiększają potrzeby koordynacyjne.
- Stare warstwy dostępu do danych (np. BDE) mogą pośrednio blokować Unicode i zachęcać do modernizacji.
W praktyce sprawdza się podejście, które najpierw ustabilizuje Unicode w jądrze i krytycznych przepływach danych. Potem moduły można sukcesywnie doprowadzać do porządku. To ogranicza ryzyko i zapobiega długim „big bang“ fazom bez kolejnych wydań.
Umieszczenie w ścieżkach modernizacyjnych: REST, usługi, multiplatformowość
Unicode często jest fundamentem, jeśli oprogramowanie utrzymaniowe ma być modernizowane. Typowe pytania po drodze:
- REST-serwer lub dodanie API REST (JSON/UTF-8 — właściwe traktowanie).
- Stabilne działanie Windows-usług lub Linux-usług (logowanie, pliki konfiguracyjne, protokoły).
- Stopniowa modernizacja UI w VCL, a później ewentualnie klient multiplatformowy.
Ważna jest kolejność: budując nowe interfejsy, najpierw ustal reguły enkodowania. Migracja Unicode „przy okazji“ podczas tworzenia nowych interfejsów prowadzi do trudno testowalnych symptomów, bo przyczyna i skutek się mieszają.
Do wewnętrznego linkowania w magazynie warto powiązać tematy pokrewne, takie jak modernizacja Delphi, dostęp do danych FireDAC czy architektura serwerów REST, aby czytelnik mógł płynnie przejść do kolejnego kroku technicznego.
Wniosek: migracja do Unicode to zagadnienie ryzyka — z właściwą metodą staje się planowalne
Migracja do Unicode starych projektów Delphi nie jest kosmetyczną aktualizacją, lecz korektą podstawowych założeń dotyczących tekstu, bajtów i interfejsów. Przy uporządkowanym podejściu zyskuje się jednak więcej niż „umlauty znowu działają“: przepływy danych stają się jednoznaczne, integracje bardziej odporne, a późniejsza modernizacja (np. REST-serwery, usługi, porządkowanie baz danych) prostsza, ponieważ enkodowania nie zachodzą już implicite „gdzieś“.
Jeżeli dla swojej aplikacji Delphi potrzebują Państwo konkretnego planu migracji, analizy ryzyka hotspotów lub wsparcia we wdrożeniu, najszybszym następnym krokiem jest techniczna rozmowa wstępna o Państwa warunkach i zależnościach: prosimy o kontakt.
W środowisku merytorycznym istotną rolę odgrywają także Delphi Unicode Migration oraz Delphi Ansi do Unicode, gdy integracje, przepływy danych i rozwój muszą współgrać.
Omówienie projektu lub przedsięwzięcia modernizacyjnego z Net-Base.