Rozdział 2
Wr '
owa z
eme
o unik
0
u
W pierwszym rozdziale obiecałem opowiedzieć o wszystkich aspektach języka
C, z którymi mogłeś nie spotkać się podczas tradycyjnego programowania w try-
bie znakowym, a które są ważne w Windows. Zestawy znaków szerokich i uni-
kod (ang. Unicode) niemal na pewno kwalifikują się do tego.
Mówiąc prosto, unikod jest rozszerzeniem kodowania znaków ASCII. Zamiast 7
bitów, używanych do reprezentowania wszystkich znaków w "czystym" ASCII,
albo 8 bitów na znak, co jest dzisiejszym standardem w komputerach, unikod
używa do oznaczenia znaku 16 bitów. Pozwala to zakodować wszystkie litery,
ideogramy i inne symbole, które występują we wszystkich rodzajach pism na
świecie i mogą być przedmiotem komunikacji komputerowej. W zamierzeniach,
unikod ma za zadanie uzupełniać ASCII i przy odrobinie szczęścia ostatecznie
go zastąpić. Ponieważ ASCII jest jednym z najpopularniejszych standardów kom-
puterowych, będzie to niełatwe zadanie.
Wpływ unikodu widać we wszystkich aspektach szeroko rozumianego przemy-
shz komputerowego, ale chyba najbardziej w systemach operacyjnych i językach
programowania. Pod tym względem jesteśmy prawie w połowie drogi. Windows
NT umożliwia korzystanie wyłącznie z unikodu. (Niestety, w Windows 98 uni-
kod ma rolę jedynie wspomagającą). Język programowania C, sformalizowany
przez ANSI, umożliwia korzystanie ze znaków szerokich i w ten sposób wspo-
maga unikod, co omówię bardziej szczegółowo dalej.
Oczywiście, jak zwykle, my, jako programiści, musimy poradzić sobie z tą brud-
ną robotą. Próbowałem to nieco ułatwić pisząc przykładowe programy w tej książ-
ce tak, aby były gotowe do użycia unikodu. Co to dokładnie znaczy, okaże się w
dalszej części rozdziału.
Krótka historia zestawów znaków
Nie wiadomo, kiedy ludzie zaczęli mówić, ale pismo pojawiło się około sześciu
tysięcy lat temu. Pierwsze pisma były z natury obrazkowe. Alfabety - gdzie poje-
dyncze litery odpowiadają dźwiękom mówionym - pojawiły się dopiero około
trzech tysięcy lat temu. Przez długie wieki tradycyjne języki zupełnie wystarczały
naszym przodkom do komunikowania się; dopiero kilku dziewiętnastowiecznych
20 Część I: Podstawy
wynalazców zapragnęło ezegoś więcej. Samuel F. B. Morse, pracując nad telegra-
fem w Iatach 1838-1854, stworzył kod do jego obsługi. Każdej literze alfabetu od-
powiadała seria krótkich i długich impulsów (kropek i kresek). Nie było rozróżnie-
nia między wielkimi i małymi literami, aIe liczby i znaki dodatkowe miały swoje
własne kody.
Kod Morse'a nie był pierwszym pismem, reprezentowanym przez coś innego niż
rysowane lub drukowane figury. W latach 1821-1824 młody Louis Braille, zainspi-
rowany przez wojskowy system nocnego czytania i pisania wiadomości, stworzył
z myślą o niewidomych kod składający się z wypukłych kropek na papierze. Język
Braille'a jest właściwie 6-bitowym kodem, który zawiera litery, typowe zestawie-
nia liter, często powtarzające się słowa i znaki przestankowe. Specjalny kod uniko-
wy (ang. escape code) oznacza, że następny kod litery powinien być interpretowa-
ny jako wielka litera. Specjalny kod przełączający powoduje, że następne kody zna-
ków powinny być interpretowane jako cyfry.
Kody teleksowe, z kodem Baudota (od nazwiska francuskiego inżyniera, który
zmarł w 1903 roku) i kodem znanym jako CCITT #2 (ustandaryzowanym w 1931
roku) włącznie, były kodami 5-bitowymi i zawierały przełączniki literowe i cy-
frowe.
Standardy amerykańskie
Pierwsze komputerowe kody znakowe rozwinęły się z kodowania stosowanego
na kartach Holleritha ("nie zginać, nie gnieść, nie uszkadzać"), wymyślonych przez
Hermana Holleritha i pierwszy raz użytych przy spisie Iudności w Stanach Zjed-
noczonych w 1890 roku. Ten 6-bitowy kod znakowy, znany jako BCDIC (ang. Bi-
nary-Coded Decima2 Interehange Code - kodowany dwójkowo kod dziesiętny do
wymiany informacji ) został rozszerzony do 8-bitowego EBCDIC w latach sześć-
dziesiątych i zadomowił się tylko na dużych komputerach IBM i nigdzie więcej.
Prace nad ASCII (ang. American Standard Code for Information Interchange) rozpo-
częły się pod koniec Iat pięćdziesiątych i trwały do 1967 roku. W międzyczasie
wywiązała się dyskusja o tym, czy kod powinien mieć szerokość 6, 7 czy 8 bitów.
Niezawodność wymagała rezygnacji ze znaków przełączających, więc ASCII nie
mogło być kodem 6-bitowym. Wersję 8-bitową odrzucono z powodu wysokich
kosztów (wtedy bity były bardzo drogie). Wersja ostateczna kodu miała 26 ma-
łych liter, 26 wielkich liter, 10 cyfr, 32 symbole, 33 kody kontrolne i puste miejsce,
aż do 128 znaków. Oficjalnym dokumentem opisującym ASCII jest obecnie Coded
Character Sets - 7-Bit American National Standard Code for Information Interchange
(7-Bit ASCII), opublikowany w ANSI X3.4-1986 przez American National Stan-
dards Institute. Rysunek 2-1 pokazuję (po raz tysięczny) ASCII, w formie bardzo
podobnej do zdefiniowanej w dokumencie ANSI.
Rozdział 2: Wprowadzenie do unikodu 21
0- l- 2- 3- 4- 5- 6- 7-
-0 NUL DLE SP 0 @ P ' p
-1 SOH DCl ! 1 A Q a q
-2 STX DC2 " 2 B R b r
-3 ETX DC3 # 3 C S c s
-4 EOT DC4 $ 4 D T d t
-5 ENQ NAK % 5 E U e u
-6 ACK SYN & 6 F V f v
-7 BEL ETB ' 7 G W g w
-8 BS CAN ( 8 H X h x
-9 HT EM ) 9 I Y I y
-A LF SUB * . J Z j z
-B VT ESC + , K [ k
-C FF FS , < L \ 1
-D CR GS - - M ] m }
-E SO RS > N ^ n ~
-F SI US / ? O o DEL
Rysunek 2-1. Zestaw znaków ASCII
Wiele dobrych rzeczy można powiedzieć o ASCII. Na przykład to, że 26 kodów
liter występuje bez przerw (co nie zawsze jest prawdą w przypadku EBCDIC).
Wielkie litery mogą być zamienione na małe (i na odwrót) poprzez odwrócenie
jednego bitu. Kody dla 10 cyfr są łatwe do określenia z wartości cyfr (w BCDIC
kod znaku "0" następował po kodzie znaku "9"!).
Ale najcenniejsze jest to, że ASCII to niezawodny standard. Żaden inny standard
! nie jest tak powszechny ani tak zakorzeniony w naszych klawiaturach, kartach
graficznych, sprzęcie, drukarkach, plikach z czcionkami, systemach operacyjnych
i i Internecie.
Inne języki
Największe ograniczenie ASCII zostało odnotowane już w pierwszym wyrazie
nazwy: "american". ASCII jest standardem amerykańskim i nie może sprostać
potrzebom innych krajów, nawet anglojęzycznych. Gdzie, na przykład, jest bry-
tyjski symbol funta (^)?
Język angielski, oparty na alfabecie łacińskim (rzymskim), charakteryzuje się tym,
że niewiele słów wymaga liter ze znakami diakrytycznymi. Nawet tam, gdzie
akcenty być powinny, na przykład cooperate albo resume, pisownia bez użycia
znaków diakrytycznych jest całkowicie akceptowalna.
Jednak na południu i północy Stanów Zjednoczonych i za Atlantykiem jest wiele
krajów i języków, gdzie znaki diakrytyczne występują znacznie częściej. Znaki
akcentu pomogły zaadaptować alfabet łaciński do brzmienia tych języków. Pod-
różując dalej, na wschód albo południe Europy, natrafisz na takie języki, jak grecki,
hebrajski, arabski i rosyjski (który używa cyrylicy), które w ogółe nie używają
alfabetu łacińskiego. A jeśli będziesz przemieszczał się jeszcze dalej na wschód,
22 Część I: Podstawy
odkryjesz chińskie ideogramy Han, które zostały przejęte także w Japonii i Ko-
rei.
Historia ASCII od roku 1967 jest głównie historią prób przełamania jego ograni-
czeń i uczynienia go odpowiednim także dla języków innych niż amerykańska
odmiana angielskiego. Na przykład w roku 1967 ISO (International Standards Or-
ganisation) zalecała używanie odmiany ASCII z kodami 0x40, Ox5B, Ox5C, Ox5D,
Ox7B, Ox7C i Ox7D "zarezerwowanymi do użytku narodowego" i kodami Ox5E, 0x60
i Ox7e oznaczonymi jako "mogą być użyte dla innych symboli graficznych wtedy,
gdy konieczne jest 8, 9 lub 10 pozycji do użytku narodowego". Nie jest to na pew-
no najlepszy sposób intemacjonalizacji, ponieważ nie gwarantuje znaczeń tych
kodów. To posunięcie pokazało jednak, jak zdesperowani ludzie mogą kodować
symbole, niezbędne w różnych językach.
Rozszerzenie ASCII
W czasie, gdy tworzono pierwsze małe komputery, 8-bitowy bajt był już stan-
dardem. Jeśli więc do przechowywania znaków był używany bajt, można było
wykorzystać 128 dodatkowych znaków do wsparcia ASCII. Gdy w 1981 roku
został zaprezentowany oryginalny IBM PC, jego karta graficzna miała zakodo-
wany w ROM-ie 256-znakowy zestaw znaków, który sam szybko został ważną
częścią standardu IBM.
Oryginalny rozszerzony zestaw znaków IBM zawierał kilka znaków akcentowa-
nych i małe litery alfabetu greckiego (użyteczne w notacji matematycznej), jak
również trochę znaków pseudograficznych (kształtów i linii). Dodatkowe znaki
zostały także przypisane kodom znaków kontrolnych ASCII, ponieważ większość
z tych kodów była niepotrzebna.
Ten rozszerzony zestaw znaków IBM był wypalany w niezliczonych ROM-ach
na kartach graficznych i w drukarkach, a liczne aplikacje stosowały go do uatrak-
cyjnienia swojego wyglądu w trybie znakowym. Nadal jednak nie zawierał zna-
ków akcentowanych języków Europy Zachodniej, które używały alfabetu łaciń-
skiego, a co więcej -był nieodpowiedni dla Windows. Windows nie potrzebował
już znaków z rysunkami, ponieważ był systemem całkowicie graficznym.
W Windows 1.0 (przedstawionym w listopadzie 1985), Microsoft nie zrezygnował
całkiem z rozszerzonego zestawu znaków IBM, ale też go nie eksponował. Podsta-
wowy zestaw znaków Windows nazwano zestawem znaków ANSI, ponieważ
opierał się na szkicu standardu ANSI i ISO, który później został oficjalnie ogłoszo-
ny w ANSI/ISO 8859-1-1987, American National Standard for Information Processing-
8-Bit Single-Byte Coded Graphic Character Sets-Part 1: Latin Alphabet No 1. Jest on
znany także pod prostszą nazwą "Latin 1".
Oryginalną wersję zestawu znaków ANSI, wydrukowaną w Programmer's Refe-
rence dla Windows 1.0, pokazuje rysunek 2-2.
Rozdział 2: Wprowadzenie do unikodu 23
0- 1- 2- 3- 4- 5- 6- 7-' 8- 9- A- B- C- D- E- F-
-0 0 @ P ' p ł a D "
-1 ! 1 A Qa q I ń ElIsl" ń
-2 0 " 2 B R b r 2 " "
-3 0 0 # 3 C S c s ^ 3 Ó " ó
-4 $ 4 D T d t x ' " "
-5 % 5 E U e u p " "
-6 & 6 F V f v 9[ E "
-7 0 ' 7 G W g w
-8 ( 8 H X h x " , E e
-9 ) 9 I Y I y ^ i E e
-A * . J Z j z a ł E LJ e
-B + , K [ k { 0 0 Ż E e
-C , < L \ 1 I 0 1/a I i
-D0 - -M ] m } - '/zI Y i y
-E 0 . > N ^ n ~ 0 ^ 3/a I h i
-F / ? O - o DEL 0 I i y
Rysunek 2-2. Zestaw znaków ANSI w Windows (oparty na ANSI/ISO 8859-1)
Puste prostokąty oznaczają kody, dla których nie zdefiniowano znaków. Podobnie
był zdefiniowany ANSI/ISO 8859-1. ANSI/ISO 8859-1 pokazuje tylko znaki gra-
ficzne, a nie znaki kontrolne (więc nie definiuje DEL). Ponadto kod OxAO zdefinio-
wano jako spację nierozdzielającą (co oznacza, że nie może być używana do łama-
nia linii podczas formatowania) i kod OxAD jako łącznik opcjonalny (co oznacza,
że jest używany do oznaczenia miejsca dzielenia wyrazu podczas łamania linii i tylko
wtedy powinien być wyświetlany). ANSI/ISO 8859-1 definiuje kody: OxD7 jako
! symbol mnożenia (*) i OxF7 jako symbol dzielenia (=). Niektóre czcionki w Win-
dows definiują także pewne znaki z zakresu od 0x80 do Ox9F, ale nie są one częścią
standardu ANSI/ISO 8859-1.
W wersji 3.3 MS-DOS-a (udostępnionej w kwietniu 1987 roku) przyjęto takie roz-
; wiązanie stron kodowych dla komputerów IBM PC, które zostało przeniesione do
Windows. Strona kodowa określa przyporządkowanie kodów znaków do znaków.
Oryginalny zestaw znaków IBM występuje pod dwoma nazwami: strona kodowa
437 albo MS-DOS Latin US. Strona kodowa 850 to MS-DOS Latin l, gdzie część
znaków pseudografiki zastąpiono dodatkowymi literami akcentowanymi (ale nie
jest ona zgodna ze standardem ISO/ANSI Latin 1, pokazanym na rysunku 2-2).
Dla innych języków zostały zdefiniowane dodatkowe strony kodowe. Pierwszych
,, 128 kodów jest zawsze takie same; kolejne 128 kodów zależy od języka, dla które-
go strona została zdefiniowana.
Jeśli użytkownik pracujący na PC wyposażonym w MD-DOS ustawi na klawiatu-
rze, karcie graficznej i drukarce pewną stronę kodową, a potem będzie pisał i dru-
kował dokumenty na PC, wszystko będzie dobrze działać, gdyż całość jest spójna.
Kłopotów należy się spodziewać wtedy, gdy użytkownik spróbuje wymienić do-
; kumenty z innym użytkownikiem, który korzysta innej strony kodowej. Kodom
I
24 Część I: Podstawy
znaków zostaną przyporządkowane złe znaki. Próbowano rozwiązać ten problem,
zapamiętując informację o stronie kodowej razem z dokumentem, ale wymagało
to sporej pracy przy konwertowaniu stron kodowych.
Początkowo strony kodowe uzupełniały tylko alfabet łaciński o dodatkowe znaki
poza znakami nie akcentowanymi). Ostatecznie stworzono strony kodowe, gdzie
wyższe 128 znaków zawierało pełne alfabety niełacińskie, takie jak hebrajski, grec-
ki i cyrylica. Taka różnorodność stron kodowych powoduje, oczywiście, zamiesza-
nie; jeśli kilka akcentowanych liter będzie źle wyświetlanych, to nic się nie stanie -
gorzej, jeśli cały tekst zmieni się w bezładną, niezrozumiałą mieszaninę.
Strony kodowe mnożyły się mimo wszystkich swoich wad. Strona kodowa MS-
DOS 855 z cyrylicą nie jest ekwiwalentem strony 1251 Windows dla cyrylicy ani
też strony 10007 dla cyrylicy w komputerach Macintosh. Strony kodowe w każ-
dym środowisku są modyfikacjami standardowych zestawów znaków środowi-
ska. IBM OS/2 dodatkowo umożliwia korzystanie ze stron kodowych EBCDIC.
Ale poczekaj. Będzie jeszcze gorzej.
Zestaw znaków dwubajtowych
Dotychczas przyglądaliśmy się zestawom zawierającym 256 znaków. Ale sym-
boli ideograficznych chińskich, japońskich i koreańskich jest ponad 21000. Jak
można pomieścić te języki, zachowując ciągle jakąś zgodność z ASCII?
Rozwiązaniem (jeśli to właściwe słowo) jest zestaw znaków dwubajtowych (ang.
double-byte character set, DBCS). DBCS zaczyna się 256 kodami, zupełnie jak ASCII.
Podobnie jak we wszystkich stronach kodowych, pierwsze 128 kodów to ASCII.
Jednak niektóre z kodów powyżej 128 mają zawsze jeszcze dodatkowy bajt. Dwa
bajty razem (nazywane odpowiednio bajtem początkowym i bajtem końcowym)
definiują pojedynczy znak, będący zwykle złożonym ideogramem.
Chociaż Chiny, Japonia i Korea wspólnie korzystają z wielu takich samych ide-
ogramów, to przecież rozwinęły różne języki i często te same ideogramy ozna-
czają dla nich trzy różne rzeczy. Windows umożliwia korzystanie z czterech róż-
nych zestawów znaków dwubajtowych: strony kodowej 932 (japońska), 936 (chiń-
ska uproszczona), 949 (koreańska) i 950 (chińska tradycyjna). Korzystanie z DBCS
jest moźliwe tylko w wersjach Windows przeznaczonych dla tych krajów.
W dwubajtowym zestawie znaków nie przysparzają kłopotów znaki reprezento-
wane przez 2 bajty. Natomiast niedogodne jest to, że część znaków (a zwłaszcza
znaki ASCII) jest reprezentowana przez 1 bajt. Powoduje to różne problemy pro-
gramistyczne. Na przykład liczba znaków w łańcuchu znaków nie może być okre-
ślona na podstawie wielkości łańcucha znaków w bajtach. Trzeba wykonać ana-
lizę łańcucha znaków, aby określić jego długość, a każdy bajt musi być zbadany,
czy nie jest bajtem początkowym znaku dwubajtowego. Jeśli masz wskaźnik do
znaku gdzieś w środku łańcucha znaków DBCS, jaki jest adres poprzedniego
znaku w łańcuchu znaków? Jedyne rozwiązanie to analiza łańcucha znaków od
początku, aż do wskaźnika!
Rozdział 2: Wprowadzenie do unikodu 25
Ratunek w unikodzie
Zasadniczym problemem jest to, że 256 kodów 8-bitowych to za mało, aby od-
wzorować znaki wszystkich potrzebnych języków. Poprzednie rozwiązania ze
stronami kodowymi i DBCS-em są i niewystarczające, i niezgrabne. Jak uporać
się z tym problemem?
Jako programiści zdobyliśmy pewne doświadczenie na tym polu. Jeśli mamy zbyt
dużo elementów, by przedstawić je za pomocą wartości 8-bitowych, próbujemy
zastosować szersze wartości - być może 16-bitowe. Właśnie na tym polega bar-
dzo prosta koncepcja unikodu. Zamiast mieszania różnych kodów 256-znakowych
lub znaków dwubajtowych i jednobajtowych w zestawie znaków dwubajtowych,
unikod jest jednolitym systemem 16-bitowym, który pozwala na przedstawienie
65536 znaków. Jest to zasób wystarczający do zapisania wszystkich znaków i ide-
ogramów ze wszystkich pisanych języków świata, a także znaków matematycz-
nych, symboli i zbiorów ornamentów.
Istotne jest zrozumienie różnicy między unikodem i DBCS. Mówi się (szczegól-
nie w kontekście języka programowania C), że unikod używa "znaków szero-
kich". Każdy znak w unikodzie ma szerokość 16 bitów, zamiast 8 bitów. Warto-
ści 8-bitowe nie mają żadnego znaczenia w urukodzie. W odróżnieniu od tego, w
dwubajtowym zestawie znaków ciągle mamy do czynienia z wartościami 8-bito-
wymi. Niektóre bajty same definiują znaki, a niektóre wskazują na inny bajt, ko-
nieczny do pełnej definicji znaku.
O ile praca z łańcuch znakówami w DBCS jest skomplikowana, o tyle praca z
tekstem w unikodzie przypomina już pracę z normalnym tekstem. Prawdopo-
dobnie będziesz musiał nauczyć się, że pierwszych 128 znaków unikodu (o ko-
dach 16-bitowych od 0x0000 do 0x007F) to znaki ASCII, a następnych 128 zna-
ków unikodu (kody od 0x0080 do 0x00FF) to rozszerzenie ASCII - ISO 8859-1.
Różne bloki znaków wewnątrz unikodu oparte są na istniejących standardach.
Upraszcza to wykonywanie konwersji. Alfabet grecki używa kodów od 0x0370
do 0x03FF, cyrylica - od 0x0400 do 0x04FF, armeński - od 0x0530 do 0x058F, a
hebrajski - od 0x0590 do 0x05FE Ideogramy chińskie, japońskie i koreańskie (okre-
ślane zbiorczo jako CJK) zajmują kody od 0x3000 do Ox9FFF.
Najlepsza cecha unikodu to jeden zestaw znaków. Nie ma tam żadnej dwuznacz-
ności. unikod powstał dzięki współpracy praktycznie wszystkich ważniejszych
firm przemysłu komputerów osobistych i jest identyczny ze standardem ISO
10646-1. Najważniejszą pozycją o unikodzie jest The Unicode Standard, Uersion 2.0
(Addison Wesley, 1996), nadzwyczajna książka, która ujawnia bogactwo i roz-
maitość pisanych języków świata w taki sposób, w jaki niewiele innych publika-
cji było to w stanie zrobić. Dodatkowo książka zawiera racjonalne uzasadnienie
i szczegóły opracowania unikodu.
Czy unikod ma jakieś wady? Oczywiście. Łańcuchy znaków w unikodzie zajmu-
ją dwa razy więcej pamięci niż łańcuch znaków ASCII. (Chociaż kompresja pli-
ków pomaga trochę zmniejszyć różnicę w zajmowanej przestrzeni dyskowej).
Ale być może największą wadą jest to, że unikod nadal nie znajduje zastosowa-
nia. Jako programiści możemy to zmienić.
26 Część I: Podstawy
Szerokie znaki i C
Idea 16-bitowych znaków może przyprawić o dreszcze niejednego programistę
piszącego w C. Dla takiego programisty świętością jest szerokość char równa baj-
towi. Niewielu programistów jest świadomych, że ANSI/ISO 9899-1990, Ameri-
can National Standard for Programmming Languages - C (znany także jako "ANSI
C") dopuszcza korzystanie ze znaków zajmujących więcej niż jeden bajt; w tym
celu zaproponowano pojęcie znaków szerokich (ang. wide characters). Te znaki sze-
rokie współistnieją z normalnymi, dobrze znanymi znakami.
ANSI C umożliwia też korzystanie z zestawów znaków wielobajtowych, ofero-
wanych przez chińskie, japońskie i koreańskie wersje Windows. Jednak te zesta-
wy znaków wielobajtowych są traktowane jak łańcuchy z wartości jednobajto-
wych, w których kilka znaków zmienia znaczenie następujących po nich znaków.
Zestawy znaków wielobajtowych mają wpływ na funkcje bibliotek wykonawczych
C. W odróżnieniu od tego, wszystkie znaki szerokie są jednorodne i wymagają
nowej wersji kompilatora.
Znaki szerokie nie muszą być znakami unikodu. Urukod jest tylko jednym z moż-
liwych sposobów kodowania znaków szerokich. Jednak ponieważ w tej książce
koncentrujemy się na Windows, a nie abstrakcyjnej realizacji C, będę mówił o sze-
rokich znakach i unikodzie jako o synonimach.
Typ danych char
Przypuszczalnie wszyscy dobrze znamy definiowanie i przechowywanie znaków
i ich łańcuchów w naszych programach C przy zastosowaniu typu danych char.
Aby lepiej zrozumieć, jak C obshxguje znaki szerokie, przypomnimy sobie defini-
cję zwykłych znaków, jaka może wystąpić w programie Win32.
Następująca instrukcja definiuje i inicjuje zmienną zawierającą pojedynczy znak:
char c = 'A' ,
Zmienna c wymaga 1 bajtu pamięci i będzie zainicjowana wartością szesnastko-
wą 0x41, która jest kodem ASCII dla litery A.
Możesz zdefiniować wskaźnik do łańcucha znaków:
char
Ponieważ Windows jest 32-bitowym systemem operacyjnym, zmienna wskaźni-
kowa p wymaga 4 bajtów pamięci. Możesz też zainicjować wskaźnik do łańcu-
cha znaków:
char * p = "Hello!" ,
Zmienna p wymaga 4 bajtów pamięci, jak przedtem. Łańcuch znaków jest zapi-
sany w pamięci statycznej i zajmuje 7 bajtów - 6 bajtów łańcucha oraz kończące
go 0.
Możesz zdefiniować tablicę znaków:
char aC107 ;
W tym przypadku kompilator rezerwuje dla tablicy 10 bajtów pamięci. Wyrażenie si-
zeof (a) zwróci wartość 10. Jeśli tablica jest globalna (czyli zdefiniowana poza jakąkol-
wiek funkcją), możesz inicjować tablicę znaków, używając następującej instrukcji:
Rozdział 2: Wprowadzenie do unikodu 27
char a[] = "Hello!" ,
Jeśli definiujesz tę tablicę jako zmienną lokalną funkcji, musi być ona zdefinio-
wana jako zmienna static, jak tu:
static char a[] = "Hello!"
W obu powyższych przypadkach łańcuch znaków jest wprowadzany do pamię-
ci statycznej programu z dodanym na końcu 0, więc wymaga 7 bajtów pamięci.
Szersze znaki
Ani unikod, ani znaki szerokie nie zmieniają znaczenia typu danych char w C.
Nadal char oznacza 1 bajt pamięci, a sizeof (char) ciągle zwraca 1. Teoretycznie
bajt w C może być większy niż 8 bitów, ale dla większości z nas bajt (i chnr) ma
zawsze 8 bitów.
Znaki szerokie w C oparte są na typie danych zuchayt, który jest zdefiniowany
w kilku plikach nagłówkowych, łącznie z WCHAIZ.H, jako:
typedef unsigned short wchar t ;
Tak więc typ danych wchar t jest taki sam jak liczba całkowita bez znaku: ma 16
bitów szerokości.
Aby zdefiniować zmienną zawierającą pojedynczy szeroki znak, użyj następują-
cej instrukcji:
wchar t c = 'A' ,
Zmienna c jest dwubajtową ,artością 0x0041, która reprezentuje w unikodzie li-
terę A. (Ponieważ w mikroprocesorach Intela zapamiętywanie wartości wielobaj-
towych rozpoczyna się od najmniej znaczących bajtów, to rzeczywista kolejność
bajtów w pamięci jest następująca: 0x41 i 0x00. Pamiętaj o tym, jeśli sprawdzasz
w pamięci tekst unikodu).
Możesz też zdefiniować zainicjowany wskaźnik do łańcucha znaków szerokich:
wchar t * p = L"Hello!"
Zwróć uwagę na wielkie L (od ang. long) bezpośrednio przed pierwszym zna-
kiem cudzysłowu. Wskazuje ono kompilatorowi, że łańcuch znaków ma być za-
pisany z szerokimi znakami, z których każdy zajmuje 2 bajty. Zmienna wskaźni-
kowa p wymaga jak zwykle 4 bajtów pamięci, ale łańcuch znaków wymaga 14
bajtów - po 2 bajty dla każdego znaku z 2 bajtami zer na końcu.
Podobnie możesz zdefiniować tablicę znakó szerokich:
static wchar t a[] = L"Hello!"
Łańcuch znaków ponownie wymaga 14 bajtów pamięci, więc sizeof (a) powinno
zwrócić wartość 14. Możesz za pomocą indeksów tablicy uzyskać indywidualne
znaki. Wartość a(1] to szeroki znak 'e' albo 0x0065.
Chociaż wygląda to jak literówka, L poprzedzające otwarcie cudzysłowu jest bar-
dzo ważne i nie może być między tymi znakami żadnego odstępu. Tylko z tym L
kompilator będzie wiedział, że chcesz przechować łańcuch znaków z 2 bajtami
na znak. Później, kiedy będziemy spotykać łańcuchy składające się ze znaków
szerokich, ponownie napotkasz L przed pierwszym znakiem cudzysłowu. Na
szczęście kompilator C najczęściej zgłosi ostrzeżenie lub komunikat o błędzie, jeśli
zapomnisz dołączyć L.
2g Część I: Podstawy
Możesz także użyć przedrostka L przed stałą znakową składającą się z pojedyn-
czego znaku, jak poniżej, wskazując, że powinien być interpretowany jako szero-
ki znak.
wchart c = L'A' ,
Ale zwykle nie jest to konieczne. Kompilator C powinien rozszerzyć ten znak o
zero.
Funkcje biblioteki znaków szerokich
Wiemy, jak znaleźć długość łańcucha znaków. Na przykład, jeśli zdefiniowali-
śmy wskaźnik do niego:
char * pc = "Hello!" ,
możemy wywołać
iLength = strlen (pc) ;
Zmienna iLength uzyska wartość 6 równą liczbie znaków w łańcuchu.
Doskonale! Teraz spróbujmy zdefiniować wskaźnik do łańcucha znaków szero-
kich:
wchar t * pw = L"Hello!" ,
I wywołajmy ponownie strlen:
iLength = strlen (pw) :
Zaczynają się kłopoty. Najpierw kompilator C wyświetla komunikat ostrzegaw-
czy, prawdopodobnie coś w stylu
'function' . incompatible types - from 'unsigned short *' to 'const char *'
Oznacza to, że funkcja strlen jest zadeklarowana jako pobierająca wskaźnik do
char, a otrzymała wskaźnik do unsigned int. Możesz wprawdzie skompilować i uru-
chomić program, ale stwierdzisz, że iLength jest ustawiona na 1. Dlaczego?
Oto 16-bitowe wartości dla 6 znaków łańcuch znakówu "Hello!":
0x0048 0x0065 0x006C 0x006C 0x006F 0x0021
Mikroprocesor Intela umieszcza je w pamięci następująco:
48 00 65 00 6C 00 6C 00 6F 00 21 00
Funkcja strlen próbując znaleźć długość łańcucha znaków, liczy pierwszy bajt jako
znak, ale zakłada, że drugi to bajt zerowy oznaczający koniec łańcucha.
To małe ćwiczenie jasno pokazuje różnice między samym językiem C i funkcjami
biblioteki wykonawczej. Kompilator interpretuje łańcuch znaków L"Hello!" jako
zbiór wartości 16-bitowych typu short lnt i zapisuje w tablicy wchar t. Kompila-
tor obsługuje też indeksowanie tablicy i operator sizeof, więc działają one właści-
wie. Ale funkcje biblioteki wykonawczej, takie jak strlen, są dodawane podczas
łączenia. Te funkcje oczekują łańcuchów zawierających znaki jednobajtowe. Gdy
dostaną łańcuchy z szerokimi znakami, nie działają tak, jakbyśmy chcieli.
Powiesz, że to nic wielkiego. Teraz każda funkcja biblioteki C musi być przepisa-
na, aby przyjmowała znaki szerokie. Tak naprawdę, nie każda. Tylko te funkcje,
które mają argumenty w postaci łańcuchów znaków. I nie musisz ich przepisy-
wać. To już zostało zrobione.
Rozdział 2: Wpowadzenie do unikodu
Wersja funkcji strlen do obsługi znaków szerokich nazywa się wcslen (ang. wide-
character string length) i jest zadeklarowana zarówno w STRING.H (gdzie znaj-
duje się deklaracja strlen), jak też i w WCHAR.H. Funkcja strlen jest zadeklarowa-
na następująco:
size t cdecl strlen (const char *) :
a oto deklaracja funkcji wcslen:
sizę t cdecl wcslen (const wchar t *) :
Teraz wiemy, że jeśli chcemy poznać długość łańcucha składającego się z zna-
ków szerokich, musimy wywołać
iLength = wcslen (pw) :
Funkcja zwraca 6, liczbę znaków w łańcuchu. Zapamiętaj, że długość łańcucha
(w znakach) nie zmienia się przy przejściu na znaki szerokie - zmienia się tylko
liczba bajtów.
Wszystkie twoje ulubione funkcje biblioteki wykonawczej C, które pobierają ar-
gumenty w postaci łańcuchów znaków, mają swoje wersje dla znaków szerokich.
Na przykład wprintf to wersja funkcji printf dla znaków szerokich. Te funkcje są
zadeklarowane zarówno w WCHAR.H, jak też w pliku nagłówkowym, w któ-
rym deklarowane są zwykłe funkcje.
Utrzymywanie pojedynczego źródła
Oczywiście, stosowanie unikodu ma pewne wady. Pierwszą i najważniejszą jest
to, że każdy łańcuch znaków w programie potrzebuje dwukrotnie więcej miej-
sca. W dodatku, możesz zauważyć, że funkcje obsługi znaków szerokich z bi-
blioteki wykonawczej są większe niż zwykłe funkcje. Z tego powodu możesz
dążyć do tworzenia dwóch wersji programu -jednej z łańcuchami znaków ASCII
i drugiej - w unikodzie. Najlepszym rozwiązaniem byłby pojedynczy plik kodu
źródłowego, który mógłbyś kompilować z łańcuchami znaków w ASCII albo w
unikodzie.
Stanowi to pewien problem, ponieważ funkcje biblioteki wykonawczej mają różne
nazwy, różnie definiujesz znaki i kłopot sprawia poprzedzanie stałych łańcuchów
znaków dużym L.
Jednym z rozwiązań może być użycie pliku nagłówkowego TCHAR.H, dołączo-
nego do Visual C++ Microsoftu. Ten plik nagłówkowy nie jest częścią standardu
ANSI C, więc każda funkcja i makrodefinicja jest w nim poprzedzona podkreśle-
niem. TCHAR.H dostarcza zestawu altematywnych nazw dla standardowych
funkcji biblioteki wykonawczej, wymagających parametrów w postaci łańcuchów
(np. tprintf i tcslen). Czasami określamy je jako "ogólne" nazwy funkcji, ponie-
' waż mogą odnosić się zarówno do unikodowej, jak też i do nie unikodowej wer-
sji funkcji.
Jeżeli identyfikator o nazwie LTMCODE jest zdefiniowany, a plik nagłówkowy
TCHAR.H włączony do twojego programu, tcslen jest zdefiniowana jako wcslen:
lldefine tcslen wcslen
Jeżeli LTMCODE nie jest zdefiniowany, tcslen jest definiowana jako strlen:
tldefine tcslen strlen
30 Część I: Podstawy
I tak dalej. TCHAR.H rozwiązuje też problem dwóch typów danych znakowych
za pomocą nowego typu danych o nazwie TCHAR. Jeżeli identyfikator UNI-
CODE jest zdefiniowany, TCHAR jest definiowany jako wchar t:
typedef wchar t TCHAR ;
W przeciwnym przypadku TCHAR to po prostu char:
typedef char TCHAR ;
Teraz nadszedł czas rozwiązania problemu L ze stałą łańcuchową. Jeśli identyfi-
kator IJNICODE jest zdefiniowany, definiowane jest także następujące makro
o nazwie T:
define T(x) Lix
To jest dość zawiła składnia, ale zgodna ze standardem ANSI C dla preprocesora
C. Ta para znaków hash jest nazywana "wstawieniem leksemu" (ang. token paste)
i powoduje dodanie L do parametru makra. Jeśli więc parametrem makra jest "Hel-
lo!", L##x oznacza L"Hello!".
Jeżeli identyfikator LTNICODE nie jest zdefiniowany, makro T jest zdefinio-
wane po prostu w ten sposób:
idefine T(x) x
Niezależnie od tego, dwa inne makra są też zdefiniowane jako T:
define T(x) T(x)
łdefine TEXT(x) T(x)
Którego z nich użyjesz dla swojego programu konsoli Win32, zależy od tego, czy
chcesz być bardziej zwięzły, czy też rozgadany. Zasadniczo musisz zdefiniować
swój łańcuch znaków wewnątrz makra T albo TEXT w następujący sposób:
TEXT ("Hello!")
Dzięki temu, jeśli zdefiniowany jest identyfikator UNICODE, łańcuch interpre-
towany jest jako składający się ze znaków szerokich, a w przeciwnym razie, jako
składający się ze znaków 8-bitowych.
Szerokie znaki i Windows
Windows NT całkowicie korzysta z unikodu. Oznacza to, że wewnętrznie uży-
wa łańcuchów składających się ze znaków 16-bitowych. Ponieważ większość pro-
gramów jeszcze nie używa 16-bitowych znaków, Windows NT musi często kon-
wertować łańcuchy znaków przy przekazywaniu ich z lub do systemu. Pod Win-
dows NT można uruchamiać programy łańcuch znakówane dla ASCII, unikodu
albo dla mieszaniny ASCII i unikodu. W tym celu Windows NT zawiera oddziel-
ne funkcje API, które akceptują 8-bitowe lub 16-bitowe łańcuch znakówy. (Nie-
długo zobaczymy, jak to działa).
Windows 98 znacznie mniej korzysta z unikodu niż Windows NT. Tylko niektóre
funkcje Windows 98 umożliwiają korzystanie z łańcuchów znaków szerokich.
(Funkcje te są wymienione w artykule Q125671 Microsoft Knowledge Base; jest
tam także MessageBox). Jeśli chcesz rozprowadzać pojedynczy plik EXE, który ma
się uruchamiać w środowisku Windows NT i Windows 98, nie powinien on opie-
rać się na unikodzie, bo nie uruchomi się w środowisku Windows 98; dokładniej,
program nie powinien wywoływać unikodowych wersji funkcji Windows. Jeśli
Rozdział 2: Wprowadzenie do unikodu 31
jednak chcesz być przygotowany na stworzenie wersji unikodowej twojego pro-
gramu, prawdopodobnie powinieneś utworzyć pojedyncze źródło, które może
być skompilowane albo dla ASCII, albo dla unikodu. Programy w tej książc są
łańcuch znakówane właśnie w ten sposób.
Typy plików nagłówkowych Windows
Jak już widziałeś w pierwszym rozdziale, program Windows włącza plik nagłów-
kowy WINDOWS.H. Ten plik włącza wiele innych plików nagłówkowych, mię-
dzy innymi WINDEF.H, który zawiera wiele typów podstawowych, używanych
w Windows, i który z kolei włącza WINNT.H. Plik WINNT.H umożliwia korzy-
stanie z unikodu.
WINNT.H rozpoczyna się od włączenia pliku nagłówkowego C - CTYPE.H, któ-
ry jest jednym z wielu plików zawierających definicję wchar t. WINNT.H defi-
niuje nowe typy danych, zwane CHAR i WCHAR:
typedef char CHAR ;
typedef wchar t WCHAR ; // szerokie znaki
CHAR i WCHAR są typami danych zalecanymi do użytku w programach Win-
dows, gdy musisz zdefiniować znak 8-bitowy lub 16-bitowy. Komentarz nastę-
pujący po definicji WCHAR jest sugerowanym przedrostkiem notacji węgierskiej:
zmienna oparta na typie WCHAR może być poprzedzona literami zuc, aby za-
znaczyć, że jest to szeroki znak.
Plik nagłówkowy WINNT.H definiuje dalszych sześć typów danych, których
możesz używać jako wskaźników do 8-bitowych łańcuch znakówów i cztery typy
danych, których możesz używać jako wskaźników do 8-bitowych łańcuch zna-
kówów const. Trochę skondensowałem prawdziwe instrukcje z pliku nagłówko-
wego, aby je tutaj przedstawić:
typedef CHAR * PCHAR, * LPCH, * PCH, * NPSTR, *LPSTR, * PSTR ;
typedef CONST CHAR * LPCCH, * PCCH, * LPCSTR, * PCSTR ;
Przedrostki N i L oznaczają "near" i "long" i miały znaczenie w 16-bitowych
Windows z powodu różnych wielkości wskaźników. W Win32 nie ma różnicy
między wskaźnikami bliskimi i dalekimi.
Podobnie WINNT.H definiuje sześć typów danych, których możesz używać jako
wskaźników do 16-bitowych łańcuchów znaków, i cztery typy danych, których
możesz używać jako wskaźników do 16-bitowych łańcuchów const:
typedef WCHAR * PWCHAR, * LPWCH, * PWCH, * NPWSTR, *LPWSTR, * PWSTR ;
typedef CONST WCHAR * LPCWCH, * PCWCH, * LPCWSTR, * PCWSTR ;
Mamy więc typ danych CHAR (8-bitowy char) i WCHAR (16-bitowy wchar t) oraz
wskaźniki do CHAR i WCHAR. Podobnie jak TCHAR.H, WINNT.H definiuje
TCHAR jako ogólny typ danych znakowych. Jeśli jest zdefiniowany identyfika-
tor UNICODE (bez podkreślenia), TCHAR i wskaźniki do TCHAR są oparte na
WCHAR i wskaźnikach do WCHAR; jeśli identyfikator UNICODE nie jest zdefi-
niowany, TCHAR i wskaźniki do TCHAR są oparte na char i wskaźnikach do char:
ifdef UNICODE
typedef WCHAR TCHAR, * PTCHAR ;
typedef LPWSTR LPTCH, PTCH, PTSTR, LPTSTR ;
32 Część I: Podstawy
typedef LPCWSTR LPCTSTR ;
#else
typedef char TCHAR, * PTCHAR ;
typedef LPSTR LPTCH, PTCH, PTSTR, LPTSTR ;
typedef LPCSTR LPCTSTR ;
iendi f
Oba pliki nagłówkowe, WINNT.H i WCHAR.H, chronią przed redefiniowaniem
typu danych TCHAR, jeśli został on już zdefiniowany przez jeden z nich. Jednak
jeśli używasz różnych plików nagłówkowych w twoim programie, powinieneś
włączyć w pierwszej kolejności plik WINDOWS.H.
Plik nagłówkowy .H definiuje także makro, które dopisuje L przed pierw-
szym znakiem cudzysłowu w łańcuchu znaków. Jeśli identyfikator UNICODE zo-
stał zdefiniowany, makro zwane TEXT jest zdefiniowane następująco:
4idefine TEXT(quote) Lquote
Jeśli identyfikator UNICODE nie został zdefiniowany, makro TEXT wygląda
następująco:
idefine TEXT(quote) quote
Niezależnie od tego, makro TEXT jest zdefiniowane następująco:
define TEXT(qoute) TEXT(quote)
Definicja ta przypomina definicję makra TEXT w TCHAR.H, tyle że nie ma tu-
taj tego kłopotliwego podkreślenia. W tej książce używam makra w wersji TEXT.
Te definicje pozwalają mieszać łańcuchy znaków ASCII i unikodu w jednym pro-
gramie lub łańcuch znakówaćjeden program, który może być skompilowany dla
ASCII albo dla unikodu. Jeśli chcesz jawnie definiować 8-bitowe zmienne znako-
we lub łańcuchy znaków, użyj CHAR, PCHAR (albo innego) i łańcuchów zna-
ków w cudzysłowach. Jeśli chcesz definiować 16-bitowe zmienne znakowe lub
łańcuchy znaków, użyj WCHAR, PWCHAR i dopisz L przed cudzysłowami. Przy
zmiennych, które mają być 8-bitowe albo 16-bitowe, zależnie od definicji identy-
fikatora UNICODE, użyj TCHAR, PTCHAR i makra TEXT.
Wywołania funkcji Windows
W 16-bitowych wersjach Windows, poczynając od Windows 1.0, a kończąc na Win-
dows 3.1, funkcja MessageBox była umieszczona w dołączanej dynamicznie biblio-
tece USER.EXE. W pliku nagłówkowym WINDOWS.H, dołączonym do SDK dla
Windows 3.1, funkcja MessageBox była zdefiniowana następująco:
int WINAPI MessageBox (HWND, LPCSTR, LPCSTR, UINT) ;
Zauważ, że drugi i trzeci argument funkcji są wskaźnikami do stałych łańcuchów
znaków (ang. constant character strings). Gdy program Winl6 jest kompilowany i łą-
czony, Windows pozostawia wywołanie MessageBox nie powiązane. Tabela w pli-
ku EXE programu pozwala Windows na dynamiczne połączenie wywołania w pro-
gramie z funkcją MessageBox znajdującą się w bibliotece USER.
32-bitowe wersje Windows (to znaczy wszystkie wersje Windows NT oraz Win-
dows 95 lub Windows 98) zawierają zarówno USER.EXE, co gwarantuje kompa-
tybilność z 16-bitowymi programami, jak i bibliotekę dynamiczną, zwaną
USER32.DLL, która zawiera punkty startowe dla 32-bitowych wersji funkcji pod-
systemu USER, włączając w to MessageBox.
Rozdział 2: Wprowadzenie do unikodu 33
I tu jest klucz do obsługi unikodu w Windows. W USER32.DLL nie ma punktu
wejścia dla 32-bitowej funkcji MessageBox. Zamiast tego są dwa inne punkty wej-
ścia: jeden dla funkcji MessageBoxA (wersja ASCII) i drugi dla funkcji Message-
BoxW (wersja dla znaków szerokich). Każda funkcja Win32, która wymaga łań-
cucha znaków jako argumentu, ma dwa punkty startowe w systemie operacyj-
nym! Na szczęście nie musisz stale o tym pamiętać. Możesz zwyczajnie używać
MessageBox w swoich programach. Podobnie jak plik nagłówkowy TCHAR, róż-
ne pliki nagłówkowe Windows wykonują konieczne sztuczki.
Oto jak funkcja MessageBoxA zdefiniowana jest w WINUSER.H. Zwróć uwagę na
podobieństwo do wcześniejszej definicji MessageBox.
WINUSERAPI int WINAPI MessageBoxA (HWND hWnd, LPCSTR lpText,
LPCSTR lpCaption, UINT uType) ;
A oto MessageBoxW:
WINUSERAPI int WINAPI MessageBoxW (HWND hWnd, LPCWSTR lpText,
LPCWSTR lpCaption, UINT uType) ;
Zauważ, że drugi i trzeci parametr funkcji MessageBoxW są wskaźnikami do łań-
cucha składającego się ze znaków szerokch.
Możesz używać niezależnie tunkcji MessageBoxA i MessageBoxW w swoich pro-
gramach Windows, jeśli musisz mieć dostęp do funkcji ASCII i do funkcji ze zna-
kami szerokimi. Ale większość programistów będzie dalej używać MessageBox,
co jest równoważne MessageBoxA lub MessageBoxW, zależnie od tego, czy zdefi-
niowano UNICODE. Oto dość prosty kod w WINUSER.H, który wykonuje tę
sztuczkę:
ifdef UNICODE
define MessageBox MessageBoxW
el se
define MessageBox MessageBoxA
endif
Tak więc wszystkie wywołania funkcji MessageBox, które znajdują się w twoim
programie, będą naprawdę wywołaniami funkcji MessageBoxW, jeśli jest zdefinio-
wany identyfikator UNICODE, lub funkcji MessageBoxA, jeśli nie jest zdefinio-
wany.
Gdy uruchamiasz program, Windows łączy różne wywołania funkcji w twoim
programie z punktami startowymi w różnych bibliotekach dynamicznych Win-
dows. Jednak, z kilkoma wyjątkami, większość funkcji Windows w wersjach uni-
kodu nie jest obecna w Windows 98. Te funkcje mają punkty startowe, ale za-
zwyczaj zwracają one kod błędu. Do aplikacji należy uporanie się z tym błędem.
Funkcje obsługi łańcuchów znaków w Windows
Miałem już okazję nadmienić, że kompilator C Microsoftu zawiera zarówno wersje
ogólne, jak i wersje dla znaków szerokich tych wszystkich funkcji biblioteki wy-
konawczej, które wymagają łańcuchów znaków jako argumentów. Windows zaś
powiela niektóre z nich. Jako przykład podaję spis funkcji zdefiniowanych w Win-
dows, które obliczają długość, kopiują, łączą i porównują łańcuchy znaków:
34 Część I: Podstawy
ILength = lstrlen (pString) ;
pString = lstrcpy (pStringl, pString2) ;
pString = lstrcpyn (pStringl, pString2, iCount) ;
pString = lstrcat (pStringl, pString2) ;
iComp = lstrcmp (pStringl, pString2) ;
iComp = lstrcmpi (pStringl, pString2) ;
Działają one podobnie, jak ich odpowiedniki z biblioteki C. Jeśli zdefiniowany
jest identyfikator UNICODE, akceptują one łańcuchy znaków szerokich, a jeśli
nie - zwykłe łańcuchy znaków. Wersja funkcji IstrlenW dla znaków szerokich jest
zrealizowana w Windows 98. ,
Użycie printf w Windows
Zdarza się, że programiści, którzy wyrośli na modelu programowania znakowe-
go w C w wierszu poleceń, nadmiernie lubią funkcję printf. To nie przypadek, że
w programie "hello, world!" Kernighana i Ritchiego występuje właśnie printf,
mimo że można było posłużyć się prostszą funkcją, taką jak puts. Każdy wie, że
rozszerzanie programu "hello, world!" może ewentualnie wymagać formatowa-
nego wyjścia tekstowego funkcji printf, więc równie dobrze można wprowadzić
ją już na początku.
Zła wiadomość: nie możesz używać printf w programie Windows. Chociaż mo-
żesz stosować większość biblioteki wykonawczej C w programach Windows (wie-
lu programistów woli używać funkcji zarządzania pamięcią i plikowego I/O z C,
niż ich odpowiedników z Windows), w Windows nie istnieje pojęcie standardo-
wego wejścia i standardowego wyjścia. W programie Windows możesz używać
fprintf, ale nie printf.
Dobra wiadomość: nadal możesz wyświetlać tekst, używając sprintf i innych funk-
cji z jej rodziny. Działają one jak printf, z tą różnicą, że zapisują formatowane
wyjście do bufora znaków, który jest dostarczany jako pierwszy argument. Na-
stępnie możesz robić co chcesz z tym łańcuchem znaków (na przykład przekazać
go do MessageBox).
Jeżeli nigdy nie miałeś okazji używać sprintf (tak jak ja, kiedy zaczynałem pro-
gramowanie w Windows), podam tu krótki opis. Przypomnij sobie, że funkcja
printf jest zadeklarowana następująco:
int printf (const char * szFormat, ...) ;
Pierwszym argumentem jest formatujący łańcuch znaków, a po nim następuje
pewna liczba argumentów różnych typów, odpowiadających formatującemu łań-
cuchowi znaków.
Funkcja sprintf jest zdefiniowana następująco:
int sprintf (char * szBuffer, const char * szFormat, ...)
Pierwszym argumentem jest bufor znaków; po nim formatujący łańcuch znaków.
Zamiast wypisywać sformatowany wynik na standardowe wyjście, sprintf wpi-
suje go do bufora szBuffer. Funkcja zwraca długość łańcucha znaków. Przy pro-
gramowaniu w trybie znakowym instrukcja
printf ("Suma %i i %i wynosi %i", 5, 3, 5+3) ;
jest funkcjonalnie równoważna
T
Rozdział 2: Wprowadzenie do unikodu 35
char szBuffer C100) ;
sprintf (szBuffer, "Suma %i i %i wynosi %i", 5, 3, 5+3) ;
puts (szBuffer) ;
W Windows możesz używać MessageBox zamiast puts do wyświetlenia rezulta-
tów.
Niemal każdy programista natknął się na źle działającą funkcję printf. Może być
ona przyczyną zawieszenia się programu, gdy ciąg formatujący nie odpowiada
kolejności zmiennych, które mają być sformatowane. W przypadku sprintf będziesz
musiał troszczyć się nie tylko o to, ale jeszcze o inną rzecz: bufor znaków musi
mieć dostateczną wielkość, aby zmieścić wynik. Niestandardowa funkcja Micro-
softu, snprintf, rozwiązuje ten problem, za pomocą argumentu, który określa wiel-
kość bufora w znakach.
Odmianą sprintf jest funkcja vsprintf, która ma tylko trzy argumenty. Funkcja
vsprintf jest używana do zrealizowania własnych funkcji, które muszą wykony-
wać, podobne do printf, formatowanie zmiennej liczby argumentów. Pierwsze dwa.
argumenty vsprintf są takie same jak w sprintf bufor znaków do przechowywa-
nia wyniku i formatujący łańcuch znaków. Trzeci argument jest wskaźnikiem do
tablicy argumentów, które mają być sformatowane. W praktyce ten wskaźnik
wiąże zmienne, które są przechowywane na stosie podczas przygotowywania
wywołania funkcji. Makra va_list, v" start i v" end (zdefiniowane w STDARG.H)
pomagają obsłużyć ten wskaźnik do stosu. Program SCRNSIZE, przedstawiony
na końcu tego rozdziału, pokazuje, jak używać tych makr. Funkcja sprintf może
być łańcuch znakówana z wykorzystaniem logiki vsprintf w następujący sposób:
int sprintf (char * szBuffer, const char * szFormat, ...)
(
int iReturn ;
valist pArgs ;
va_start (pArgs, szFormat) ;
iReturn = vsprintf (szBuffer, szFormat, pArgs) ;
vaend (pArgs) ;
return iReturn ;
Makro v" start ustawia pAr, by wskazywał na następną zmienną na stosie, po-
wyżej argumentu szFormat.
Tak wiele dawniejszych programów Windows używało sprintf i vspintf, że Micro-
soft dodał do API Windows dwie podobne funkcje. Funkcje Windows wsprintf
i zuvsprintf są funkcjonalnymi odpowiednikami sprir2tf i vsprintf, z wyjątkiem tego,
że nie obsługują formatowania liczb zmiennoprzecinkowych.
Oczywiście, wraz z pojawieniem się znaków szerokich, urosła liczba funkcji sprintf,
tworząc dziwną mieszaninę nazw funkcji. Oto tabela, która pokazuje wszystkie
funkcje sprintf zawarte w bibliotece wykonawczej C Microsoftu i w Windows.
36 Część I: Podstawy
Funkcje ASCII dla znaków szerokich ogólne
Zmienna liczba
argumentów
Wersja standardowa sprintf swprintf stprintf
Wersja o maksy- snprintf sriwprintf sntprintf
malnej długości
Wersja Windows wsprintfA wsprintf4V wsprintf
Wskaźnik do
tablicy argumentów
Wersja standardowa vsprintf vswprintf vstprintf
Wersja o maksy- vsnprintf vsnwprintf vsntprintf
malnej długości
Wersja Windows wvsprintfA wvsprintfYV wvsprintf
W wersjach funkcji sprintf obshxgujących znaki szerokie, bufor znaków jest też zde-
finiowany jako łańcuch znaków szerokich. W wersjach wszystkich tych funkcji do
obsługi znaków szerokich, formatujący łańcuch znaków też musi być złożony ze
znaków szerokich. Jednak musisz być pewny, że wszystkie łańcuchy znaków, któ-
re przekazujesz do tych funkcji, są także złożone ze znaków szerokich.
Formatowanie okna komunikatu
Program SCRNSIZE z rysunku 2-3 pokazuje, jak zrealizować funkcję MessageBox-
Printf, która pobiera zmienną liczbę argumentów i formatuje je tak jak printf.
SCRPISIZE.C
/*
SCRNSIZE.C - Wyświetla w oknie komunikatów rozmiar ekranu
(c) Charles Petzold. 1998
*/
#include
ifinclude
#include
int CDECL MessageBoxPrintf (TCHAR * szCaption, TCHAR * szFormat, ...)
l
TCHAR szBuffer [1024] ;
valist pArgList ;
// Makro va start (zdefiniowane w STDARG.H) jest zwykle równoważne:
// pArgList = (char *) &szFormat + sizeof (szFormat) ;
r
Rozdział 2: Wprowadzenie do unikodu 37
vastart (pArgList, szFormat) ;
// Ostatni argument wvsprintf wskazuje na argumenty
vsntprintf (szBuffer, sizeof (szBuffer) / sizeof (TCHAR),
szFormat, pArgList) ;
/! makro vaend tylko czyci pArgList, bez wyraźnej przyczyny
va ęnd (pArgList) ;
return MessageBox (NULL, szBuffer, szCaption, 0) ;
int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow>
int cxScreen, cyScreen ;
cxScreen = GetSystemMetrics (SM CXSCREEN) ;
cyScreen = GetSystemMetrics (SM CYSCREEN) :
MessageBoxPrintf (TEXT ("ScrnSize"),
TEXT ("The screen is i pixels wide by ii pixels high."),
cxScreen, cyScreen) ;
return 0 ;
Rysunek 2-3. Program SCRNSIZE
Program wyświetla szerokość i wysokość ekranu w pikselach, posługując się in-
formaejami zwróconymi przez funkeję GetSystemMetrics. Funkeja GetSystemMe-
trics służy do pobierania informacji o rozmiarach różnych obiektów w Windows.
W rozdziale 4 pokażę, jak używać funkcji GetSystemMetrics, aby wyświetlić i prze-
wijać wiele linii tekstu w oknie Windows.
Wielojęzyczność i ta książka
Przygotowanie twojego programu Windows na rynek międzynarodowy nie ogra-
nicza się jedynie do użycia unikodu. Zagadnienie wielojęzyczności nie mieści się
w tematyce tej książki, ale jest dokładnie opisane w książce Nadine Kano Develo-
ping International Software for Windows 95 and Windozus NT (Microsoft Press, 1995).
Nasza książka ogranicza się do pokazywania programów, które mogą być skom-
pilowane ze zdefiniowanym identyfikatorem UNICODE albo bez niego. Wyma-
ga to wprowadzenia TCHAR we wszystkich definicjach znaków i łańcuchów zna-
ków, używania makra TEXT dla literałów (stałych) łańcuchów znaków i rozróż-
niania bajtów i znaków. Na przykład zobacz wywołanie vsntprintf w SCRNSI-
ZE. Drugi argument jest wielkością bufora w znakach. Zazwyczaj użyłbyś size-
of(szBvffer). Ale jeśli bufor ma znaki szerokie, nie będzie to wielkość bufora w
znakach, ale wielkość bufora w bajtach. Musisz podzielić to przez sizeof(TCHAR).
Zwykle w Visual C++ Developer Studio możesz skompilować program w dwóch
różnych konfiguracjach: Debug i Release. Aby rzecz ułatwić, dla przykładowych
programów z tej książki zmodyfikowałem konfigurację Debug tak, że definio-
38 Część I: Podstawy
wany jest identyfikator UNICODE. W tych programach, które używają funkcji
bibliotecznych C wymagających argumentów w postaci łańcucha znaków, w kon-
figuracji Debug definiowany jest także identyfikator UNICODE. (Aby zobaczyć,
jak to jest zrobione, wybierz Settings z menu Project i kliknij kartę C/C++). Tak
oto programy mogą być w prosty sposób powtórnie kompilowane i dołączane
do testów.
Wszystkie programy w tej książce -niezależnie, czy skompilowane dla unikodu,
czy nie - uruchamiają się w Windows NT. Z kilkoma wyjątkami programy skom-
pilowane dla unikodu z tej książki nie uruchomią się w środowisku Windows 98,
ale wersje nieunikodowe będą działać. Programy w tym i w pierwszym rozdzia-
le stanowią te nieliczne wyjątki. MessageBoxW jest jedną z niewielu funkcji Win-
dows dla znaków szerokich, które działają w środowisku Windows 98. Jeśli za-
stąpisz funkcję vsntprintf w SCRNSIZE.C funkcją Windows wprintf (będziesz mu-
siał także pozbyć się drugiego argumentu funkcji), wersja unikodowa SCRNSI-
ZE.C nie uruchomi się w środowisku Windows 98, ponieważ Windows 98 nie
umożliwia korzystania z wprintfW.
Jak zobaczymy dalej w tej książce (zwłaszcza w rozdziale 6, który omawia uży-
cie klawiatury), nie jest łatwo napisać program, który obsługuje dwubajtowe ze-
stawy znaków dla dalekowschodnich wersji Windows. Nasza książka nie poka-
zuje, jak to zrobić, i dlatego niektóre nieunikodowe wersje programów z tej książki
nie będą dobrze działać z dalekowschodnimi wersjami Windows. Właśnie dlate-
go unikod jest tak ważny dla przyszłości programowania. Unikod pozwala pro-
gramom na łatwiejsze przekraczanie barier językowych.
Wyszukiwarka
Podobne podstrony:
Programowniae windows petzold Petzold01
Programowniae windows petzold Petzold05
Programowniae windows petzold Petzold08
Programowniae windows petzold Petzold09
Programowniae windows petzold Petzold13
Programowniae windows petzold Petzold24
Programowniae windows petzold Petzold02
Programowniae windows petzold Petzold21
Programowniae windows petzold Petzold22
Programowniae windows petzold Petzold14
Programowniae windows petzold Petzold04
Programowniae windows petzold Petzold20
Asembler Podstawy programowania w Windows
2 Podstawy programowania Windows (2)
Visual Studio 05 Programowanie z Windows API w jezyku C vs25pw
informatyka usb praktyczne programowanie z windows api w c andrzej daniluk ebook
więcej podobnych podstron