Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autorzy: 'Złośliwiec'; 'NiceDog'
Biblioteki C++

Unicode w WinAPI

[lekcja]

Typy danych

W podobny sposób (tj. za pomocą #define) WinAPI wprowadza typy danych do obsługi Unikodu. Mamy więc typ WCHAR, który reprezentuje pojedynczy znak i jest odpowiednikiem typu wchar_t (lub ewentualnie unsigned short, jeśli platforma nie obsługuje wchar_t). Mamy również LPWSTR i LPCWSTR, czyli odpowiedniki char* i const char*. "Typów" takich jest więcej, ale myślę, że nie ma co zaprzątać sobie nimi głowy – to wszystko i tak tylko makra.

Neutralne stringi

Jak zapewne wiesz, podając tekst w cudzysłowie musisz go poprzedzić prefiksem L, aby został potraktowany jako Unicode. Wtedy jednak z kolei kod zawierający takie stringi nie skompiluje się w projekcie skonfigurowanym na użycie ANSI. Tymczasem dobrze by było (zwłaszcza, gdy piszemy bibliotekę albo innego rodzaju kawałek kodu, który ma być używany przez innych), gdyby kompilował się zarówno dla ANSI, jak i Unicode. Można to łatwo osiągnąć przy pomocy makra TEXT (lub __TEXT). Przykład dla funkcji MessageBox:

C/C++
MessageBox( hWnd, TEXT( "Wyświetlam się i w Unicode, i bez niego." ), NULL, MB_ICONINFORMATION );

Tak więc makro TEXT "neutralizuje" nam stringi, makro MessageBox sprawia, że to samo dzieje się z funkcjami, a co z tekstem umieszczonym w zmiennych? Zadbano i o to – istnieje typ TCHAR (oraz pochodne: LPTSTR, LPTCSTR itd.), które to typy w zależności od symbolu UNICODE "zamieniają się" w CHAR albo WCHAR (lub pochodne). Na przykład:

C/C++
TCHAR c = TEXT( 'A' );

Konwersja

Ponieważ programistów Microsoftu (i nie tylko ;-)) cechuje ogromne lenistwo, nie wszystkie funkcje w WinAPI są zaimplementowane zarówno w wersji ANSI jak i Unicode. W WinAPI znajdziemy funkcje mające tylko swój odpowiednik ANSI lub funkcje mające tylko odpowiednik Unicode, np. ReadDirectoryChangesW nie ma swojego odpowiednika w ANSI. Poza tym dołączając do naszego programu różne biblioteki też zapewne natkniemy się na sytuację, gdy jedna lub kilka z nich nie jest napisana w sposób "neutralny znakowo".

Mieszanie tych dwóch standardów jest więc trudne do uniknięcia, a wiąże się z koniecznością konwersji znaków. Na szczęście w WinAPI znajdziemy funkcje, które taką konwersję nam umożliwią. Nie są one może zbyt wygodne (jak całe WinAPI zresztą), ale to zawsze coś :-). Funkcje te to MultiByteToWideChar i WideCharToMultiByte. Na początek omówimy tę pierwszą:

Składnia:
C/C++
MultiByteToWideChar( UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr,
int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar )

ArgumentZnaczenie
CodePageStała oznaczająca stronę kodową
dwFlagsRóżne flagi
lpMultiByteStrWskaźnik na string ANSI
cbMultiByteRozmiar w bajtach stringa ANSI
lpWideCharStrBufor Unicode, do którego zostanie zapisany przekonwertowany string.
cchWideCharDługość bufora Unicode.

Przyjrzyjmy się teraz bliżej tej funkcji. Zwraca ona liczbę znaków, które zapisała do bufora. Pierwszy argument powinien zawierać stałą oznaczającą stronę kodową. Dostępne stałe zostały zebrane w tabeli:

StałaZnaczenie
CP_ACPUżyje domyślnej strony kodowej Windows'a (zależy od wersji językowej systemu).
CP_MACCPUżyje domyślnej strony kodowej Mac'a.
CP_OEMCPUżyje systemowej strony kodowej (system OEM code page)
CP_SYMBOLUżyje symbolowej strony kodowej (42)
CP_THREAD_ACPUżyje domyślnej strony kodowej dla bieżącego wątku.
CP_UTF7Użyje strony kodowej UTF-7.
CP_UTF8Użyje strony kodowej UTF-8.

