28 Interfejsy API i SDK


Rozdział 28
Interfejsy API i zestawy SDK
Korzystanie z licznych dostępnych podczas programowania w Visual C++
funkcji interfejsów API i zestawów SDK
Tworzenie szybkich aplikacji dźwiękowych i graficznych za pomocą
interfejsów DirectX ______ _______
Programowanie wysyłania i odbierania poczty w oparciu o archi-tekturę
MAPI______________________________
Wykorzystywanie interfejsu MCI do obsługi aplikacji multimedial-nych
Krótkie wprowadzenie API i SDK
API (od ang. application programming interface, interfejs programowania aplikacji) są to zbiory
funkcji przechowywane zazwyczaj w bibliotekach DLL (ang. dynamie link li-braries, biblioteka
konsolidowana dynamicznie, .dli) lub statycznych bibliotekach (ang. static libraries, .libs). Funkcje te
pomagają implementować w programie inne funkcje użytkowe i dostarczają połączenia między aplikacją
a sprzętem przez nią obsługiwanym.
Istnieje wiele różnych interfejsów API upraszczających różne aspekty programowania poprzez
możliwość odwoływania się do standardowych funkcji API. Poszczególnym interfejsom poświęcone są
osobne książki, dlatego w tym rozdziale ograniczymy się do przedstawienia kilku podstawowych
przykładów zastosowań funkcji API.
Ostatnie postępy w rozwoju technologii OLE i COM sprawiają, że wiele z interfejsów API jest
obecnie implementowanych jako obiekty COM, co znacznie ogranicza problemy z zachowaniem
zgodności wersji między różnymi bibliotekami .dli.


742 Poznaj Visual C++ 6
SDK (od ang. software development kits, zestawy do budowania oprogramowania) są
interfejsami API, wzbogaconymi o dokumentację i przykładowe programy. Zestawy SDK
pomagają tworzyć określone rodzaje aplikacji, tak jak Gamę SDK pomaga tworzyć gry, a
OLE DB SDK aplikacje baz danych oparte na technologii OLE.
Generalnie rzecz biorąc, funkcje API nie są standardowo załączane do bibliotek projektu i
wymagają łączenia się z określonymi bibliotekami. Zazwyczaj dołączamy wszystkie prototypy
funkcji i definicje makroinstrukcji API potrzebne w programie przez załączenie plik nagłówka
.h, specyficznego dla interfejsu API, z którego korzystamy.
Zdobywanie interfejsów API i zestawów SDK
Nie wszystkie dostępne interfejsy API i zestawy SDK rozprowadzane są z kompilatorem Visual C++,
większość jednak można ściągnąć spod adresu internetowego Microsoftu www.microsoft.com,
zazwyczaj za darmo. Nawet jeśli potrzebny nam interfejs API lub zestaw SDK jest dostępny w
posiadanej przez nas wersji Visual C++, warto sprawdzić, czy nie są już dostępne ich nowsze wersje,
i
PATRZ TAKŻE
Na temat OLE i COM pisaliśmy w rozdziale 25.
Tworzenie szybkich aplikacji dźwiękowych i graficznych za pomocą
interfejsów DirectX
Interfejsy API typu DirectX stworzone zostały, by rozwiązać problem szybkiego dostępu
do sprzętu obsługującego obraz i dźwięk. Chociaż system Windows oferuje skomplikowane
standardowe funkcje obsługi dźwięku i grafiki, nie są one wystarczająco szybkie i wygodne
dla programistów tworzących gry czy programy do prezentacji graficznych. Bardzo długo
jedynym sposobem ominięcia tego problemu było napisanie programu w systemie DOS
rezygnując tym samym z szeregu usług (takich jak niezależność programu od sprzętu czy
informacje na temat konfiguracji), które oferuje system Windows.
Microsoft wypełnił tę lukę za pomocą serii interfejsów API zapewniających charakte-
rystyczną dla Windows niezależność od wykorzystywanego sprzętu, ale oferujących znacznie
szybszy i bardziej bezpośredni dostęp do urządzeń obsługujących dźwięk, obraz i kontakt z
użytkownikiem. Te interfejsy API to DirectSound, DirectDraw, Direct3D, DirectPlay,
Directlnput i DirectSetup. Łącznie tworzą one zestaw Gamę SDK, który staje się coraz
popularniejszy wśród programistów piszących gry i oprogramowanie obsługujące prezentacje
graficzne.


Interfejsy API i zestawy SDK 743
Problemy z bardzo szybką grafiką
Głównym ograniczeniem dla aplikacji obsługujących animacje komputerowe są możliwości procesora.
Aby oszukać ludzkie oko dając mu złudzenie ruchomego obrazu, należy wyświetlać klatki filmu z
prędkością około 24 klatek na sekundę. Jedna klatka 256-kolorowego ekranu o rozdzielczości 800x600
to 480 000 pikseli. Aby zmienić kolor każdego piksela 24 razy na sekundę, musimy w ciągu tej
sekundy wykonać 11 520 000 operacji. Zdefiniowanie każdego piksela zajmuje około 12 cykli
procesora, co daje 138 240000 cykli zegara tylko po to, by odświeżyć w ciągu sekundy zawartość
ekranu. We współczesnych procesorach o częstotliwości 266 MHz nawet 50% czasu procesora może
być poświęcane operacjom odświeżania ekranu. W pozostałych 50% czasu procesora musimy
wykonać wiele skomplikowanych i czasochłonnych operacji, takich jak obliczenia 3D,
odwzorowywanie tekstur, skalowanie mapy bitowej i komunikację z użytkownikiem.
Interfejsy API korzystają z modelu COM i dlatego odwołania do funkcji grupowane są przez
specyficzne interfejsy COM w obiektach COM implementujących te funkcje.
Biblioteki DLL niezbędne dla interfejsów API nie są instalowane razem z systemem Windows, są
jednak rozprowadzane za darmo i dostępne w kodzie większości współczesnych gier. Można je również
ściągnąć z internetowego adresu Microsoftu: www.micro-soft.com/directK.
PATRZ TAKŻE
Na temat OLE i COM pisaliśmy w rozdziale 25.
DirectSound
Interfejs API DirectSound służy do bezpośredniego sięgania do karty dźwiękowej w celu
wygenerowania dźwięku w oparciu o bufor dźwiękowy (ang. waveform buffer). Bufor ten jest po prostu
fragmentem pamięci, który może być odczytywany bezpośrednio przez kartę muzyczną zmieniającą
wartości tam zapisanych na dźwięki. Najprostsza technika kodowania dźwięku polega na przypisaniu
każdemu z bajtów w buforze cyfry reprezentującej jeden z 256 poziomów amplitudy fali dźwiękowej,
który odpowiadać będ/ic następnie odpowiedniemu wychyleniu membrany w podłączonym do
komputera głośniku
Przypisując kolejnym bajtom wartości, które ułożą się w kształt fali sinusoidalnej otrzymamy czysty
dźwięk określonej wysokości. Zmieniając amplitudę fali poprzez wpisanie odpowiednich liczb do bufora
możemy modulować kształt fali uzyskując najróżniejsze efekty dźwiękowe.
Bufor przechowujący falę, którą słyszymy nazywany jest podstawowym buforem interfejsu
DirectSound (ang. primary buffer). Zazwyczaj nie tworzymy bezpośrednio pod-


744 Poznaj Visual C++ 6
stawowego bufora, tylko definiujemy dźwięki zapisane w podrzędnych buforach (ang. secondary
buffers), które odegrane razem zostają automatycznie zmiksowane w dźwięk bufora podstawowego.
Korzystając z możliwości miksowania dźwięku i kilku podrzędnych buforów możemy uzyskać
bardzo skomplikowane efekty dźwiękowe. Na rysunku 28.1 pokazany został efekt zmiksowania dźwięku
zapisanego w dwu różnych podrzędnych buforach. Dźwięk bufora przedstawiony na górze jest opadającą
sinusoidą (czysty dźwięk), a fala poniżej jest utworzona z losowych wartości dających szum tła (ang.
wbite noise) o rosnącej amplitudzie. Fala na samym dole pokazuje zawartość podstawowego bufora
będącego efektem jednoczesnego odegrania i zmiksowania dwóch podrzędnych buforów. Efektem jest
czysty dźwięk przechodzący stopniowo w szum. Możemy mieszać czyste dźwięki z dźwiękami o falach
kwadratowych lub trójkątnych albo z innymi buforami zawierającymi na przykład nagrane fragmenty
melodii, aby uzyskać najbardziej niesamowite efekty dźwiękowe.
Pierwszą rzeczą, którą należy zrobić, aby móc korzystać z DirectSound APljest utworzenie za
pomocą bezpośredniego odwołania do funkcji CoCreateinstance () interfejsu iDirectSound (patrz rozdział
25) lub korzystając ze skrótu, jaki oferuje funkcja Direct-SoundCreate ().
Funkcja DirectSoundCreate ()
Jeśli korzystamy z funkcji DirectSoundCreate (), musimy załączyć do projektu bibliotekę dsound.lib
wpisując odpowiednią linię na liście Object/L;ibrary Modliłeś dostępnej na karcie Link okna
dialogowego Project Settings. Jeśli natomiast korzystamy z funkcji CoCreateinstance (), biblioteka ta
wprawdzie nie będzie nam potrzebna, ale będziemy musieli zadeklarować globalny identyfikator klasy
(CLSID) i identyfikator interfejsu (IID).
Funkcja DirectSoundCreate () wymaga trzech parametrów: pierwszy z nich jest wskaźnikiem do
globalnego identyfikatora GUID definiującego urządzenie (kartę muzyczną), z którego korzystamy.
Identyfikator ten można zdobyć za pomocą funkcji zwrotnej (ang. caliback function)
DirectSoundEnumerate (). Możemy również przesłać funkcji wartość NULL, aby skorzystać z urządzenia
domyślnego. Drugi parametr jest wskaźnikiem do wskaźnika obiektu DirectSoundinterface i służy do
przypisywania wskaźnikowi aplikacji nowego obiektu. Trzeciemu parametrowi przypisujemy zazwyczaj
wartość NULL, chyba że musimy skorzystać z techniki agregacji COM (ang. COM aggregation).
Jeśli funkcji uda się utworzyć interfejs, zwraca ona wskaźnik do interfejsu iDirect-Sound, który jak
każdy interfejs COM może być dealokowany (usuwany) z pamięci za pomocą funkcji Relase ().
Gdy już mamy interfejs IDirectSound, musimy natychmiast zdefiniować jego poziom
współdziałania (ang. cooperative level). Wiele z interfejsów API typu DirectX posiada poziomy
współdziałania definiujące jak sprzęt, z którego korzystają, będzie wykorzy-


Interfejsy API i zestawy SDK________________________________745
stywany oraz jak jego czas będzie rozdzielany między różne aplikacje. Poziom współdziałania możemy
zdefiniować przyzywając w interfejsie iDirectSoundO funkcję Set-CooperativeLevel (), przesyłając jej
definiujący naszą aplikację identyfikator obsługi okna. Funkcji tej musimy również przesłać znacznik
definiujący poziom współdziałania. Najczęściej stosuje się znacznik DSSCL_NORMAL umożliwiający
urządzeniu podczas obsługi naszego programu pełne współdziałanie z innymi aplikacjami.
Teraz możemy utworzyć podrzędne bufory dźwiękowe za pomocą funkcji CreateSo-undBufferO
interfejsu iDirectSound. Funkcja CreateSoundBuffer() wymaga trzech parametrów. Pierwszy parametr to
struktura DSBUFFERDESC opisująca typ bufora, który tworzymy. Drugi parametr jest wskaźnikiem do
wskaźnika interfejsu bufora iDirectSo-undBuffer aplikacji. Trzeciemu parametrowi przesyłamy wartość
NULL, chyba że korzystamy z agregacji COM.
Struktura DSBUFFERDESC jest definiowana w następujący sposób:
typedef struct _DSBUFFERDESC{
DWORD dwSize;
DWORD dwFlags;
DWORD dwBufferBytes;
DWORD dwReserved;
LPWAVEFORMATEX lpwfxFormat;
} DSBUFFERDESC, *LPDSBUFFERDESC;
Zmienne składowe struktury reprezentują:
Zmienna dwSize reprezentuje rozmiar struktury (który można znaleźć za pomocą operatora sizeof ()).
" Zmienna dwFlags pozwala definiować kilka opcji umożliwiających kontrolę nad różnymi parametrami
odgrywania dźwięku, takimi jak dźwięk stereofoniczny czy głośność. Całkiem dobry zestaw
standardowych ustawień oferuje znacznik
DSBCAPS_CTRLDEFAULT.
Zmienna dwBufferBytes informuje, ile bajtów danych może zawierać bufor. Dla bardziej
skomplikowanych dźwięków lub efektów potrzebny będzie oczywiście dłuższy bufor, dla krótkich
lub powtarzających się dźwięków krótszy. Bufory można odgrywać w pętli powtarzając ich
zawartość wielokrotnie, by uzyskać powtarzające się efekty dźwiękowe.
Zmienna lpwfxFormat wskazuje strukturę WAVEFORMATEX przechowującą informacje instruujące kartę
dźwiękową jak należy dany dźwięk odegrać.
Struktura WAVEFORMATEX przechowuje informacje takie jak prędkość, z jaką bufor ma zostać
odegrany czy liczba bajtów reprezentujących każdy pojedynczy dźwięk. Zawiera również informacje na
temat techniki odgrywania dźwięku, które zależne są już od konkretnej karty muzycznej, chociaż
większość kart dźwiękowych korzysta z opisanej wcześniej techniki WAVE_FORMAT_PCM.


746 Poznaj Visual C++ 6
Po zdefiniowaniu podrzędnego bufora możemy zdefiniować jego zawartość przez
wpisanie odpowiednich wartości w programie albo załadowanie z pliku .wav. W obu
przypadkach dostęp do bufora umożliwia funkcja Lock() interfejsu iDirectSound-Buffer
zwracająca adres i rozmiary bufora. Możemy w buforze zapisać odpowiednie liczby
reprezentujące dźwięki, a następnie zawezwać funkcję Uniock(), która uwolni bufor.
Blokowanie i odblokowywanie bufora
Należy starać się nie blokować bufora zbyt długo, bowiem jeśli zdarzy się, że aktualnie odgrywany
dźwięk będzie potrzebować zablokowanego bufora i odegra w jego miejsce dźwięki generowane
losowo. Niektóre karty dźwiękowe posiadają własną pamięć bufora niedostępną bezpośrednio z
przestrzeni adresowej procesora. W tym przypadku funkcje Lock() i unlock() zajmują się również
transferowaniem zawartości pamięci pomiędzy buforem karty a pamięcią komputera.
Po przypisaniu buforowi odpowiednich reprezentujących dźwięki liczb możemy odegrać
go przyzywając funkcję Play () interfejsu iDirectSoundBuffer. Funkcja ta wymaga
zdefiniowania trzech parametrów. Pierwszym dwóm parametrom należy przypisać wartość
zero. Trzeciemu natomiast znacznik DSBPLAY_LOOPING, jeśli chcemy odgrywać bufor w pętli
(wielokrotnie) lub zero jeśli chcemy odegrać go tylko raz. Odgrywanie bufora można przerwać
przyzywając funkcję Stop ().
Na listingu 28.1 pokazujemy, w jaki sposób za pomocą interfejsu DirectSound można w
oparciu o aplikację SDI utworzyć prosty, sterowany klawiaturą syntezator dźwięku. Aplikacja
będzie tworzyć i wyświetlać w oknie zawartość dwóch podrzędnych buforów (dwie górne
linie, czerwona i zielona widoczne na rysunku 28.1).

Rysunek 28.1. Program SoundYiew wyświetlający zawartość dwóch podrzędnych buforów i dźwięk
powstały po ich zmiksowaniu


Interfejsy API i zestawy SDK 747
Kiedy użytkownik wciśnie odpowiedni klawisz, oba bufory są odgrywane dając w efekcie dźwięk,
którego fala przedstawiona została na dole (niebieska linia, czego niestety nie widać na rysunku).
Wysokość dźwięku (sinusoidalnej fali) definiowana jest w zależności od kodu ASCII klawisza, który
został wciśnięty, dzięki czemu dalsze litery alfabetu dają wyższe dźwięki.
Oczywiście program ten wymaga karty dźwiękowej.
Listing 28.1. LST32_1.CPP - prosty program syntetyzatora miksujący zawartość dwóch buforów po
wciśnięciu klawisza klawiatury
1 CSoundView::CSoundView()
2 {
3 m_pDSObject = NULL;
4 m_pDSBuffer = NULL;
5 m_pDSMix = NULL;
6 }
7
8 CSoundView::~CSoundView()
9 <
10 if (m_pDSMix) m_pDSMix->Release(); O
11 if (m_pDSBuffer) m_pDSBuffer->Release(); O
12 if (m_pDSObject) m_pDSObject->Release(); O
13 }
14
15 void CSoundView::PlayFreq(double dFreq)
16 (
17 if (!m_pDSObject)
18 {
19 // Utwórz obiekt DirectSound
20 if(DS_OK==Direct3oundCreate(
21 NULL,&m_pDSObject,NULL))
22 {
23 // Ustal poziom współdziałania
24 m_pDSObject->SetCooperativeLevel(m_hWnd,
25 DSSCL_NORMAL) ;
26
27 // Oczyść bufor obiektu DirectSound
28 memset(&m_DSBufferDesc,O,sizeof(DSBUFFERDESC)) ;
29
30 // Zdefiniuj podrzędny bufor
31 m_DSBufferDesc.dwSize = sizeof(DSBUFFERDESC);
32 m_DSBufferDesc.dwFlags = DSBCAPS_CTRLDEFAULT;
33 m_DSBufferDesc.dwBufferBytes = 4096;
34


748___________________ ____ Poznaj Visual C++ 6





35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// Zdefiniuj format fali jako 22.0Khz, l bajt DtoA
static WAVEFORMATEX sWave = (
WAVE_FORMAT_PCM,1,22000,22000,1,8,0 };
m DSBufferDesc.lpwfxFormat = &sWave;
// Utwórz pierwszy podrzędny bufor m_pDSObject-
>CreateSoundBuffer(&m_DSBufferDesc, &m_pDSBuffer,NULL)
;
// Utwórz drugi podrzędny bufor m_pDSObject-
>CreateSoundBuffer(&m_DSBufferDesc, &m_pDSMix,NULL) ;
}
}
// Jeśli obiekt DirectSound istnieje ...
i f (m_pDSObject)
{
// Zadeklaruj wskaźniki buforów
LPBYTE pBufferl,pBuffer2;
LPBYTE pEnvl,pEnv2;
DWORD dwSizel,dwSize2;
// Usuń tło CCIientDC
dcCIient(this);
CRect rcCIient;
GetCIientRect(&rcClient) ;
int yOff = (rcCIient.Height()-24)/3;
dcCIient.FilISolidRect(
rcCIient,RGB(255,255,255)) ;
// Zablokuj bufor
m pDSBuffer->Lock(0,m_DSBufferDesc.dwBufferBytes, @
(LPVOID*)SpBufferl,SdwSizel,
(LPVOID*)&pBuffer2,&dwSize2,0) ;
m_pDSMix->Lock(0,m_DSBufferDesc.dwBufferBytes,
(LPVOID*)&pEnvl,SdwSizel,
(LPVOID*)&pEnv2,&dwSize2,0) ;
// Zapełnij bufory double dRange = 128.0 /
(double)dwSizel;
double dXRange = (double)rcCIient.Width() /
(double)dwSizel;
for(int i=0;i<(int)dwSizel;i++)


80 {
81 double dAmplitude = 1.0 + dRange * (double)i;
82 BYTE bl = (BYTE)(127 + (128.0 - dAmplitude)
83 * sin((double)i / (0.147 * dFreq)));
84 BYTE b2 = (BYTE) (rand ()% (int) dAmplitude) 1;
85 *(pBufferl+i) = bl;
86 *(pEnvl+i) = b2;
87
88 BYTE b3 = bl + b2;
89
90 // Namaluj fale @
91 int x = (int) (dXRange * (double)i);
92 dcClient.SetPixelV(x,
93 (bll) ,RGB(255,0,0) ) ;
94 dcClient.SetPixelV(x,y0ff+64+
95 (b2l) ,RGB(0,255,0) );
96 dcClient.SetPixelV(x,yOff*2+'
97 (b3l) ,RGB(0,0,255) );
98 }
99
100 // Odblokuj bufory i odegraj dźwięk Q
101 m_pDSBuffer->Unlock(pBufferl,dwSizel,
102 pBuffer2,dwSize2);
103 m_pDSMix->Unlock(pEnvl,dwSizel,
104 pEnv2,dwSize2) ;
105 m_pDSBuffer->Play(0,0,0) ;
106 m_pDSMix->Play(0,0,0) ;
107 }
108 }
109
110 void CSoundView::OnKeyDown(UINT nChar.UINT nRepCnt,
111 UINT nFlags)
112 {
113 CYiew::OnKeyDown(nChar, nRepCnt, nFlags);
114
115 // Zagraj nutę o częstotliwości zależnej od wciśniętego klawisza
116 if 117 }
O W elegancki sposób usuwamy obiekty COM DirectSound za pomocą funkcji Relase.
@ Funkcja Lock() blokuje dostęp do pamięci i zwraca wskaźniki do buforów używanych
przez program.


750 Poznaj Visual C++ 6
W oparciu o zapisane w buforach wartości rysujemy odpowiednie fale. O Po
odblokowaniu zawartość buforów jest odgrywana.
Fragment kodu przedstawiony na listingu 28.1 powinien zostać załączony do klasy
widoku zbudowanego za pomocą kreatora CIassWizard szkieletu aplikacji SDI (tutaj nazwanej
Sound). Z powodu dodatkowych elementów, które umieszczamy w klasie widoku (w pliku
SoundYiew.cpp), należy do pliku nagłówka zawierającego definicję klasy (Sound-View.h)
dodać zmienne składowe wykorzystywane na listingu:
IDirectSound* m_pDSObject;
IDirectSoundBuffer* m_pDSBuffer;
IDirectSoundBuffer* m_pDSMix;
DSBUFFERDESC m_DSBufferDesc;
void PlayFreq(double dFreq);
Deklarujemy tutaj zmienne składowe wskaźnika interfejsu potrzebne interfejsowi
DirectSound: dwa bufory i strukturę opisującą bufor. Definiujemy również funkcję PlayFreq ()
wykorzystywaną do implementacji efektów dźwiękowych.
Należy również za pomocą kreatora CIassWizard dodać funkcję obsługi komunikatu
WM_KEYDOWN. W ten sposób automatycznie utworzymy plik nagłówka i odpowiednie hasła w
mapie komunikatów.
Aby poinformować kompilator o niezbędnych wskaźnikach interfejsu, strukturze
WAVEFORMATEX i wykorzystywanej funkcji sinusoidalnej, należy do pliku definicji klasy
dodać następujące dyrektywy łinclude:
łinclude "mmsystem.h" łinclude
"DSOUND.H" łinclude "math.h"
Konstruktor widoku przedstawiony w liniach 1-6 listingu 28.1 inicjuje wskaźnik obiektu
DirectSound m_pDSObject i dwa wskaźniki buforów m_pDSBuffer i m_pDS-Object przypisując im
wartość NULL. Odpowiadający mu przedstawiony w liniach 8-13 destruktor niszczy obiekty COM
przyzywając ich funkcję Relase () usuwając za jej pomocą odwołania do tych obiektów. W liniach 17-49
główna funkcja PlayFreq () wykorzystywana jest do inicjowania obiektu DirectSound i buforów, gdy
wzywana jest po raz pierwszy. Inicjacja tych elementów musi być wykonana właśnie tutaj, ponieważ
funkcja SetCooperativeLevel () z linii 24 wymaga identyfikatora obsługi istniejącego okna m_hWnd,
które jeszcze nie istniało w czasie wykonywania konstruktora.
Jeśli obiekt DirectSound zostanie w linii 20 prawidłowo inicjalizowany, za pomocą funkcji
CreateSoundBuffer () tworzymy dwa podrzędne bufory zaraz po zdefiniowaniu odpowiedniego
opisu bufora m_DSBufferDesc i struktury sWave typu WAVEFORMATEX.


Interfejsy API i zestawy SDK 751
Poziomy współdziałania
Istnieją cztery możliwe poziomy współdziałania dla obiektów Direct Sound. Poziom DSSCL_EXCLUSIVE
daje aplikacji wyłączny dostęp (jeśli jest pierwszą, która to zadeklaruje) do karty dźwiękowej. Inne
aplikacje zostaną odcięte od dźwięku. Poziom DSSCL_NORMAL pozwala innym aplikacjom na
jednoczesne korzystanie z karty na równych zasadach. DSSCL_PRIORITY daje aplikacji priorytet w
dostępie do zasobów karty dźwiękowej, a poziom DSSCL_WRITEPRIMARY daje aplikacji najwyższy prio-
rytet dostępu do podstawowego bufora i blokuje odgrywanie wszystkich podrzędnych buforów (z
pozostałych aplikacji).
Druga połowa funkcji PlayFreq () definiuje bufory, rysuje fale w oknie widoku i odgrywa dźwięk
odpowiednio do wartości częstotliwości zapisanej w parametrze dFreq. W liniach 59-65 inicjowany jest
kontekst urządzenia klienta, widok jest oczyszczany i inicjowany obszar roboczy całego widoku. W
liniach 68-73 za pomocą funkcji Lock() blokowane są oba podrzędne bufory i pobierane wskaźniki do
nich. W liniach 79-98 znajduje się główna pętla, która generuje wartości zapisywane w buforach i
przenosi je na malowany w widoku rysunek fal. W linii 83 wyliczamy punkty b l tworzące sinusoidalną
falę w oparciu o częstotliwość i pozycję w buforze definiowaną przez licznik pętli i oraz opadającą
wartość amplitudy głośności dAmplitude. Uzyskana wartość jest w linii 85 zapisywana w pierwszym z
dwu podrzędnych buforów, a w linii 92 wykorzystywana przez funkcję Setpixeiv() do narysowania
czerwonego piksela, z których to pikseli powstanie górna fala.
Losowa wartość dla szumu zapisywanego w drugim buforze za pomocą zmiennej b2 wyliczana jest
w linii 84 w oparciu o funkcję rand () generującą liczby losowe i o odwróconą odwróconą i zmniejszoną
o połowę wartość amplitudy głośności dAmplitude, co daje w efekcie narastający szum. Wartość ta jest
następnie w linii 86 przypisywana buforowi, a w linii 94 wyświetlana na ekranie jako zielony piksel.
Trzecia zmienna b3 odpowiadająca zawartości podstawowego bufora wyliczana jest jako zmiksowana
(przez dodanie ich do siebie) wartość zmiennych bl+b2 w linii 88 i wyświetlana na ekranie jako niebieski
piksel w linii 96.
Miksowanie buforów dźwiękowych
Poprzez zmiksowanie dwóch fal odgrywanych jednocześnie z dwóch podrzędnych buforów możemy
modulować dźwięk jednej fali za pomocą drugiej. Za pomocą tej techniki możemy połączyć falę o
wysokiej częstotliwości z falą o niskiej częstotliwości, otrzymując wibrujący lub falujący dźwięk
wynikowy.
W liniach 101-103 bufory są odblokowywane za pomocą funkcji Uniock (), a następ nie odgrywane za
pomocą funkcji Play () w liniach 105 i 106. W momencie gdy przyzy-