Najczęściej konwertujemy tekst w aktualnej stronie kodowej Windowsa, więc jeśli nie za bardzo orientujesz się w stronach kodowych, po prostu przyjmij, że ma tam być CP_ACP. Kolejny argument to flagi (dwFlags). Flagi te nie są zbyt istotne, więc możemy w tym parametrze wstawić 0. Następny argument to string ANSI, który ma być przekonwertowany. Czwarty argument to rozmiar tego stringa.
Jeśli w funkcji MultiByteToWideChar podamy rozmiar stringa w parametrze cbMultiByte, to funkcja nie dopisze znaku zerowego (NULL) w wynikowym stringu Unicode. Rozwiązaniem tego problemu jest podanie wartości -1.
Kolejny parametr to bufor Unicode, oraz jego długość (nie rozmiar!). Tym sposobem przebrnęliśmy przez funkcję MultiByteToWideChar konwertującą znaki ANSI na Unicode. Oto przykład jej użycia:

C/C++
wchar_t WideChar[ 100 ];

if( !MultiByteToWideChar( CP_ACP, 0, "Zażółć gęślą jaźń", - 1, WideChar, 100 ) )
{
    MessageBoxW( hwnd, L"Nie można przekonwertować napisu!", L"Straszny błąd", 0 );
}
else
{
    MessageBoxW( hwnd, WideChar, L"Konwersja OK", 0 );
}

Funkcja MultiByteToWideChar zwraca 0, jeżeli wystąpił jakiś błąd. Jeśli akurat nie wystąpił, to w tablicy WideChar znajdzie się nasz napis przekowertowany na Unicode.

Czasem zachodzi też potrzeba konwersji w drugą stronę – string Unicode na ANSI. Do tego służy funkcja WideCharToMultiByte. Jest ona nieco bardziej złożona, ponieważ siłą rzeczy taka konwersja wiąże się z potencjalną utratą danych (w standardzie Unicode jeden string może zawierać nawet kilkadziesiąt tysięcy różnych znaków, string ANSI – tylko 256). Z tego powodu funkcja potrzebuje "wskazówek", co zrobić ze znakami, których przekonwertować się nie da:

Składnia:
C/C++
WideCharToMultiByte( UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, cchWideChar, lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar,
LPBOOL lpUsedDefaultChar )

ArgumentZnaczenie
CodePageStała oznaczająca stronę kodową
dwFlagsRóżne flagi
lpWideCharStrWskaźnik na string Unicode
cchWideCharDługość stringa Unicode (znaki nie bajty!)
lpMultiByteStrBufor ANSI, do którego zostanie zapisany przekonwertowany string.
cbMultiByteRozmiar bufora ANSI.
lpDefaultCharWskaźnik na znak, który ma zostać wstawiony, gdy nie można znaleźć odpowiednika ANSI dla danego znaku Unicode.
lpUsedDefaultCharWskaźnik na flagę, która określa, czy funkcja musiała zastosować domyślny znak podczas konwersji.

Pierwszy parametr to strona kodowa, czyli CP_ACP. Drugi parametr to flagi - nie są one zbyt istotne, więc 0. Potem analogicznie do funkcji MultiByteToWideChar string Unicode i jego długość, a potem string ANSI i jego rozmiar. Tutaj także występuje haczyk ze znakiem zerowym, tak jak przy MultiByteToWideChar. Ostatnie dwa parametry są opcjonalne, czyli wielkiej katastrofy nie będzie, jeśli damy tam NULL.

Dwa ostatnie argumenty funkcji WideCharToMultiByte pełnią rolę wskazówki, o której przed chwilą wspomnieliśmy. W ciągu znaków Unicode mogą się znajdować znaki nie występujące w danej stronie kodowej ANSI np. jakieś chińskie krzaczki. Ponieważ znaku nie ma w ANSI, musi on zostać zastąpiony przez jakiś inny. Te dwa ostatnie argumenty pozwalają nam właśnie określić jaki znak tam wstawić. Jeśli wystarczy nam standardowy znak systemowy używany w takim wypadku (zwykle jest on wyświetlany jako kwadracik lub kropka), to możemy spokojnie ustawić te parametry na NULL.

Teraz zobaczmy przykład takiej konwersji:

C/C++
char MultiByte[ 100 ];

if( !WideCharToMultiByte( CP_ACP, 0, L"Zażółć gęślą jaźń", - 1, MultiByte, 100, NULL, NULL ) )
{
    MessageBoxA( hwnd, "Nie można przekonwertować napisu!", "Straszny błąd", 0 );
}
else
{
    MessageBoxA( hwnd, MultiByte, "Konwersja OK", 0 );
}
Poprzedni dokument Następny dokument
MDI Wątki