752_____________________________________Poznaj Visual C++ 6
wana jest pierwsza funkcja p l a y (), automatycznie konstruowany jest podstawowy bufor dopasowujący
się do wymagań podrzędnego bufora, wykorzystywany do przesyłania informacji do karty muzycznej.
Kolejne odwołanie do funkcji Play () sprawi, że drugi podrzędny bufor zostanie zmiksowany z
dźwiękiem zapisanym w podstawowym buforze, co da pożądany efekt dźwiękowy.
Na koniec możemy jeszcze za pomocą okna dialogowego New Windows Messa-ge/Event handler
dodać funkcję OnKeydown () przechwytującą komunikaty o wciśnięciu klawisza klawiatury i
przyzywającą funkcję PlayFreq (). Funkcja OnKeydown () przesyła funkcji PlayFreq () jako wartość
częstotliwości numer wciśniętego klawisza, tworząc prosty sterowany klawiaturą syntezator dźwięku.
Zanim zbudujemy opisaną wyżej aplikację musimy pamiętać, żeby połączyć się z biblioteką
dsound.lib wpisując plik dsound.lib na liście Object/Library Modules na karcie Link okna
dialogowego Project Settings. Okno to wywołujemy albo wciskając klawisze Alt+F7, albo wybierając
w menu Project polecenie Settings.
Po zbudowaniu i uruchomieniu aplikacji będziemy mogli wciskając wybrany klawisz klawiatury
wyświetlać w oknie rysunek fal dźwiękowych i porównywać go jednocześnie z odegranym przez
komputer dźwiękiem.
PATRZ TAKŻE
Op/s zasad działania OLE i COM znaleźć można w rozdziale 25.
DirectDraw
Interfejs API DirectDraw możemy wykorzystać do napisania bardzo szybkiej, wolnej od
nieprzyjemnego migania obrazu aplikacji graficznej. Tenże interfejs API umożliwia szkieletowi aplikacji
synchronizację techniki podwójnego buforowania (ang. double buffeńng), niezbędnej przy tworzeniu gier
i oprogramowania do tworzenia animacji wymagających szybkiej grafiki. Pozwala on na całkowitą
kontrolę sposobu wyświetlania obrazu w systemie Windows, zmienianie różnych trybów wyświetlania
(włączając niesławny tryb ModeX wykorzystywany w grze Doom 2) i zamianę (ang. flip) powierzchni
ekranu, dzięki czemu, kiedy ekran wyświetla jeszcze poprzedni obraz, możemy już malować na
powierzchni tła następny. Ta zamiana może być synchronizowana z operacjami monitoraw taki sposób,
aby zachodziła w momencie, gdy strumień elektronów w monitorze powraca do wyjściowej pozycji,
umożliwiając w ten sposób płynne wyświetlanie obrazu bez nieprzyjemnego migotania.
Możemy również całkowicie porzucić windowsowy system wyświetlania i wewnątrz zwykłego
okna wykorzystać szybszy bezpośredni sposób rysowania. Interfejs DiresctDraw zawdzięcza swoje
wysokie możliwości w zakresie rysowania i przenoszenia bitów (ang. bit bliting) dwóm elementom
składowym: jednym z nich jest HAL (ang. hardware abstraction layer) a drugim HEL (ang. hardware
emulation layer). Kiedy korzystamy z funkcji renderu-jących (ang. rendering) i przenoszących bity
powierzchni DirectDraw, przyzywamy HAL.


Interfejsy API i zestawy SDK 753
ModeX
ModeX jest nieudokumentowanym trybem karty VGA. Wielu programistów gier było swego czasu
zmuszonych do korzystania z trybu co prawda wielokolorowego, ale o stosunkowo niskiej
rozdzielczości, aby uzyskać wystarczająco szybkie odświeżanie ekranu niezbędne dla
wykorzystywanej w grze animacji. Jednym z lepszych kandydatów był tu tryb VGA 0x13 (w zapisie
szesnastkowym) dający rozdzielczość 320x200. Jednak nieudokumentowany tryb ModeX pozwalał
osiągnąć rozdzielczość 320x240 pikseli, co tłumaczy jego popularność.
W ten sposób otrzymujemy niezależny od karty graficznej mechanizm pozwalający naszemu
programowi pracować na wielu różnych kartach graficznych. Jeśli funkcja rende-rująca może być
wykonywana przez kartę graficzną, zyskujemy bardzo na szybkości. Jeśli nie, HEL w sposób
niezauważalny tworzy niezbędne nam funkcje karty, dzięki czemu nie musimy martwić się o rzeczywiste
możliwości naszej karty graficznej.
Interfejs API DirectDraw oferuje tylko cztery obiekty COM przedstawione w tabeli 28.1. Oglądając
tabelę można zauważyć, że niektóre z interfejsów mają dodaną do nazwy cyfrę 2. Dzieje się tak dlatego, że
są to nowsze wersje interfejsów, których wersje wcześniejsze są nadal dostępne dla starszych aplikacji.
Możemy również zauważyć, że najważniejszym obiektem jest tutaj DirectDrawSurface. Powierzchnie (ang.
surfaces) są tworzone przez ten właśnie główny obiekt DirectDraw, a następnie mogą być odpowiednio
przycinane (ang. clipped) przez obiekty DirectDrawCIipper lub korzystać z kolorów obiektów
DirectDrawPalette.
Tabela 28.1. Obiekty DirectDraw i ich funkcje
Obiekt

DirectDraw

Interfejs COM

Jego funkcje

DirectDraw

IDirectDraw2

Zarządza trybami karty, możliwościami urządzenia i tworzy
pozostałe obiekty




DirectDrawSurface IDirectSurface2
Zarządza głównymi i podrzędnymi mapami
bitowymi, podwójnym buforowaniem,
rysowaniem i funkcjami przenoszącymi bity.
Może być przycinany przez obiekt
DirectDrawCIipper i korzystać z kolorów obiektu
DirectDrawPalette
IDirectDrawCIipper Zarządza listą informacji definiujących przycinanie powierzchni
DirectDrawCIipper
IDirectDrawPalette Zarządza paletą kolorów i informacjami odwzorowywania dla kolorów wykorzystywanych przez powierzchnię
DirectDrawPalette


754_________________ ___ ______________ Poznaj Visual C++ 6
Gtówny obiekt DirectoryDraw tworzymy korzystając z funkcji biblioteki ddraw.lib,
DirectDrawCreate () lub bezpośrednio za pomocą funkcji CoCreateInstance ().
Funkcja DirectDrawCreate () wymaga przesłania jej trzech parametrów. Pierwszy parametr jest
wskaźnikiem do globalnego identyfikatora GUID, który definiuje wykorzystywany sterownik ekranu.
Standardowo przesyłamy tutaj wartość NULL informującą, że powinniśmy korzystać z aktywnego
sterownika. Kolejne sterowniki możemy odnaleźć za pośrednictwem odpowiedniej funkcji
wyliczeniowej. Drugi parametr jest wskaźnikiem interfejsu iDirectDraw do wskaźnika nowego obiektu.
Jeśli skorzystamy z tej metody, powinniśmy następnie użyć funkcji COM Querylnterface (), aby zdobyć
wskaźnik do nowszego interfejsu lDirectDraw2. Trzeciemu parametrowi jest normalnie przypisywana
wartość NULL, jako że jest on wykorzystywany w agregacji COM.
Agregacja COM
Agregacja jest techniką COM wykorzystywaną przez komponenty zawierające inne komponenty, aby
korzystać z funkcji wewnętrznych komponentów. Zewnętrzny obiekt oferuje programowi klientowi
interfejs, który jest w rzeczywistości interfejsem wewnętrznego obiektu. Kiedy klient pyta o wskaźnik
do tego interfejsu zewnętrzny komponent wręcza mu bezpośredni wskaźnik do agregowanego
wewnętrznego obiektu. Przesyłając wspomniany tu trzeci parametr obiekt DirectDraw pozwala innym
obiektom agregować siebie. W starszych wersjach bibliotek DirectX technika agregacji może nie być
obsługiwana i przesłanie funkcji DirectDrawCreate () trzeciego parametru zwróci kod informujący o
błędzie.
Znacznie prościej jest utworzyć obiekt COM za pomocą funkcji CoCreateinstan-ce (), jako że
docieramy wtedy bezpośrednio do nowszego interfejsu lDirectDraw2 i nie musimy się łączyć z
biblioteką ddraw.lib. Obiekt DirectDraw tworzymy przesyłając funkcji CoCreateInstance () jako
identyfikator klasy wartość CLSlD_DirectDraw, a jako identyfikator interfejsu, HD_DirectDraw, łącząc
się od razu z nowszym interfejsem. Odpowiednie identyfikatory dostępne są w pliku nagłówka
DrawDIg.h. Następnie musimy wezwać funkcję initializef) przesyłając jej identyfikator GUID, który
identyfikuje sterownik ekranu (standardowo wpisujemy NULL).
Gdy już mamy obiekt DirectDraw, musimy za pomocą funkcji IDirectDraw: :SetCooperativeLevel
() zdefiniować jego poziom współdziałania ze sterownikiem. Funkcja ta ma dwa parametry; pierwszy jest
identyfikatorem obsługi identyfikującym aplikację będącą właścicielem obiektu, a drugi jest
odpowiednim znacznikiem definiującym poziom współdziałania.
Wpisując znacznik DDSCL_NORMAL dzielimy się dostępem do .sterownika z innymi aplikacjami, jeśli
jednak potrzebna jest nam kontrola nad całym ekranem, musimy użyć znaczników DDSCL_EXCLUSIVE i
DDSCL_FULLSCREEN. Aby korzystać z techniki podwójnego buforowania, musimy mieć kontrolę nad
całym ekranem.


Interfejsy API i zestawy SDK 755
Możemy teraz utworzyć powierzchnie, na których będziemy tworzyć wyświetlany obraz
przywołując funkcję CreateSurface obiektu DirectDraw i przesyłając jej opisującą nową powierzchnię
strukturę DDSURFACEDESC. Struktura ta opisuje różne rodzaje powierzchni. Zazwyczaj tworzymy jedną
powierzchnie podstawową (ang. primary surface) za pomocą znacznika DDSCAPS_PRIMARYSURFACE i
pewną liczbę rezerwowych powierzni-buforów, aby korzystać z techniki przenoszenia bitów lub zamiany
stron (podwójnego buforowania). Dla techniki zamiany stron możemy połączyć rezerwowe bufory w tym
samym odwołaniu do funkcji CreateSurface () co podstawowy bufor, definiując zmienną zliczania
buforów dwBackBufferCount i dołączając znacznik DDSD_BACKBUFFER-COUNT.
Możemy również utworzyć kilka obiektów DirectDrawCIipper, aby zdefiniować listę odcinania
(ang. clipping list). Obiekty te są bardzo przydatne, kiedy wykorzystujemy DirectDraw na normalnym
ekranie Windows, ponieważ możemy utworzyć tę listę w oparciu o okno rodzica, co pozwoli nam
uniknąć niepotrzebnego odświeżania powierzchni ekranu poza jego granicami. Obiekty te mogą być
tworzone w obiekcie DirectDraw za pomocą funkcji CreateClipper (), a następnie wybierane w
utworzonej powierzchni za pomocą jej funkcji SetCIipper ().
Cykliczne zmienianie kolorów a animacja
Cykliczne zmienianie kolorów może być również wykorzystywane, by za darmo uzyskać dodatkową
animację. Zmieniając kolory w palecie kolorów (ang. color pa-lette) możemy dodać
monochromatyczne, statyczne pętle animacji tam, gdzie ekran nie musi być odmalowywany.
Przykładowo, jeśli zdefiniujemy cztery koncentryczne koła, każde w innym kolorze, możemy następnie
zdefiniować trzy z kolorów jako czarne, a jeden jako biały. Biały kolor może być przesuwany przez
paletę pomiędzy czterema pozycjami kolorów w każdej kolejnej klatce animacji, co da efekt podświe-
tlenia po kolei każdego z kół.
Palety umożliwiające cykliczne zmienianie kolorów mogą być tworzone za pomocą funkcji
CreatePalette () w głównym obiekcie DirectDraw. Palety mogą być inicjowane zmiennymi COLORREF i
wybierane w powierzchni za pomocą funkcji powierzchni Set-PaletteO.
Na powierzchni możemy malować za pomocą zwykłych funkcji GDI, blokując ją najpierw, a
następnie pobierając odpowiedni kontekst urządzenia za pomocą funkcji powierzchni GetDC (). Kiedy
skończymy rysowanie, musimy przyzwać funkcję powierzchni Rela-seDC (), która odblokuje
powierzchnię. Zestaw szybkich funkcji służących do przenoszenia bitów, takich jak Bit (), pozwala
kopiować powierzchnie, nadawać im różne barwy w oparciu o techniki graficzne, takie jak rozciąganie
czy odwzorowywanie tekstury.
Podstawowe powierzchnie i ich powierzchnie-burory mogą być zamieniane miejscami za pomocą
funkcji Flip (), która może pracować asynchronicznie czekając z wykonaniem operacji na moment, gdy
monitor przygotowuje się do wyświetlenia następnej klatki obra-


756_____________________________________Poznaj Visual C++ 6
zu. Efekt ten osiągniemy przesyłając funkcji znacznik polecający czekać jej do tego momentu lub
przywołując funkcję WaitForVerticalBlank() obiektu DirectDraw. Inne użyteczne, powiązane z
monitorem funkcje to GetMonitorFrequency () i GetScanLine ().
Korzystanie z funkcji przenoszących bity i z funkcji GDI
Należy uważać, żeby nie przyzywać funkcji przenoszącej bity, gdy mamy aktywny kontekst
urządzenia zdobyty za pomocą funkcji GetDC (). Funkcja GetDC () blokuje kontekst
urządzenia i nie pozwoli funkcji przenoszącej bity na wykonanie zadania. Dlatego zanim
wezwiemy funkcję przenoszącą bity, musimy przywołać funkcję Re-
laseDC().
Listing 28.2 prezentuje przykładową aplikację DirectDraw, która wykorzystuje podwójne
buforowanie, aby po przejęciu kontroli nad pulpitem Windows wyświetlić szybką, wolną od migotania
animację. Nie musi się ona łączyć z biblioteką ddraw.lib, ponieważ do tworzenia obiektu DirectDraw
wykorzystuje funkcję CoCreateinstance () i łagodnie przywraca ekran Windows, kiedy wciśniemy
klawisz Esc. Przykład ten używa standardowego szkieletu aplikacji opartej na oknie dialogowym
(nazwanej Draw). W funkcji Oni-nitDialogO definiowany jest ekran. Następnie w podrzędnym buforze
w określonych odstępach czasu malowany jest rysunek. Windowsowe komunikaty czasu odbierane są za
pomocą funkcji OnTimer (). Kiedy już rysunek zostanie ukończony, bufory tła i pierwszego planu są
zamieniane w momencie, gdy strumień elektronów w monitorze wyłączany jest między kolejnymi
klatkami obrazu, tak że nowy rysunek wyświetlany jest natychmiast i bez migotania.
Listing 28.2. LST32_2.CPP - obiekt DirectDraw wykorzystany do tworzenia pozbawionej migotania
animacji korzystającej z podwójnego buforowania
1 CDrawDlg::CDrawDlg(CWnd* pParent /*=NULL*/)
2 : CDialog(CDrawDlg::IDD, pParent)
3 {
4 //((AFX_DATA_INIT(CDrawDlg)
5 // UWAGA: CIassWizard doda inicjację zmiennych składowych
6 //}}AFX_DATA_INIT
7 // Zauważ, że Loadlcon nie wymaga następującego
8 m_hlcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
9
10 m_pIDraw = NULL;
11 m_pIMainSurface = NULL;
12
13 CoInitialize(NULL); O
14 }
15


Interfejsy API i zestawy SDK 757





16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
CDrawDlg::-CDrawDlg() (
if (m_pIMainSurface) ( m pIDraw-
>SetCooperativeLevel(m_hWnd,DDSCL_NORMAL) ;
m_pIDraw->ReśtoreDisplayMode(); @
m_pIMainSurface->Release() ;
) if (m pIDraw) m pIDraw->Release();
CoUninitialize() ;
>
BOOL CDrawDlg::OnInitDialog() {
CDialog::OnInitDialog() ;
// ** Inicjuj bibliotekę OLE / COM if
(FAILED(CoInitialize(NULL))) return FALSE;
// ** Utwórz interfejs obiektu DirectDraw HRESULT hr =
CoCreateInstance(CLSID_DirectDraw,NULL, CLSCTX_ALL,
IID_IDirectDraw2, (void**)&m_pIDraw) ;
if( !FAILED(hr))
{
hr = m_pIDraw->Initialize((struct _GUID*)NULL);
if (hr !=DD_OK) return FALSE;
}
// ** Zdefiniuj wyłączność na korzystanie z całego ekranu
m_pIDraw->SetCooperativeLevel(m_hWnd,
DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN |DDSCL_ALLOWREBOOT) ;
// ** Zdefiniuj strukturę opisującą powierzchnię
DDSURFACEDESC DrawSurfaceDesc;
memset(SDrawSurfaceDesc,O,sizeof(DrawSurfaceDesc)) ;
DrawSurfaceDesc.dwSize = sizeof(DrawSurfaceDesc);
DrawSurfaceDesc.dwFlags = DDSD_CAPS O
| DDSD_BACKBUFFERCOUNT;
DrawSurfaceDesc.ddsCaps.dwCaps = DDSCAPS_COMPLEX |
DDSCAPS_FLIP | DDSCAPS_PRIMARYSURFACE;
DrawSurfaceDesc.dwBackBufferCount = l;
// ** Utwórz podstawową, powierzchnię


758_____________________________________Poznaj Visual C++ 6
hr = m_pIDraw->Create3urface(SDrawSurfaceDesc,
(IDirectDrawSurface**)&m_pIMainSurface,NULL) ;
if (FAILED(hr)) return FALSE;
// ** Znajdź podrzędny bufor DrawSurfaceDesc.ddsCaps.dwCaps =
DDSCAPS_BACKBUFFER;
hr = m_pIMainSurface->GetAttachedSurface( &DrawSurfaceDesc,
ddsCaps,&m_pIFlipSurface) ;


66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
SetTimer(l,50,0); // ** Uruchom licznik czasu z interwałem 50ms
return TRUE;
) '
void CDrawDlg::OnTimer(UINT nIDEvent) (
CDialog::OnTimer(nIDEvent) ;
static RECT rc=(0,0,0,0);
static double dPetals=2.0;
// ** Szybko oczyść powierzchnię
if (rc.right>0)
{
DDBLTFX dbltfx;
dbltfx.dwSize=sizeof(DDBLTFX) ;
dbltfx.dwFillColor=RGB(O,0,0) ;
HRESULT hr = m_pIFlip3urface->Blt(&rc,NULL,
&rc,DDBLT_COLORFILL,&dbltfx) ;
} HDC hdc = NULL;
// ** Pobierz kontekst urządzenia dla powierzchni
if (m_pIFlip3urface->GetDC(Shdc) == DD_OK)
{
if (hdc) (
C DC dc;
dc.Attach(hdc) ;
rc.right = dc.GetDeviceCaps(HORZRES) ;
rc.bottom = dc.GetDeviceCaps(VERTRES);


Interfejsy API i zestawy SDK 759
102 double mx = (double) (rc. rightl) ;
103 double my = (double) (rc.bottoml) ;
104
105 CPen psCol(PS_SOLID,1,RGB(O,255,0));
106 CPen* ppsOld = dc.SelectObject(SpsCol);
107
108 double sx = mx/2;
109 double sy = my/2;
110 double sAngle = 0.0;
111
112 // ** Narysuj klatkę animacji
,113 for(int i=0;i<(int)dPetals;i++)
114 { Q
115 double ślą = sin(sAngle);
116 double clą = cos(sAngle) ;
117 double s2a = sin(sAngle * dPetals);
118 double c2a = cos(sAngle * dPetals);
119 int x = (int) (mx+sx*cla+sx*c2a) ;
120 - int y = (int)(my+sy*sla-sy*s2a) ;
121 if (i==0) dc.MoyeTo(x,y) ;
122 else dc.LineTo(x,y);
123 sAngle+=0.01745;
124 }
125
126 dPetals+=0.1;
127 dc.SelectObject(ppsOld) ;
128 dc.Detach();
129 }
130 m_pIFlipSurface->ReleaseDC(hdc) ;
131 }
128
133 // ** Wyświetl nowa. powierzchnię
134 m_pIMainSurface->Flip(NULL,DDFLIP_WAIT);
135 }
O Funkcja Colnitialize inicjuje biblioteki COM. @ Tryb wyświetlania zostaje ustawiony jako
NORMAL, a obiekty COM są uwalniane.
Wykorzystujemy funkcję CoCreatelnstance(), aby pobrać wskaźnik do interfejsu !DirectDraw2
nowego obiektu DirectDraw.
O Podstawowa powierzchnia z jednym podrzędnym buforem jest opisana w strukturze
DrawSurfaceDesc.


:C!*|A" SKW -
Wwswrssyar "-c-T.
cas'lr--..^i! 3
ifsssScK -, yit, :
V:VV -f 11-1-i K
^TŁM^StL ;is
c-ł.-LiL :;:: e -^ - -
^ -,; TfcTCid
-MCO^-I-w-gTl t-
a"-!-,-M:toin ^T". Z
Sr. r^utcg z a.qM, ^




^łl LEŁK L1E.J tA
=1 pnEllOC.l
ilatł-gi L-.-I.-B" ' " ii-
La: 7J. FJr.fci:
e~J.Aa:ily?. i ŁLi->-
^i-i.ir.Łi3i;r..-i-i.
FJ ..ł-EJI-^/Ł-llJ
kIO, *3TT;!:fc
/. Jrc P: FT.LT.a-
r.=IŁ*^.E : aiłKirJ .-F
E- pw-.aŁŁł-s:
fe.LrlK


760 Poznaj Visual C++ 6
Wskaźnik do podrzędnego bufora możemy pobrać za pomocą funkcji GetAtta-
chedSurface ().
Funkcję Blt() wykorzystujemy, aby szybko zapełnić powierzchnię określonym kolorem zamiast
kopiować w tym celu powierzchnie.
Q Za pomocą funkcji matematycznych tworzymy dwa szybko wirujące skomplikowane rysunki.
Funkcja Flip() odczekuje z zamianą powierzchni do momentu, gdy ekran zakończy wyświetlanie
kolejnej klatki obrazu, by uniknąć migotania wywołanego przez wyświetlanie na ekranie na wpół
narysowanych stron.
Dodatkowo w kodzie z listingu 28.2 musimy upewnić się, że na początku pliku
DrawDIg.cpp załączone zostaną za pomocą dyrektywy ttinclude pliki przedstawione niżej,
niezbędne do dostarczenia programowi identyfikatorów GUID i definicji funkcji
trygonometrycznych.
łinclude łinclude
"DrawDIg.h" łinclude "math.h"
Definicje interfejsów DirectDraw możemy dostarczyć programowi załączając na początku pliku
DrawDIg.h plik nagłówka interfejsu DirectDraw:
#include
Następujące wskaźniki powinny zostać dodane do pliku DrawDIg.h jako zmienne składowe:
IDirectDraw2* m pIDraw;
IDirectDrawSurface2* m_pIMain3urface;
IDirectDrawSurface2* m_pIFlipSurface;
W kodzie z listingu 28.2 wskaźnikom interfejsu przypisywana jest w konstruktorze CDrawDlg (linie
10 i 11) wartość NULL, a w linii 13 za pomocą funkcji Colnitialize () inicjowana jest biblioteka COM.
Funkcja oninitDialog () wykorzystywana jest do tworzenia obiektu DirectDraw za pomocą
przyzywanej w linii 36 funkcji coCreateinstance (). Proces tworzenia kończymy przyzywając w linii 41
funkcję initialize (), która informuje, że będziemy korzystać w programie z aktywnego sterownika.
Następnie w linii 47 ustalamy poziom współdziałania rezerwując sobie wyłączność w korzystaniu z
urządzenia i prawa do całego ekranu.
Struktura DrawSurfaceDesc typu DDSURFACEDESC deklarowana'jest w linii 50. Definiujemy w niej
podstawowy bufor z pojedynczym buforem podrzędnym dla potrzeb podwójnego buforowania.
Następnie zadeklarowana struktura DrawSurfaceDesc wykorzy-


Interfejsy API i zestawy SDK 761
stywana jest w linii 59 w tworzącej powierzchnię funkcji CreateSurface (). Wskaźnik do dodatkowej
powierzchni zdobywamy w linii 65 za pomocą funkcji GetAttachedSurface ().
Na koniec w linii 68 za pomocą funkcji SetTimer () definiujemy licznik czasu (ang. timer)
uaktywniający co 50 milisekund funkcję rysująca, której kod znaleźć można w funkcji obsługi OnTimer
() w linii 73. Funkcję obsługi OnTime r () komunikatu WM_TIMER dodajemy za pomocą kreatora
CIassWizard. Dzięki temu będziemy mieć pewność, że do nagłówka klasy i mapy komunikatów dodane
zostaną odpowiednie linie kodu.
Po zdefiniowaniu kilku domyślnych wartości przyzywana w linii 86 funkcja Bit () szybko oczyszcza
bufor tła, tworząc czarne tło i usuwając poprzednie wykonane na powierzchni rysunki.
W linii 92 przyzywamy funkcję GetDC (), aby zdobyć identyfikator obsługi kontekstu urządzenia
dla pomocniczej powierzchni. Identyfikator ten jest następnie w linii 97 przypisywany klasie CDC. W
liniach 99-126 za pomocą zwykłych odwołań do funkcji GDI i obiektu CPen rysowana jest bardzo
szybko jedna klatka animacji. Kontekst urządzenia jest uwalniany w linii 130, a następnie bufory są
zamieniane miejscami za pomocą funkcji Flip (), aby wyświetlić nowo narysowaną klatkę animacji.
Funkcji przesyłamy znacznik DDFLIP_WAIT polecający jej, aby czekała z wykonaniem operacji do
przerwy pomiędzy kolejnymi wyświetlanymi przez monitor klatkami obrazu.
Przerwa między wyświetlaniem kolejnych klatek obrazu
Kiedy klasyczny (oparty na lampie katodowej) monitor wyświetla obraz przesyłany mu przez kartę
graficzną, strumień elektronów wysyłany przez katodę przelatuje ekran linia po Unii, zaczynając od
górnego lewego rogu. Elektron uderza w powierzchnię kineskopu powodując świecenie punktu na
powierzchni kineskopu. Kiedy strumień elektronów osiągnie prawy dolny róg ekranu promień wodzący
kineskopu musi powrócić do prawego górnego rogu, nie podświetlając w trakcie tej operacji punktów
na powierzchni kineskopu. W tym momencie strumień elektronów jest na krótką chwilę, pomiędzy
kolejnymi klatkami obrazu wyświetlanymi przez monitor wyłączany. Karta graficzna w tym momencie
przesyła znacznik informujący, że strumień elektronów został wyłączony. Jest to najlepszy moment,
aby zamienić informacje zapisane w pamięci, ponieważ monitor nie wyświetla w tym momencie
niczego.
Funkcja destruktora z linii 16-25 odtwarza normalny pulpit Windows zmieniając poziom
współdziałania i przyzywając funkcję RestoreDisplayMode (), która przywraca normalny tryb
wyświetlania. Interfejsy są uwalniane za pomocą funkcji Re l ase (), a biblioteka COM jest
automatycznie odłączana.
Jeśli wprowadzimy przedstawione tu zmiany do szkieletu aplikacji opartego na oknie dialogowym,
będziemy mogli zbudować aplikację bez konieczności dodawania żadnych dodatkowych bibliotek API,
korzystając tylko z możliwości, które oferują nam obiekty


762_____________________________________Poznaj Visual C++ 6
COM. Jeśli teraz uruchomimy aplikację, będziemy mogli podziwiać szybką i płynną animację oferowaną
przez technikę DirectX.
Aplikację możemy wyłączyć w dowolnym momencie wciskając klawisz Esc (będący skrótem do
ciągle aktywnego, mimo iż okno aplikacji jest zamknięte, przycisku Cancel). Wciśnięcie klawisza Esc w
łagodny sposób przywróci pulpit Windows.
PATRZ TAKŻE
^ O technikach COM i OLE pisaliśmy w rozdziale 25.
Aby dowiedzieć się więcej na temat kontekstu urządzenia i rysowania za pomocą obiektów i funkcji
GDI należy zajrzeć do rozdziału 15.
Direct3D
Za pomocą interfejsu API Direct3D możemy umożliwić karcie graficznej obsługę grafiki
trójwymiarowej. Wspólnie z obiektem DirectDraw interfejs ten oferuje płynną i szybką grafikę
trójwymiarową, wykorzystywaną w grach komputerowych i aplikacjach wirtualnej rzeczywistości.
Nowe karty graficzne wspomagające grafikę trójwymiarową
Ostatnimi czasy pojawiło się na rynku wiele kart graficznych przejmujących część operacji grafiki
trójwymiarowej. Specjalistyczne układy scalone karty graficznej pozwalają wykonywać część
skomplikowanych obliczeń związanych z grafiką trójwymiarową znacznie szybciej niż programy
graficzne. Dodatkowo uwalniana jest w ten sposób także część mocy obliczeniowej procesora, co daje
programistom tworzącym gry i programy CAD możliwość zwiększenia ich efektywności.
Interfejs Direct3D korzysta z zestawu rysującego złożonego z trzech modułów, które obsługują
macierzowe transformacje współrzędnych, efekty oświetlenia dla światła punktowego i oświetlenia
biernego. Ostatni moduł odpowiedzialny jest za rastrowanie. Za pomocą tych modułów tworzona jest
wynikowa scena.
Niektóre z interfejsów Direct3D oferują pełny zestaw funkcji umożliwiających konstruowanie i
renderowanie trójwymiarowych widoków, które przypisać można do dwóch kategorii: trybu
natychmiastowego (ang. immediate-mode) i trybu opóźnionego (ang. retained-mode).
Obiekty trybu natychmiastowego są niskopoziomowymi i szybko działającymi obiektami rysowania
w trzech wymiarach (ich interfejsy zostały przedstawione w tabeli 28.2). Na bazie obiektów
natychmiastowych zbudowane są liczne obiekty trybu opóźnionego, umożliwiające tworzenie
skomplikowanych wysokiej jakości animacji trójwymiarowych.


Interfejsy API i zestawy SDK 763
Interfejs Direct3D Jego funkcje





IDirect3D
IDirect3DDevice
IDirect3DExecuteBuffer
IDirect3DLight
IDirect3DMaterial
IDirect3DTexture
IDirect3DViewport
Inicjuje środowisko graficzne i tworzy obiekty Light, Materiał i
Viewport.
Zarządza konfiguracją umożliwiającą sprzętowi i środowisku
uruchomienie możliwości grafiki trójwymiarowej oferowanych przez
sprzęt. Interfejs ten możemy utworzyć z powierzchni
DirectDraw za pomocą funkcji Querylnterface ()
Wykonuje bufory i przechowuje informacje o wierzchołkach i
instrukcje renderowania dla elementów grafiki trójwymiarowej
gotowych do narysowania
Konfiguruje dane oświetlenia dla każdego ze świateł określonej
sceny
Obsługuje właściwości materiału - stopień przezroczystości,
odbijanie światła
Obsługuje nakładanie na powierzchnie trójwymiarowe mapy bitowej
tekstury.
Grupuje efekty oświetlenia tekstury i tła w określonym elemencie,
który będzie wyświetlony na ekranie. Kilka takich powierzchni
ukośnych jest następnie łączonych razem i z informacjami o
wierzchołkach, tak aby można je było wyświetlić na ekranie






DirectPlay
Interfejs API DirectPlay umożliwia łatwe konstruowanie gier komputerowych umożliwiających
kilku graczom wspólną grę przez sieć bez konieczności programowania osobnej obsługi połączeń dla
różnego rodzaju połączeń sieciowych. Dwa przynależne tutaj interfejsy lDirectplay2 i iDirectPlayLobby
zarządzają wszystkimi aspektami odpowiednio obiektów gry trybu multiplayer i połączeniami między
komputerami.
Jakie połączenia sieciowe obsługuje DirectPlay?
Interfejs API DirectPlay obsługuje grę w komputerach połączonych kablem szeregowym, połączenia
TCP/IP sieci lokalnych, połączenia przez modem, Netware i wiele wiele innych połączeń sieciowych.
Directlnput
Interfejs API Directlnput pozwala na szybszy dostęp do danych wprowadzanych za pomocą myszy i
klawiatury niż za pośrednictwem standardowych interfejsów API Win-


764___________________________________Poznaj Visual C++ 6
dows. Obsługuje również pobieranie danych od różnego rodzaju joysticków dostarczając
funkcji pozwalających na ich kalibrację. Ten interfejs API jest stosunkowo prosty, oferuje przy
tym znaczne możliwości. Składa się z dwóch interfejsów COM. Pierwszy z nich, interfejs
IDirectInput, zarządza zainstalowanymi w systemie urządzeniami umożliwiającymi
wprowadzanie danych i ich statusem. Umożliwia również tworzenie egzemplarzy interfejsów
IDirectInputDevices obsługujących konfigurację i komunikację z konkretnymi urządzeniami.
DirectSetup
Interfejs API DirectSetup jest prawdopodobnie jednym z najmniejszych interfejsów API. Posiada
tylko dwie funkcje: DirectXSetup (), która automatyzuje instalację i konfi-gurowanie wszystkich
komponentów DirectX i funkcję DirectXRegisterApplica-tion (), która rejestruje grę jako obiekt
DirectPlayLobby, umożliwiający granie w trybie multiplayer.
Tworzenie wiadomości i programowanie obsługi poczty za po-
mocą MAPI
Interfejs Microsoft Messaging API (MAPI) tak naprawdę nie jest interfejsem API, tylko
zestawem standardów tworzących architekturę. Posługując się tymi sztywnymi i roz-
budowanymi zasadami twórcy oprogramowania nie muszą tworzyć od razu całego opro-
gramowania związanego z obsługą poczty, ale mogą ograniczyć się do programowania
wybranych fragmentów systemu pocztowego.
Część twórców oprogramowania będzie więc tworzyć własne aplikacje klientów
(odczytujące i zapisujące pocztę), podczas gdy inni będą tworzyć obiekty oferujące usługi w
zakresie transportowania i przechowywania wiadomości lub przechowujące listę adresów w
książce adresowej. Każdy z tych komponentów można zaprogramować osobno, a użytkownik
może łączyć je ze sobą na różne sposoby wybierając narzędzia do przesyłania poczty, które
mu najbardziej odpowiadają. Ta plastyczność systemów pocztowych jest często niezbędna w
dużych firmach, które muszą zintegrować ze sobą nowsze i starsze, często bardzo różne
systemy pocztowe.
Pisząc aplikację MAPI możemy wybierać między dwoma wersjami MAPI, w zależności
od tego jak bardzo skomplikowanych narzędzi do wysyłania poczty potrzebujemy. Prostsza
wersja MAPI (Simple MAPI) zawiera minimalny zestaw funkcji niezbędny do rejestrowania i
utworzenia sesji MAPI, odczytywania i pisania wiadomości oraz rozpatrywania (odnajdywania
najbliższego podobnego adresu) adresów w książce adresowej. Jeśli aplikacja, którą tworzymy
potrzebuje tylko tych podstawowych funkcji programu pocztowego, lepiej korzystać z
prostszej wersji MAPI. Jakiekolwiek dodatkowe funkcje będą wymagały wykorzystania
Extended MAPI (rozszerzonego MAPI) implementowanego w pliku mapi28.dll, który
dostarczy nam potrzebnych komponentów. Niestety, wersja Extended MAPI jest z
konieczności bardzo duża i skomplikowana. Aby z nią pracować,


Interfejsy API i zestawy SDK________________________________765
trzeba dobrze znać zasady programowania COM, ponieważ wersja ta jest implementowana w większej
części przez obiektową hierarchię interfejsów i obiektów COM.
Extended MAPI SDK i przykładowy program MDBView
Jeśli naprawdę potrzebne nam są możliwości Extended MAPI, musimy być przygotowani na ciężką
pracę i długą naukę. Będziemy musieli również ściągnąć z sieci MAPI SDK. Ten zestaw SDK zawiera
bardzo pouczający przykładowy program MDBVIEW.exe, wielce pomocny przy próbie zrozumienia
skomplikowanych zagadnień programowania związanych z rozszerzonym MAPI.
PATRZ TAKŻE
Na temat OLE i COM pisaliśmy w rozdziale 25.
Korzystanie z prostszej wersji MAPI
Prostsza wersja MAPI implementowana jest w pliku mapi28.dll, zaś prototypy funkcji i wartości
odpowiednich znaczników definiowane są w pliku nagłówka mapi.h. Nasza prosta aplikacja MAPI może
zarejestrować się u dostawcy wiadomości (ang. message provider) definiując profil (ang. profile) MAPI
lub akceptując profil domyślny, co pozwoli jej rozpocząć sesję pocztową. Profile MAPI możemy
skonfigurować za pomocą apletu Maił and Fax w Panelu sterowania (rysunek 28.2). Każdy z profili
definiuje szczegółowe informacje na temat przesyłania, wiadomości i programów dostarczających
książkę adresową, z których program klient będzie korzystał w momencie zarejestrowania, takich jak
Microsoft Exchange czy Lotus Notes. Kiedy już się zarejestrujemy, będziemy mogli wysyłać i odbierać
wiadomości i wyrejestrować się po zakończeniu.
Aplet Maił and Fax
Aplet Maił and Fax w Panelu sterowania może wyglądać inaczej lub mieć inny tytuł niż ten, który
przedstawiamy na rysunku, w zależności od aplikacji pocztowej MAPI, którą zainstalujemy na
komputerze.


766 Poznaj Visual C++ 6
s

ervices Oelivery] Addresshg
'heioltowinginformation sernice s arę setup in
thi$ pro

file;


PersonalA
ddi
Fersonal
Fold
Add...

ess
Boak
ers
Remoye

Pro partie
s



1 ^


Copy...



About..








JahowProftle
s,

l
















OK



Caneel





Hełp

Rysunek 28.2. Aplet Maił and Fax w Panelu sterowania w czasie konfigurowania profilu MAPI
Podstawowe funkcje MAPI przedstawione zostały w tabeli 28.3. Funkcje te można przywoływać w
aplikacji, albo bezpośrednio łącząc się z biblioteką mapi28.1ib, która pomoże połączyć się z biblioteką
mapi28.dll, albo korzystając z funkcji LoadLibrary () i GetProc-Address () pozwalających dynamicznie
ładować i odłączać bibliotekę DLL.
Tabela 28.3. Podstawowe funkcje MAPI
Funkcja Opis





MAPILogonO MAPILogoffO
MAPISendMail()
MAPISendDocuments()
MAPIFindNext()
MAPIReadMail()
MAPISaveMail()
MAPIDeleteMaiK)
MAPIFreeBufferf)
MAPIAddress()
MAPIDetails()
MAPIResolveName()
Rejestruje sesję MAPI przez konfigurację profilu
Wyrejestrowuje z sesji MAPI
Wysyła wiadomość ze struktury MapiMessage
Wysyła zestaw plików dokumentów po sprecyzowaniu ich nazw
Znajduje pierwszą lub następną wiadomość zwracając jej identyfikator
Wczytuje różne aspekty wiadomości do struktury MapiMessage
Zachowuje nową wiadomość w wewnętrznej skrzynce dostarczanej przez
dostawcę
Usuwa wiadomość zdefiniowaną przez ostatnią funkcję MAPIFindNext ()
Zwalnia bufory wykorzystywane przez MAPI
Wyświetla okno dialogowe z listą adresów umożliwiając użytkownikowi
modyfikację zawartości
Pokazuje informacje związane z określonym adresem Zmienia
niezrozumiałą nazwę odbiorcy na adres pocztowy



f/MKŁ IAIV.t
Więcej informacji na temat OLE i COM znaleźć można w rozdziale 25.
Dodawanie programu pocztowego MAPI za pomocą kreatora AppWizard
Na stronie czwartej kreatora AppWizard (rysunek 28.3) w sekcji What features would
like to include? (Jakie dodatkowe możliwości chcesz dołączyć do aplikacji?) znajduje się
opcja MAPI (Messaging API) pozwalająca dodać do aplikacji SDI lub MDI możliwości
interfejsu MAPI. Opcja ta dodaje do menu File polecenie Send, a do klasy dokumentu
następujące hasła mapy komunikatów:
ON_COMMAND(ID_FILE_SEND_MAIL, OnFileSendMail)
ON_UPDATE_COMMAND_UI(ID_FILE_SEND_MAIL,
OnUpdateFileSendMail)
MFC AppWizald - Step 4 ol G


IK Chck Baz ( M.
; r n.
What fealur&s would you like lo include?
| 17 iDockingjcoibal! l F Initia! status bar s
^ Pnff^a aftd ptint pfeview ; F' Conteiit-
tereitiye Hglp '. P 30 controls ^ F MAP!
(Messaging API) l F Windows Sockets How
do you waht your toolbars lo look?
y Hennal ^ Inteinet E^plofet
ReBafS
How many f ileś would yw like on your lecent f ile list?
R:ri Advanced... |





|~ Cancel
Rysunek 28.3. Dodawanie funkcji interfejsu MAPI do aplikacji SDI za pomocą kreatora AppWizard
Jeśli zbudujemy i uruchomimy aplikację już w tym momencie, będzie ona zaopatrzona we wszystkie
funkcje niezbędne do wysyłania poczty. Możemy kliknąć polecenie Send w menu File, aby przyzwać
okno rejestracji MAPI. Po wybraniu profilu (lub skonfigurowaniu systemu pocztowego) pakiet pocztowy
zostanie uruchomiony i będziemy mogli utworzyć i wysłać wiadomość e-mail. Szkielet aplikacji
automatycznie serializuje bieżący dokument za pomocą funkcji OnSaveDocument () i dołącza go do
nowego e-maila.
Jeśli potrzebujemy bardziej rozbudowanych funkcji aplikacji pocztowej, musimy skorzystać z
podstawowych funkcji MAPI albo sięgnąć do Extended MAPI.
W kodzie z listingu 28.3. pokazujemy, jak za pomocą podstawowych funkcji MAPI
implementowanych w aplikacji SDI bazującej na widoku Edit utworzyć klienta pocztowego, który
będzie umożliwiał wysyłanie wiadomości za pośrednictwem polecenia Send


768_____________________________________Poznaj Visual C++ 6
menu File i odbieranie ich za pomocą polecenia Receive menu File. Odebrane wiadomości są następnie
wyświetlane w widoku Edit.
Przykładowy program ładuje plik biblioteki mapi28.dll dynamicznie w konstruktorze dokumentu,
rejestruje nową sesję MAPI, a po wszystkim kończy sesję i zwalnia bibliotekę DLL w destruktorze.
Najpierw tworzymy zwykły szkielet aplikacji SDI za pomocą kreatora AppWizard, włączając opcję,
która umożliwia korzystanie z funkcji MAPI, w celu dodania do aplikacji polecenia Send. Na ostatniej
stronie kreatora AppWizard wybieramy widok Edit jako widok bazowy. Za pomocą edytora zasobów
dodajemy do menu File polecenie Receive. Przedstawiony niżej kod jest w większości dodawany do
funkcji obsługi nowych poleceń w klasie dokumentu.
Listing 28.3. LST32_3.CPP - wykorzystanie prostych funkcji MAPI do odczytywania wiadomości w
aplikacji SDI korzystającej z MAPI
1 łinclude
2
3 HMODULE g_hMAPI;
4 LHAŃDLE g_hSession;
5
6 LPMAPILOGON g_lpfnLogon;
7 LPMAPILOGOFF g_lpfnLogoff;
8 LPMAPIFINDNEXT g_ipfnFindNext;
9 LPMAPIREADMAIL g_lpfnReadMail;
10 LPMAPIFREEBUFFER g_lpfnFreeBuffer;
11
12 CMailClientDoc::CMailClientDoc()
13 {
14 // ** Dynamicznie załaduj bibliotekę DLL i odpowiednie funkcje
15 g_hMAPI = LoadLibraryf"MAPI28.DLL");
16 g_lpfnLogon = (LPMAPILOGON)
17 GetProcAddress(g_hMAPI,"MAPILogon"); O
18 g_lpfnLogoff = (LPMAPILOGOFF)
19 GetProcAddress(g_hMAPI,"MAPILogoff") ;
20 g_lpfnFindNext = (LPMAPIFINDNEXT)
21 GetProcAddress(g_hMAPI,"MAPIFindNext") ;
22 g_lpfnReadMail = (LPMAPIREADMAIL)
23 GetProcAddress(g_hMAPI,"MAPIReadMail") ;
24 g_lpfnFreeBuffer= (LPMAPIFREEBUFFER)
25 GetProcAddress(g_hMAPI,"MAPIFreeBuff er");
26
27 (*g_lpfnLogon)(O,NULL,NULL, @
28 MAPI_NEW_SESSION | MAPI_LOGON_UI,O,&g_hSession) ;
29 }


CMailClientDoc::~CMailClientDoc() (
// ** Wyrejestruj się i zakończ sesję
(*g_lpfnLogoff)(g_hSession,0,0,0) ;
FreeLibrary(g_hMAPI) ;
}
void CMailClientDoc::OnFileReceive()
{
// ** Znajdź widok POSITION pos =
GetFirstViewPosition();
CView* pView = GetNextView(pos) ;
// ** Zadeklaruj Identyfikatory wiadomości static
char szSeedMessage[512]; . static char
szMessage[512];
static LPSTR pSeed = NULL;
static LPSTR pMsg = szMessage;
IpMapiMessage IpMessage = NULL;
// ** Znajdź następna, wiadomość
ULONG uIResult = (*g_lpfnFindNext)(g_hSession,
(ULONG)pView->m_hWnd,NULL,pSeed, MAPI_LONG_MSGID, OL,
pMsg) ;
// ** Odczytaj następna, wiadomość uIResult =
(*g_lpfnReadMail)(g_h3ession, (ULONG)pView-
>m_hWnd,pMsg,MAPI_PEEK, OL,SipMessage)
CString strMessage;
CString strFmt;
if (uIResult == OL) (
// ** Zachowaj elementy wiadomości
strFmt.Format("From: %s\r\n",
!pMessage->lpOriginator->lpszName) ;
strMessage += strFmt;
strFmt.Format("To: %s\r\n",
!pMessage->lpRecips[0].IpszName); , strMessage
+= strFmt;
strFmt.Format("Subject: %s\r\n",
lpMessage->lpsz3ubject);


770 Poznaj Visual C++ 6
74 strMessage += strFmt;
75 strMessage += !pMessage->lpszNoteText;
76 pSeed = szSeedMessage;
77 stropy(p3eed,pMsg);
78
79 // ** Przypisz wiadomość widokowi Edit
80 pView->SetWindowText(strMessage) ;
81
82 // ** Zwolnij bufor
83 (*g_lpfnFreeBuffer)( (LPVOID)IpMessage) ;
84 }
85 }
O Funkcje biblioteki DLL odnajdujemy ręcznie za pomocą funkcji GetProcAdress () @ Program
rejestruje MAPI korzystając z domyślnego profilu
Funkcja FindNext() odnajduje następną wiadomość w oparciu o wartość przesłaną przez wiadomość
poprzednią
O Funkcja ReadMail () wczytuje zawartość wiadomości do bufora MapiMessage
W pierwszej linii listingu 28.3 załączamy nagłówek mapi.h, który przechowuje prototypy funkcji i
używane znaczniki. Konstruktor dokumentu (linie 12-29) ładuje w linii 15 za pomocą funkcji
LoadLibraryO plik rnapi28.dll. Następnie za pomocą funkcji Get-ProcAddress () odnajduje adresy
funkcji biblioteki DLL zachowując je w globalnych wskaźnikach deklarowanych w liniach 6-10. Na
koniec w linii 27 rejestruje nową sesję MAPI zachowując identyfikator sesji w zmiennej g_h3ession.
Sprawdzanie wartości zwracanych przez funkcję GetProcAddress ()
Po udanym załadowaniu biblioteki DLL możemy odnajdywać jej funkcje za pomocą funkcji
GetProcAddress (). Funkcja ta zwraca adres funkcji definiowanej jako łańcuch, pod którym występuje
w bibliotece DLL. Identyfikator biblioteki DLL również należy przesłać funkcji GetProcAddress ().
Jeśli funkcja GetProcAddress () odnajdzie właściwy adres, zostanie on zapisany we wskaźniku funkcji
ipfnLogon. Jeśli nie, wskaźnikowi funkcji przypisana zostanie wartość NULL. Należy zawsze
sprawdzać, czy funkcji GetProcAddress () udało się zdobyć odpowiedni wskaźnik funkcji. W
przeciwnym wypadku program zawiesi się, gdy wyślemy go do zerowego adresu pamięci!


Interfejsy API i zestawy SDK 771
Funkcję obsługi polecenia menu OnFileReceive () dostarczającą aplikacji kod umożliwiający
odczytywanie wiadomości należy dodać do dokumentu za pomocą kreatora CIassWizard. W liniach 41 i
42 odnajdujemy za jej pomocą widok Edit, którego identyfikator obsługi okna jest w linii 53
wykorzystywany w funkcji FindNext () do odnajdywania kolejnej oczekującej wiadomości. Bufor
szSeedMessage dostarcza funkcji FindNext () informacji, dzięki której może ona odnaleźć następną
wiadomość lub pierwszą, o ile wskaźnik pSeed ma wartość NULL.
W linii 58 odczytywana jest zawartość wiadomości identyfikowanej przez wskaźnik IpMessage za
pomocą odwołania do funkcji ReadMail (). Jeśli wiadomość uda się odczytać, zmiennej uIResult
przypisywana jest wartość zero, a wskaźnikowi IpMessage struktura MapiMessage. Szczegółowe
informacje na temat wiadomości będzie można następnie wydobywać ze struktury w oparciu o wskaźnik
IpMessage (linie 65-75).
Kiedy już sformatowany łańcuch strMessage, zostanie zbudowany z zapisanych w strukturze
części możemy przypisać wiadomość widokowi korzystając z funkcji set-windowText () (linia 80).
Następnie w linii 83 oczyszczamy strukturę za pomocą funkcji MAPiFreeBuffer () przyzywanej za
pośrednictwem wskaźnika (zdefiniowanego w linii 24)
g_lpfnFreeBuffer.
W liniach 76-77 kopiujemy bieżącą wiadomość do bufora szSeedMessage, przygotowując się do
kolejnego wezwania funkcji FindNext (), która pobierze następną wiadomość, gdy tylko użytkownik
kliknie w menu polecenie Receive.
Po zamknięciu dokumentu sesja pocztowa zostaje zakończona w linii 34, a biblioteka .dli jest w linii
35 odłączana za pomocą funkcji FreeLibrary ().
Po zbudowaniu i uruchomieniu aplikacji będziemy mogli, tak jak to zostało pokazane na rysunku
28.4, wysyłać i odbierać pocztę za pomocą poleceń Send i Receive z menu File. Jeśli wiadomości do
odebrania jest więcej, należy po prostu kolejny raz kuknąć Receive.
Gdy będziemy uruchamiać aplikację po raz pierwszy, wyświetlone zostanie okno dialogowe
rejestracji profilu pokazujące nasz domyślny profil pocztowy. Kliknięcie OK zatwierdzi profil
przedstawiony w oknie.


File Edit "D j
Cg| B j
Yiew hełp






^om: Microsoft
To: New Windows 95 Customer
Subject Welcome!
Welcome to Microsoft Exchange!
Welcome to Microsoft Exchange and the worid ot e-mail.
E-mail provides afast and efficientway of communicating
with others. There arę many different kinds of e-mail
systems. You can use Microsoft Exchange with these
systems as a universal lnboxto:
Rysunek 28.4. Odczytywanie wiadomości e-mail w widoku Edit aplikacji SDI za pomocą funkcji Simple
MAPI


772_____________________________________Poznaj Visual C++6
Biblioteki multimedialne i interfejs MCI
Pojawienie się oprogramowania multimedialnego zmieniło pokutujący do niedawna
wizerunek komputera PC jako topornej maszyny biurowej. Obecnie na komputerze PC można
odgrywać płyty kompaktowe, oglądać telewizję, uruchamiać efekty dźwiękowe i animacje.
Dzięki bibliotekom multimedialnym i interfejsowi MCI (od ang. Media Con-trol Interface)
możemy bez większych problemów dołączyć wszystkie te możliwości do naszej aplikacji.
Interfejs MCI i związana z nim klasa obsługująca interfejs użytkownika MCIWnd pozwalają
w prosty i jednolity sposób kontrolować nawet bardzo skomplikowany sprzęt i
oprogramowanie multimedialne.
Interfejs multimedialny MCI
Interfejs MCI dostarcza dwóch systemów przesyłania poleceń (tekstowego i bazującego na
strukturach) pozwalających kontrolować z poziomu aplikacji najróżniejsze urządzenia
multimedialne.
Oparty na tekście system łańcuchów poleceń (ang. Command Strings) pozwala łączyć ze
sobą w łańcuchu polecenia sterujące, które są następnie przesyłane do interfejsu MCI za
pomocą funkcji mciSendStringO. Tekstowy system definiowania poleceń jest prosty, łatwy w
użyciu i co ważne przyjazny, gdy usuwamy błędy programu w procesie debugo-
wania.
Polecenia MCI
Polecenia MCI są zaprojektowane w taki sposób, żeby były maksymalnie ogólne. Interfejs oferuje
zestaw bazowych poleceń, które można stosować na każdym urządzeniu. Te bazowe polecenia
umożliwiają otwieranie, zamykanie, odczytywanie statusu i możliwości urządzenia, ładowanie danych,
zachowywanie danych, odtwarzanie zapisu, zatrzymywanie odtwarzania i nagrywania, a także
odszukiwanie określonego momentu odtwarzanego zapisu dla dowolnego urządzenia medialnego.
Możemy również definiować polecenia za pomocą zwykłych znaczników i struktur języka
C++ w systemie komunikatów poleceń (ang. Command Messages), który wykorzystuje do
przesyłania komunikatów do interfejsu MCI funkcję mciSendCommandO. Ten system jest
odrobinę szybszy, ponieważ nie wymaga budowania łańcuchów. Jednak oba systemy są
podobne i projektując aplikację możemy wybrać ten, który będzie wydawał nam się
wygodniejszy.
Pierwsze polecenie, które należy wysłać do interfejsu MCI, to polecenie otwarcia
konkretnego urządzenia. Możemy to zrobić albo za pomocą funkcji mciSendStringO i łańcucha
open, albo za pomocą funkcji mciSendCommandO i znacznika MCI_OPEN. Możemy otworzyć
dowolne urządzenie należące do jednego z typów przedstawionych w tabeli 28.4, o ile
oczywiście jest zainstalowane w naszym systemie. Niektóre z urządzeń


Interfejsy API i zestawy SDK 773
będą wymagały podłączenia specjalistycznego sprzętu, podczas gdy do innych, takich jak waveaudio czy
digitalvideo, w systemie Windows zostało już standardowo zainstalowane odpowiednie oprogramowanie.
Tabela 28.4. Typy urządzeń w interfejsie MCI
Łańcuch Znacznik Opis urządzenia





waveaudio MCI_DEVTYPE_WAVEFORM_AUDIO
sequencer MCI_DEVTYPE_SEQUENCER
cdaudio MCI_DEVTYPE_CD_AUDIO
dat MCI_DEVTYPE_DAT
digitalvideo MCI_DEVTYPE_DIGITAL_VIDEO
vcr MCI_DEVTYPE_VCR
videodisc MCI_DEVTYPE_VIDEODISC
mmmovie MCI_DEVTYPE_ANIMATION
overlay MCI_DEVTYPE_OVERLAY
scanner MCI_DEVTYPE_SCANNER
other MCI DEVTYPE OTHER
Nagrywa/odtwarza pliki dźwiękowe .wav
Sekwenser MIDI Odtwarza płyty
kompaktowe
Odtwarza/zapisuje cyfrowe taśmy
dźwiękowe
Odgrywa cyfrowe pliki filmowe .avi
Odtwarza/nagrywa kasety VCR Odtwarza
wideodyski Odtwarza animacje
Odtwarza w oknie analogowy zapis
wideo
Skanuje rysunki Urządzenia
niezdefiniowane






Tak jak można się spodziewać, polecenie open wymaga paru dodatkowych parametrów, dlatego
funkcji mciSendStringO należy po poleceniu open przesłać trzy (oddzielone spacjami) parametry
przedstawione poniżej:
open
Jako parametr nazwa urządzenia można przesłać dowolną nazwę podaną w tabeli 28.4. Parametr
znaczniki otwarcia może definiować zastępczą nazwę (alias identyfikujący później określony egzemplarz
otwartego urządzenia) i różne specyficzne dla urządzenia opcje. Parametrowi znaczniki powiadamiające
można przypisać albo wartość wait informującą, że polecenie powinno czekać na otwarcie urządzenia,
albo notify polecające kontynuować wykonanie programu i powiadamiające go, kiedy urządzenie
zostanie otwarte.
Możemy również polecić funkcji mciSendStringO zwracać komunikat o statusie przesyłając jej
bufor zwrotny (ang. retum buffer) wraz z jego rozmiarami. Jeśli chcemy otrzymywać komunikaty
powiadamiające, możemy również przesłać identyfikator obsługi okna, które ma je otrzymywać.


774 Poznaj Visual C++ 6
Nazwy urządzeń MCI
Interfejs MCI daje się swobodnie rozbudowywać. Nowe urządzenia MCI można do niego
dodawać rejestrując odpowiednie sterowniki urządzeń. Odpowiednie hasła mówiące na
temat rejestracji urządzeń multimedialnych w Windows 95 można znaleźć w pliku
C:\Windows\System.ini w sekcji [mci]. W Windows NT z kolei odpowiednie informacje
znajdziemy w folderze HKEY_LOCAL_MACHINE\SOFT-
WARE\Microsoft\WindowsNT\CurrentVersion\#SYS...\MCI.
Przykładowo, za pomocą przedstawionej niżej funkcji mciSendStringO możemy przesłać
interfejsowi MCI łańcuch poleceń polecający mu otworzyć odtwarzacz płyt kompaktowych i w dalszej
części programu opisywać go jako mycd:
char szRetMsg[80] ;
mciSendString("open cdaudio alias mycd wait",
szRetMsg,sizeof(szRetMsg),m hWnd) ;
Możliwości urządzeń MCI
Możemy sprawdzić, czy dane urządzenie MCI oferuje konkretne możliwości (funkcje) korzystając z
łańcucha capabilities. Przykładowo, aby sprawdzić, czy czytnik CD oferuje możliwości nagrywania,
należy wpisać łańcuch Capabilities cdaudio can record i sprawdzić kod zwrotny informujący, czy tak
jest w istocie. Odpowiedni znacznik polecenia to MCI_GETDEVCAPS.
Przedstawione tu polecenie zaczeka aż odtwarzacz CD zostanie prawidłowo otwarty, jeśli wszystko
pójdzie dobrze zwracając w buforze szRetMsg wartość "l". Jeśli odtwarzacz nie zostanie otwarty, w
buforze zostanie zwrócona wartość "Q". Jeśli urządzenia nie uda się otworzyć, zwracana jest struktura
MCIERROR, którą można przekształcić w łańcuch komunikatu za pomocą funkcji mciGetErrorString ().
Ta sama operacja otwarcia urządzenia za pomocą funkcji mciSendCommand () wymaga przesłania
czterech parametrów:
identyfikatora urządzenia (nie wykorzystywanego przy otwieraniu)
znacznika polecenia (MCI_OPEN)
znaczników powiadamiających: albo MCI_WAIT, albo MCI_NOTIFY
wskaźnika do struktury MCI_OPEN_PARAMS
Funkcja zwróci następnie, podobnie jak to robiła funkcja mciSendString (), strukturę MCIERROR.
Aby otworzyć odtwarzacz CD za pomocą funkcji mcisendCominandO, należy przesłać następujący
komunikat polecenia:


Interfejsy API i zestawy SDK 775
MCI_OPEN_PARMS myCDOpen = {NULL,O,"cdaudio",NULL,NULL};
errOpen = mciSendCommand(NULL,MCI_OPEN,
MCI_OPEN_TYPE|MCI_WAIT,(DWORD)SmyCDOpen) ;
Funkcja mciSendCommand () wykorzystuje strukturę MCI_OPEN_PARAMS przesyłaną jako
wskaźnik myCDOpen. Struktura ta definiowana jest w następujący sposób:
typedef struct {
DWORD dwCallback;
MCIDEVICEID wDeviceID;
LPCSTR !pstrDeviceType;
LPCSTR IpstrElementName;
LPCSTR IpstrAlias;
} MCI_OPEN_PARMS;
Struktura MCI_OPEN_PARAMS pozwala przesłać w zmiennej dwCaliBack identyfikator obsługi okna,
któremu będą przesyłane komunikaty powiadamiające. Identyfikator otwartego urządzenia jest odsyłany
w zmiennej wDeviceID, którą będziemy mogli następnie wykorzystać w kolejnych odwołaniach do tegoż
urządzenia. Zmienna IpstrElementName służy do definiowania nazwy pliku, jeżeli otwieramy urządzenie
razem z powiązanym z nim plikiem dźwiękowym .wav lub filmowym .avi. Możemy zapisać odpowiedni
alias w zmiennej IpstrAlias, aczkolwiek ten krok nie jest niezbędny, jako że urządzenie możemy zawsze
zidentyfikować za pomocą zmiennej zawierającej identyfikator urządzenia wDeviceID.
Różnych struktur dla różnych komunikatów poleceń MC l_ jest dokładnie tyle, ile jest łańcuchów dla
łańcuchów poleceń.
Po otwarciu urządzenia możemy za pomocą następnych poleceń odgrywać, zapisywać, zamykać i
wykonywać wiele innych poleceń multimedialnych. Część z licznej rzeszy możliwych poleceń została
przedstawiona w tabeli 28.5. Każde z poleceń posiada wiele różnych parametrów, zdecydowanie zbyt
wiele, abyśmy mogli je tutaj opisywać.
Tabela 28.5. Niektóre polecenia MCI Komunikat
Łańcuch Opis polecenia





MCI_OPEN
MCI_CLOSE
MCI_PLAY
MCI_RECORD
MCI_STOP
MCIJ3EEK
MCI PAUSE
open
close
play
record
stop
seek
pause
Otwiera urządzenie
Zamyka urządzenie
Odtwarza w urządzeniu
Nagrywa w urządzeniu
Zatrzymuje odgrywanie lub nagrywanie
Odszukuje pozycję w materiale (filmie, utworze muzycznym)
Pauzuje odgrywanie lub nagrywanie


776 Poznaj Visual C++ 6
MCI_RESUME resume Uruchamia ponownie po pauzie
MCI_LOAD load Ładuje plik
MCI_SAVE save Zachowuje w pliku
MCI_STATUS status Pobiera informacje o statusie
MCI_WINDOW window Wyświetla okno wideo dla zapisu filmowego
MCI_WHERE where Definiuje pozycję okna
Komunikaty powiadamiające MCI
Jeśli w poleceniu MCI wykorzystamy znacznik wait lub MCI_WAIT, polecenie nie zwróci
aplikacji kontroli, dopóki nie zostanie wykonane do końca. Taka procedura wykonywania
poleceń jest bardzo praktyczna w niektórych sytuacjach, jednak jeśli przykładowo wydamy
aplikacji odtwarzającej płyty kompaktowe polecenie odtworzenia zawartości kompaktu,
aplikacja będzie czekać, dopóki polecenie odgrywania nie zostanie wykonane do końca. To
może oznaczać, że na kolejną możliwość kontaktu z aplikacją użytkownik będzie musiał
czekać prawie godzinę! Jeśli natomiast nie prześlemy żadnych znaczników powiadamiających
ani polecających poleceniu odczekanie aż zadanie zostanie wykonane, polecenie natychmiast
przekaże sterowanie z powrotem do aplikacji. Kolejnym rozwiązaniem jest przesłanie do
polecenia znacznika MCI_NOTIFY nakazującego mu poinformować aplikację, gdy wykona już
swoją funkcję. Aplikacja będzie mogła wtedy wyświetlić odpowiedni komunikat, odłączyć
płytę kompaktową lub wykonać inną niezbędną w tym momencie akcję. Większość poleceń
MCI może wysyłać komunikaty powiadamiające.
Funkcje mciSetYeldProc() imciGetYeldProc()
Jeśli polecimy poleceniu odczekiwać z przekazaniem kontroli aplikacji za pomocą znacznika wait,
możemy chcieć, aby interfejs MCI przyzywał co pewien czas funkcje zwrotne wykonujące określone
operacje lub testy. Możemy robić to za pomocą funkcji mciSetYieldProc (). Funkcja ta wymaga jako
pierwszego parametru identyfikatora urządzenia, jako drugiego parametru adresu funkcji zwrotnej, a
jako trzeciego parametru zmiennej typu DWORD przechowującej pewne definiowane przez użytkownika
dane, które będą przesyłane funkcji zwrotnej. Jeśli funkcja wykona zadanie, zwraca wartość TRUE.
Pokrewna jej funkcja mciGetyieldProc () służy do pobierania adresu innej działającej procedury tego
typu w zdefiniowanym urządzeniu.
Jeśli prześlemy do polecenia znacznik MCI_NOTIFY lub notify, polecenie zwróci aplikacji
kontrolę natychmiast i prześle komunikat powiadamiający do pętli komunikatów (ang.
message loop) okna, którego identyfikator obsługi przesłaliśmy definiując adresata
komunikatu. Tym oknem może być widok, okno dialogowe lub dowolny inny obiekt klasy
wywodzącej się z cwnd, który jest powiązany z działającym w programie oknem (możemy


Interfejsy API i zestawy SDK________________________________777
przesłać zmienną składową zawierającą identyfikator obsługi dowolnego legalnego obiektu CWnd).
Funkcję obsługi komunikatu MM_MCINOTIFY możemy dodać do aplikacji MFC wpisując do mapy
komunikatów Windows makroinstrukcję ON_MESSAGE:
BEGIN_MESSAGE_MAP(CMciDlg, CDialog)
ON_MESSAGE(MM_MCINOTIFY,OnMCINotify)
END_MESSAGE_MAP()
Teraz możemy dodać funkcję obsługi, której należy przesłać dwa parametry: pierwszy
identyfikujący urządzenie wysyłające komunikat powiadamiający i drugi definiujący kod statusu
zwracany przez polecenie. Funkcja ta powinna wyglądać mniej więcej tak:
void CMciDlg::OnMCINotify(WPARAM wFlags,LPARAM !DevID) {
AfxMessageBox ("Komunikat MCI: Wykonywanie polecenia zostało zakończone!") ;
}
Zestaw kodów wFlags informujących o statusie polecenia przedstawiliśmy w tabe 28.6. Zmienna l
De vi D identyfikuje urządzenie, które wysyła komunikat powiadamiając;
Jeśli korzystamy z łańcuchów poleceń i identyfikujemy urządzenia za pomocą aliasóv możemy zdobyć
prawdziwy identyfikator urządzenia przyzywając funkcję mciDetDevice (), której przesyłamy alias jako
parametr służący nam za nazwę urządzenia.
Tabela 28.6. Kody statusu urządzenia w komunikatach powiadamiających Kod
statusu polecenia Opis
MC I_NOT l FYJSUCCE s S FUL Wykonywane polecenie zakończyło się sukcesem MC I_NOT l
FY_FA l LURE w tracie wykonywania polecenia wystąpił błąd
MCI_NOTIFY_ABORTED Komunikat powiadamiający poprzedniego polecenia został usunięty przez
następne polecenie skierowane do tego samego urządzenia,
niekompatybilne z poprzednim
MCI_NOTIFY_SUPERESEDED Nowe polecenie zażądało komunikatu powiadamiającego od tego samego
urządzenia. Polecenia są kompatybilne i stare polecenie zostało
zastąpione przez nowe


778 _______________ Poznaj Visual C++ 6
Sprawdzanie kodów zwrotnych funkcji poleceń
Należy zawsze sprawdzać kod zwracany przez funkcję, która domaga się komunikatu
powiadamiającego. Jeśli funkcja polecenia zwróci kod informujący o tym, że nie udało jej się wykonać
zadania, komunikat powiadamiający nie zostanie wysłany. Może to doprowadzić do tego, że program
zawiesi się oczekując komunikatu powiadamiającego, który nigdy nie nadejdzie.
Na listingu 28.4 prezentujemy obie formy wysyłania poleceń Command String i Command
Message, które wykorzystywane są tam do odtworzenia pięciosekundowego fragmentu zapisu
dźwiękowego z kompaktu (pomiędzy 12 a 17 sekundą trzeciej ścieżki utworu). Przedstawiony tam
fragment kodu możemy dodać w dowolnym miejscu aplikacji MFC (w przypadku aplikacji opartej na
oknie dialogowym fragment ten najlepiej umieścić W funkcji InitDialog () ).
Aby załączyć do programu znaczniki i prototypy funkcji interfejsu MCI, należy wpisać w pliku
nagłówka następująca dyrektywę i n cłu de:
łinclude "mmsystem.h"
Aplikacje korzystające z bibliotek MCI powinny łączyć się z plikiem biblioteki winmm.lib. Plik ten
należy wpisać w liście Object/L;ibrary Modules na karcie Link okna dialogowego Project Settings.
Listing 28.4. LST32_4.CPP - fragment kodu opartego na poleceniach MCI odgrywający pięć sekund
muzyki z płyty kompaktowej
1 // Odegraj 5 sekund muzyki z płyty za pomocą łańcucha polecenia
2 char szRetMsg[80];
3 char szErrorMessage[512];
4 MCIERROR errOpen;
5 errOpen = mciSendString("open cdaudio alias mycd wait", O
6 szRetMsg,sizeof(szRetMsg),m hWnd);
7 if (szRetMsg[0]!='l')
8 {
9 // Wyświetl komunikat o błędzie podczas otwierania
10 mciGetErrorString(errOpen,szErrorMessage,512) ;
11 AfxMessageBox(szErrorMessage) ;
12 )
13 else
14 {
15 // Zdefiniuj format wyświetlania informacji o czasie
16 mciSendString("set mycd time format tmsf",
17 szRetMsg,sizeof(szRetMsg),m_hWnd);


Interfejsy API i zestawy SDK 779





18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// Odegraj 5 sekund ze ścieżki 3 poczynając od sekundy 12 mciSendString(
"play mycd from 3:0:12:0 to 3:0:17:0 wait",
szRetMsg,sizeof(szRetMsg),m_hWnd) ;
// ** Zamknij urządzenie mciSendString("close mycd",
szRetMsg,sizeof(szRetMsg),m hWnd) ;
// Odegraj 5 sekund muzyki z płyty za pomocą, komunikatu polecenia MCI_OPEN_PARMS
myCDOpen=(NULL,O,"cdaudio",NULL,NULL}; errOpen = mciSendCommand(NULL,MCI_OPEN,
MCI_OPEN_TYPE|MCI_WAIT,(DWORD)SmyCDOpen) ;
if (errOpen) {
// Wyświetl komunikat o błędzie podczas otwierania
mciGetErrorString(errOpen,szErrorMessage,512) ;
AfxMessageBox(szErrorMessage) ;
}
else
{
// Zdefiniuj format wyświetlania informacji o czasie
MCI_SET_PARMS setParams = (NULL,
MCI_FORMAT_TMSF,O} ;
errOpen=inciSendCommand (myCDOpen.wDeviceID,MCI SET,
MCI_SET_TIME_FORMAT,(DWORD)&setParams) ;
// Odegraj 5 sekund ze ścieżki 3 poczynając od sekundy 12 MCI_PLAY_PARMS playParams =
{NULL,
MCI_MAKE_TMSF(3, O, 12, 0),
MCI_MAKE_TMSF(3, O, 17, 0)};
mciSendCommand(myCDOpen.wDeviceID,MCI_PLAY,
MCI_FROM | MCI_TO l MCI_WAIT,
(DWORD)SplayParams) ;
// ** Zamknij urządzenie MCI_GENERIC_PARMS genParams = (NULL);
mciSendCommand(myCDOpen.wDeviceI D,MCI_CLOSE,
NULL,(DWORD)SgenParams) ;





O Łańcuch polecenia open definiuje alias urządzenia wykorzystywany przez następne polecenia.


780 Poznaj Visual C++ 6
@ Znacznik MCI_OPEN komunikatu polecenia odpowiada łańcuchowi open.
@ Wartości czasu są dla komunikatów poleceń definiowane za pomocą makroinstrukcji
MCI_MAKE_TMSF.
Na początku listingu 28.4 w linii 5 przyzywana jest funkcja mciSendString () otwierająca
odtwarzacz płyt kompaktowych i nadająca mu alias mycd. Jeśli urządzenia nie uda się otworzyć w liniach
10 i 11, wyświetlany jest odpowiedni komunikat o błędzie. Korzystamy w tym celu z pomocy funkcji
mciGetErrorString (), która przekształca kod błędu w dający się zrozumieć łańcuch tekstu.
W linii 16 definiujemy format wyświetlania czasu time format jako tmsf informując MCI, jak ma
traktować wysyłane później polecenia ustalające nową pozycję po s iti on w odtwarzanym utworze
(definiowaną w minutach i sekundach).
W linii 19 rozpoczynamy odgrywanie płyty kompaktowej za pomocą polecenia play i definiujemy
opcjonalne polecenia from i to pozwalające odegrać fragment utworu między 12 a 17 sekundą trzeciej
ścieżki utworu. Znacznik wait poleca funkcji zwrócić aplikacji kontrolę dopiero po odegraniu całego
fragmentu. W linii 23 zamykamy urządzenie.
Ten sam fragment ścieżki odgrywany jest za pomocą komunikatów poleceń. Urządzenie jest
otwierane za pomocą znacznika MCI_OPEN w linii 29. Format czasu definiowany jest za pomocą
komunikatu MCI_SET poprzez przesłanie znacznika MCI_SET_TIME_ FORMAT i przypisanie formatu tmsf
zmiennej składowej MCI_FORMAT_TMSF struktury setParams.
W linii 49 odgrywany jest ten sam co poprzednio fragment ścieżki za pomocą makroinstrukcji
MCI_MAKE_TMSF wykorzystywanej do definiowania momentu początkowego i momentu końcowego
odgrywanego fragmentu, które w tym celu zapisujemy w strukturze MCI_PLAY_PARAMS. Na koniec w linii
54 urządzenie jest zamykane za pomocą komunikatu
MCI_CLOSE.
Dodawanie do aplikacji okna MCI
Jak dotąd opisywaliśmy interfejs MCI od strony kodu programu, nie wspominając ani słowem o
interfejsie użytkownika. Istnieje jednak standardowy interfejs MCI implementowany w klasie MCiWnd.
Klasa MCiWnd oferuje wysoko wyspecjalizowaną kontrolkę pozwalającą użytkownikowi na pełną
kontrolę nad funkcjami wykorzystywanego urządzenia multimedialnego. Interfejs kontrolki różnić się
będzie znacznie w zależności od urządzenia, które obsługuje. Dla urządzenia digi tal video otrzymamy
okno i kontrolki umożliwiające odtwarzanie animacji, natomiast dla urządzenia waveaudio kontrolki
umożliwiające odgrywanie, przewijanie i zatrzymywanie muzyki zapisanej w pliku .wav.


Interfejsy API i zestawy SDK 781
Okno MCiwnd dodajemy do aplikacji w sposób bardzo prosty. Przykładowo, wpisując na końcu
funkcji initDialogO jedno tylko odwołanie do funkcji MCiWndCreate () dodajemy do okna dialogowego
kontrolkę obsługującą urządzenie digitalvideo pozwalającą odtwarzać pliki .avi (rysunek 28.5).


OK
Cancel






Rysunek 28.5. Okno MCI dołączone do standardowego okna dialogowego za pomocą funkcji
MCiWndCreate()
Przyzwanie funkcji MCiWndCreate () tworzy okno MCiWnd i inicjuje gotowy do odtworzenia plik
.avi zdefiniowany na końcu odwołania:
HWND hMCI = MCiWndCreate(m_hWnd, AfxGetApp()->m_hlnstance, MCIWNDF_SHOWALL,
"C:\\Vidclip.avi:") ;
Pierwszy parametr m_hWnd jest identyfikatorem obsługi okna rodzica (okna dialogo-
wego). Drugi jest identyfikatorem egzemplarza obiektu aplikacji. Trzeci parametr pozwala
zdefiniować jeden z licznych stylów okna przedstawionych w tabeli 28.7. Ostatni parametr
pozwala zdefiniować plik o rozpoznawanym przez MCI rozszerzeniu lub urządzenie MCI,
które chcemy otworzyć. Typ wyświetlonej kontro Iki będzie odpowiadać zdefiniowanemu
urządzeniu lub rozszerzeniu.
Jeśli urządzenie zostanie otwarte, zwracany jest odpowiedni identyfikator obsługi okna
MCI (zapisywany w zmiennej hMCi). Jeśli nie, zmiennej hMCI przypisywana jest wartość
NULL.
Tabela 28.7. Znaczniki funkcji MCiWndCreate
Znacznik Opis efektu





MCIWNDF_SHOWALL
MCIWNDF_SHOWNAME
MCIWNDF SHOWMODE
Okno korzysta z wszystkich znaczników stylów typu SHOW
Wyświetla nazwę pliku lub urządzenia na pasku tytułowym
oknaMCIWnd
Wyświetla na pasku tytułowym bieżący tryb urządzenia:
odtwarzanie, nagrywanie itp.


782 Poznaj Visual C++ 6





MCIWNDF_SHOWPOS MCIWNDF_RECORD
MCIWNDF_NOAUTOSIZEWINDOW
MCIWNDF_NOAUTOSIZEMOVIE
MCIWNDF_NOERRORDLG
MCIWNDF_NOMENU MCIWNDF_NOOPEN
MCIWNDF_NOPLAYBAR
MCIWNDF_MOTIFYALL
MCIWNDF_NO'TIFYMODE
MCIWNDF_NOTIFYPLAY [!]
MCIWNDF_NOTIFYMEDIA
MCIWNDF_NOTIFYSIZE MCIWNDF
NOTIFYERROR
Wyświetla na pasku tytułowym bieżącą pozycję w odtwarzanym
pliku
Jeśli urządzenie pozwala na nagrywanie, dodaje odpowiedni
przycisk
Nie zmienia automatycznie wymiarów okna, gdy zmienią się
wymiary obrazu
Nie rozszerza rozmiarów obrazu do rozmiarów okna jeśli wymiary
okna zostaną zmienione
Blokuje wyświetlanie komunikatów o błędach interfejsu MCI
Usuwa przycisk rozwijanego menu Uniemożliwia użytkownikowi
otwieranie innych plików
Ukrywa wszystkie, kontro Iki użytkownika - wszystkim zarządza
program
Przesyła oknu rodzica wszystkie komunikaty
Przesyła oknu rodzica wszystkie komunikaty o zmianie trybu
urządzenia
Przesyła oknu rodzica komunikaty o pozycji w odtwarzanym
mateńale
Przesyła oknu rodzica komunikaty o zmianie nazwy pliku lub
urządzenia
Przesyła oknu rodzica komunikaty o zmianie wymiarów
oknaMCIWnd
Przesyła oknu rodzica komunikaty o błędach MCI






Zmienianie stylów okna MCI
Style już istniejącego okna MCI możemy zmieniać za pomocą funkcji MCiWndChan-geStylesf).
Funkcja ta wymaga jako parametrów: identyfikatora obsługi okna MCI, maski bitowej wskazującej
zmieniane style i zmiennej z nowym zestawem ustawień dla okna. Powiązana z nią makroinstrukcja
MCiwndGetStyles () zwraca bieżące ustawienia znaczników dla zdefiniowanego okna.
Gdy okno MCiwnd jest otwarte, możemy przesyłać mu odpowiednie komunikaty kon-
trolek za pomocą specjalnie zdefiniowanych makroinstrukcji. Makroinstrukcje te przesyłają
komunikaty takie jak MCI_PLAY i inne odpowiadające poleceniom wymienionym w


Interfejsy API i zestawy SDK 783
tabeli 28.5. Makroinstrukcji związanych z komunikatami okna MCiWnd jest bardzo wiele, te które
odpowiadają poleceniom z tabeli 28.5, przedstawiliśmy w tabeli 28.8.
Tabela 28.8. Makroinstrukcje okna MCiWnd Makroinstrukcja
Komunikat Opis
MciWndOpenDialog

MCIWNDM OPEN

Otwiera nowy plik lub urządzenie MCI

MciWndOpen

- l

Otwiera nowy plik lub urządzenie MCI

MciWndClose

MCI CLOSE

Zamyka plik lub urządzenie MCI

MciWndPlay

MCI PLAY

Odgrywa plik lub urządzenie

MciWndRecord

MCIRECORD

Nagrywa w urządzeniu

MciWndStop

MCI STOP

Zatrzymuje odtwarzanie lub nagrywanie

MciWndSeek

MCISEEK

Odnajduje pozycję

MciWndPause

MCI PAUSE

Pauzuje odgrywanie

MciWndResume

MCIRESUME

Uruchamia po pauzie

MciWndPutDest

MCI PUT DEST

Ustala pozycję okna wyświetlającego film lub





animację




MciWndPlayFromTo
MCI_SEEK, MCIWNDM
PLAYTO
Odtwarza od miejsca do miejsca najpierw wysyłając
komunikat odszukujący pozycję, a potem odgrywając
materiał do ustalonej pozycji






Ustalanie bieżącego trybu okna
Może się zdarzyć, że po przesłaniu do okna MCI polecenia będziemy chcieli sprawdzić jego bieżący
tryb działania (czy odgrywa materiał, czy nagrywa itp.). Służy do tego instrukcja MCiWndGetMode
(). Musimy jej przesłać identyfikator obsługi okna oraz wskaźnik do bufora i rozmiary bufora, który
będzie przechowywał łańcuch lub strukturę zawierającą informacje o bieżącym trybie. Możliwe tryby
okna to:
MCI_MODE_NOT_READY, MCI_MODE_OPEN, MCI_MODE_PLAY, MCI_MODE_RECORD, MCI MODĘ
PAUSE,MCI MODĘ SEEK,MCI MODĘ STOP.
Makroinstrukcje te wymagają również przesłania identyfikatora obsługi okna MCI i w zależności od
potrzeb jeszcze kilku innych parametrów. Przykładowo, aby odegrać plik .avi od pozycji pliku l do pozycji
5, należy wpisać:
MCIWndPlayFromTo(hMCI,l,5) ;


784 Poznaj Visual C++ 6
Okno MCI możemy zniszczyć korzystając z makroinstrukcji MdWndDestroy i iden-
tyfikatora obsługi hMCI:
MCIWndDestroy (hMCI) ;
Przesyłamy w ten sposób po prostu zamykający okno komunikat WM_CLOSE.
Przedstawione tutaj makroinstrukcje możemy wykorzystywać w dowolnej aplikacji
oferującej nam legalny identyfikator obsługi okna rodzica.
Aby dodać do programu definicje makroinstrukcji i prototypy funkcji, należy załączyć
następujący plik nagłówka:
łinclude "vfw.h"
Aby dodać do programu funkcje API okna MCIWnd, należy do listy Object/Library
Modliłeś na karcie Link okna dialogowego Project Settings dodać plik biblioteki vfw28.1ib.
Okno to wywołujemy wybierając w menu Project polecenie Settings.
PATRZ TAKŻE
Okno MCIWnd jako kontrolka ActiveX opisane zostało w rozdziale 9.


Słownik


ActiveX patrz OLE
adres bazowy (ang. base address) - adres
definiujący początek obszaru zajmo-
wanego przez obiekt w pamięci.
adres uzupełniający (ang. offset address)
również adres przesunięcia; adres
wykorzystywany w połączeniu z ad-
resem bazowym identyfikujący element
składowy (zmienną, funkcję)
określonego przez adres bazowy eg-
zemplarza obiektu.
aktywny (ang. active) - jeden z dwu moż-
liwych stanów okna, drugim jest stan
nieaktywny.
anizotropiczny (ang. anisotrophic) obiekt,
element, rzecz mająca w jednym kie-
runku inne właściwości niż w pozo-
stałych kierunkach.
ANSI od American National Standards
Institute (amerykańska instytucja
normalizacyjna) - w żargonie kom-
puterowym standard znaków teksto-
wych, w którym każdemu znakowi
przypisana jest inna liczba o rozmiarach
jednego bajtu.
API patrz interfejs API
aplikacja MDI - aplikacja oparta na ar-
chitekturze dokument/widok pozwa-
lająca na jednoczesne otwieranie więcej
niż jednego dokumentu, w odróżnieniu
od aplikacji SDI, w której jednorazowo
otwarty może być tylko jeden dokument.


aplikacja SDI - aplikacja oparta na archi-
tekturze dokument/widok pozwalająca
na jednoczesne otwieranie, w od-
różnieniu od aplikacji MDI, tylko
jednego dokumentu, który jednak może
być przedstawiany jednocześnie w kilku
widokach.
AppWizard - kreator, narzędzie progra-
mowania umożliwiające automatyczne
tworzenie szkieletu projektu Visual C++.
architektura dokument/widok - podsta-
wowa zasada konstrukcji aplikacji. Patrz
również aplikacja MDI i aplikacja SDI.
archiwum, plik archiwum (ang. archiye) -
plik zawierający dane potrzebne apli-
kacji w postaci serializowanej lub w
postaci pliku dyskowego.
asercja (ang. asertions) - test wykorzysty-
wany podczas debugowania programu
ostrzegający programistę, że warunek
zapisany w makroinstrukcji ASSERT nie
został spełniony. Instrukcji ASSERT
używa się zazwyczaj do testowania, czy
wstępne warunki funkcji są poprawne.
aspect ratio - angielski termin oznaczający
stosunek długości do szerokości.
Również parametr opisujący stosunek
długości do szerokości pikseli wy-
świetlanych na ekranie monitora. Ma
istotne znaczenie, gdy zależy nam na
unikaniu zniekształceń obrazu.


Wyszukiwarka