天天看點

遊戲程式設計之五 DirectSound

 第一節 關于聲音

聲音是空氣的一系列振蕩,稱為聲波,一般可以用二維的波形圖來表示。數字音頻是指使用某種裝置将聲波記錄下來并儲存為一種數字化的檔案。播放相應的檔案就可以産生某種聲音效果。數字音頻的音質随着采樣頻率及所使用的位數不同而有很大的差異。是以,了解所使用音頻檔案格式的有關标準是很有必要的。例如,CD中的音頻是16位,采樣頻率達到44.1MHz的立體聲數字音頻。

在所有聲音檔案的格式中,WAV是最普遍的。這是Windows平台上最常見的格式,由微軟公司創造。支援8位和16位的音質、多樣本、對立體聲和單聲道音頻均可播放。它還支援多種音頻壓縮算法。

要在遊戲中取得好的聲音效果,例如,使用3D音效,可以有兩種方法來實作:一是使用一定的工具軟體對聲音檔案進行處理,生成播放效果足夠好的檔案,然後在遊戲程式中直接将這樣的檔案播放。顯然,這樣比較簡單,但是不靈活。如果需要音效随着遊戲場景的變化而不斷改變,且不受所具有聲音檔案數量的限制,就需要進行實時混音了。

第二節DirectSound結構

DirectSound的功能子產品包括播放、聲音緩沖區、三維音效、音頻抓獲、屬性集等。

DirectSound playback建構于IDirectSound COM接口之上。IDirectSoundBuffer,IDirectSound3DBuffer和IDirectSound3DListener接口則用以實作對聲音緩沖區和三維音效的操作。

DirectSound capture建構于IDirectSoundCapture和IDirectSoundCaptureBuffer COM接口之上。

其它的COM接口,如IKsPropertySet,使應用程式能夠從聲霸卡的擴充功能中最大地受益。

最後,IDirectSoundNotify接口用于在播放或音頻抓獲達到一定地方時向産生一個事件。

第三節 播放功能概述

DirectSound緩沖區對象表示一個包含聲音資料的緩沖區,這些資料以PCM格式被存儲。該對象不僅可以用于開始、停止或暫停聲音的播放,還能夠設定聲音資料中諸如頻率和格式等屬性。

緩沖區分為主緩沖區和副緩沖區。主緩沖區中是聽者将要聽到的音頻信号,一般是将副緩沖區中信号混音後的結果。而副緩沖區中存放着許多單獨的聲音信号,有的可以直接播放,有的要混音,有的循環播放。主緩沖區由DirectSound自動建立,而副緩沖區需由應用程式來建立。DirectSound将副緩沖區中的聲音混合後,存入主緩沖區,再輸出到相應播放裝置。

DirectSound中沒有解析聲音檔案的功能,需要您自己在應用程式中将不同格式的聲音信号改變過來(PCM)。

緩沖區可以在主機闆的RAM、波表存儲器、DMA通道或虛拟存儲器中。

多個應用程式可以用同一聲音裝置來建立DirectSound對象。當輸入焦點在應用程式中發生變化時,音頻輸出将自動在各個應用程式的流之間切換。于是,應用程式不用在輸入焦點改變中反複地播放和停止它們的緩沖區。

通過IDirectSoundNotify接口,當播放到了一個使用者指定的地方,或播放結束時,DirectSound将動态地通知擁護這一事件。

第四節 音頻抓獲概述

DirectSoundCapture對象可以查詢音頻抓獲裝置的性能,并為從輸入源抓獲音頻而建立緩沖區。

其實,在Win32中早已經有了抓獲音頻的功能,而目前的(版本5)DirectSoundCapture與隻比較并沒有什麼新的功能。不過,DirectSoundCapture API使您能夠編寫使用相同接口的播放和音頻抓獲程式,而且,這也為将來可能出現的API改進提供了原始模型,使您可以從中受益。

DirectSoundCapture還能夠抓獲壓縮格式的音頻。

DirectSoundCaptureBuffer對象表示一個用于抓獲音頻的緩沖區。它可以循環利用,也就是說,當輸入指針達到緩沖區的最後時,它會回到開始的地方。

DirectSoundCaptureBuffer對象的各種方式使您能夠設定緩沖區的屬性、開始或停止操作、鎖定某部分存儲器(這樣就可以安全地将這些資料儲存或用于其它目的)。

與播放類似,IDirectSoundNotify接口使在輸入指針到達一定地方時通知使用者。

第五節 初始化

對于一些簡單的操作,可以使用預設的首選裝置。不過,在遊戲的制作中,我們可能還是需要知道一些特定的聲音裝置。于是,您應該先列舉出可用的聲音裝置。

在此之前,您需要先設定一個回收函數,在每一次DirectSound發現新裝置後調用該函數。函數中您可以做任何事情,但您必須将它定義得與DSEnumCallback形式相同。如果希望列舉繼續,函數應傳回真,否則傳回假。

下面的例程來自CD光牒Example目錄下的Dsenum.c檔案。它列舉可用的裝置并在一個清單框中增加一條相應的資訊。首先是他的回收函數:

BOOL CALLBACK DSEnumProc(LPGUID lpGUID, 

                         LPCTSTR lpszDesc,

                         LPCTSTR lpszDrvName, 

                         LPVOID lpContext )

    {

    HWND   hCombo = *(HWND *)lpContext;

    LPGUID lpTemp = NULL;

    if( lpGUID != NULL )

        {

        if(( lpTemp = LocalAlloc( LPTR, sizeof(GUID))) == NULL )

        return( TRUE );

        memcpy( lpTemp, lpGUID, sizeof(GUID));

    }

    ComboBox_AddString( hCombo, lpszDesc );

    ComboBox_SetItemData( hCombo,

                ComboBox_FindString( hCombo, 0, lpszDesc ),

                lpTemp );

    return( TRUE );

    }

當包含了清單框的對話框被初始化後,列舉開始:

if (DirectSoundEnumerate((LPDSENUMCALLBACK)DSEnumProc, &hCombo)

    != DS_OK )

    {

    EndDialog( hDlg, TRUE );

    return( TRUE );

    }

建立DirectSound對象最簡單的方法是使用DirectSoundCreate函數。其中的第一個參數為相應裝置的全局獨有标志符(GUID)。您可以通過列舉聲音裝置得到GUID,或使用NULL來為預設裝置建立對象。

LPDIRECTSOUND lpDirectSound; 

HRESULT       hr;

hr = DirectSoundCreate(NULL, &lpDirectSound, NULL));

建立DirectSound對象後,應設定合作層。這是為了确定各個DirectSound應用程式被允許操作聲音裝置的範圍,防止它們在錯誤的時間或通過錯誤的方式操作裝置。

所使用的方式為IDirectSound::SetCooperativeLevel。這裡hwnd參數是應用程式視窗的句柄:

HRESULT hr = lpDirectSound->lpVtbl->SetCooperativeLevel(

                      lpDirectSound, hwnd, DSSCL_NORMAL);

這裡确定的合作層為normal,這樣使用聲霸卡的應用程式可以順序地進行切換。合作層包括

Normal、Priority、Exclusive和Write-primary,級别依次增加。

正如在前面提到過,DirectSound可以充分發揮硬體的增強功能,是以,它需要先設法了解裝置的特性。我們可以通過IDirectSound::GetCaps方式來達到這個要求。如下所示:

DSCAPS dscaps; 

dscaps.dwSize = sizeof(DSCAPS); 

HRESULT hr = lpDirectSound->lpVtbl->GetCaps(lpDirectSound, 

    &dscaps); 

DSCAPS結構接收關于聲音裝置性能和資源的資訊。注意,初始化該結構中dwSize成員是調用它之前所必須的。

除此之外,您還可以查詢和設定揚聲器的設定,以及整理聲音存儲器使盡量獲得最大的備用空間。

第六節 如何播放

初始化完成後,DirectSound将自動建立主緩沖區用于混音并傳送至輸出裝置。而副緩沖區則需要您自己來建立了。

下面的例程示範了用IDirectSound::CreateSoundBuffer方式建立一個基本的副緩沖區:

BOOL AppCreateBasicBuffer( 

    LPDIRECTSOUND lpDirectSound, 

    LPDIRECTSOUNDBUFFER *lplpDsb) 

    PCMWAVEFORMAT pcmwf; 

    DSBUFFERDESC dsbdesc; 

    HRESULT hr; 

    // 設定聲波格式結構

    memset(&pcmwf, 0, sizeof(PCMWAVEFORMAT)); 

    pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM; 

    pcmwf.wf.nChannels = 2; 

    pcmwf.wf.nSamplesPerSec = 22050; 

    pcmwf.wf.nBlockAlign = 4; 

    pcmwf.wf.nAvgBytesPerSec = 

        pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign; 

    pcmwf.wBitsPerSample = 16; 

    // 設定DSBUFFERDESC結構,用以設定緩沖區控制選項

    memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); 

    dsbdesc.dwSize = sizeof(DSBUFFERDESC); 

    // 要求預設的控制

    dsbdesc.dwFlags = DSBCAPS_CTRLDEFAULT; 

    // 3秒的緩沖區 

    dsbdesc.dwBufferBytes = 3 * pcmwf.wf.nAvgBytesPerSec; 

    dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf; 

    // 建立緩沖區

    hr = lpDirectSound->lpVtbl->CreateSoundBuffer(lpDirectSound, 

        &dsbdesc, lplpDsb, NULL); 

    if(DS_OK == hr) { 

        // 成功,獲得的接口在*lplpDsb當中 

        return TRUE; 

    } else { 

        // 失敗 

        *lplpDsb = NULL; 

        return FALSE; 

    } 

您必須設定緩沖區的控制選項。這是使用DSBUFFERDESC結構中的dwFlags成員,具體細節請參見DirectX 5的幫助。

副緩沖區不支援混音等特效,是以,您需要能夠直接操作主緩沖區。不過,當您獲權寫主緩沖區時,其它特性将失去作用,進而硬體加速混音失效。是以,大部分應用程式幾少直接操作主緩沖區。

如果要求操作主緩沖區,可以在調用IDirectSound::CreateSoundBuffer方式時設定DSBUFFERDESC結構中的DSBCAPS_PRIMARYBUFFER标志符,而且,合作層必須是Write-primary。

下面的例程示範了如何得到對主緩沖區的寫操作能力:

BOOL AppCreateWritePrimaryBuffer( 

    LPDIRECTSOUND lpDirectSound, 

    LPDIRECTSOUNDBUFFER *lplpDsb, 

    LPDWORD lpdwBufferSize, 

    HWND hwnd) 

    DSBUFFERDESC dsbdesc; 

    DSBCAPS dsbcaps; 

    HRESULT hr; 

    // 設定聲波格式結構

    memset(&pcmwf, 0, sizeof(PCMWAVEFORMAT)); 

    pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM; 

    pcmwf.wf.nChannels = 2; 

    pcmwf.wf.nSamplesPerSec = 22050; 

    pcmwf.wf.nBlockAlign = 4; 

    pcmwf.wf.nAvgBytesPerSec = 

        pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign; 

    pcmwf.wBitsPerSample = 16; 

    // 設定DSBUFFERDESC結構

    memset(&lplpDsb, 0, sizeof(DSBUFFERDESC));  

    dsbdesc.dwSize = sizeof(DSBUFFERDESC); 

    dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER; 

    // 緩沖區大小由聲音硬體決定

    dsbdesc.dwBufferBytes = 0; 

    dsbdesc.lpwfxFormat = NULL; // 對主緩沖區必須設為NULL

    // 獲得write-primary合作層

    hr = lpDirectSound->lpVtbl->SetCooperativeLevel(lpDirectSound, 

        hwnd, DSSCL_WRITEPRIMARY); 

    if (DS_OK == hr) { 

        // 成功,試圖建立緩沖區

        hr = lpDirectSound->lpVtbl->CreateSoundBuffer(lpDirectSound, 

            &dsbdesc, lplpDsb, NULL); 

        if (DS_OK == hr) { 

            // 成功,設定主緩沖區為desired格式

            hr = (*lplpDsb)->lpVtbl->SetFormat(*lplpDsb, &pcmwf); 

            if (DS_OK == hr) { 

                // 如果希望得知緩沖區大小,調用GetCaps

                    dsbcaps.dwSize = sizeof(DSBCAPS); 

                (*lplpDsb)->lpVtbl->GetCaps(*lplpDsb, &dsbcaps); 

                *lpdwBufferSize = dsbcaps.dwBufferBytes; 

                return TRUE; 

            } 

        } 

    } 

    // 設定合作層失敗

    // 建立緩沖區,或設定結構

    *lplpDsb = NULL; 

    *lpdwBufferSize = 0; 

    return FALSE; 

播放一段聲音的過程包括以下四個步驟:

1 鎖定(IDirectSoundBuffer::Lock)副緩沖區的一部分。由您設定的偏移量決定下一步寫操作的起始點;

2 寫資料;

3 解鎖(IDirectSoundBuffer::Unlock);

4 将聲音傳送給主緩沖區,并由那裡輸出(IDirectSoundBuffer::Play)。

下面的C程式向緩沖區中寫入資料,由dwOffset指定開始時的偏移量:

BOOL AppWriteDataToBuffer( 

    LPDIRECTSOUNDBUFFER lpDsb,  // DirectSound緩沖區

    DWORD dwOffset,             // 自己的寫标記位置

    LPBYTE lpbSoundData,        // 資料的起點

    DWORD dwSoundBytes)         // 拷貝塊的大小

    LPVOID lpvPtr1; 

    DWORD dwBytes1; 

    LPVOID lpvPtr2; 

    DWORD dwBytes2; 

    HRESULT hr; 

    // 得到被寫塊的位址

    hr = lpDsb->lpVtbl->Lock(lpDsb, dwOffset, dwSoundBytes, &lpvPtr1, 

        &dwBytes1, &lpvPtr2, &dwBytes2, 0); 

    // 如果傳回DSERR_BUFFERLOST,釋放并重試鎖定

    if(DSERR_BUFFERLOST == hr) { 

        lpDsb->lpVtbl->Restore(lpDsb); 

        hr = lpDsb->lpVtbl->Lock(lpDsb, dwOffset, dwSoundBytes, 

            &lpvPtr1, &dwAudio1, &lpvPtr2, &dwAudio2, 0); 

    } 

    if(DS_OK == hr) { 

        // 寫到指針

        CopyMemory(lpvPtr1, lpbSoundData, dwBytes1); 

        if(NULL != lpvPtr2) { 

            CopyMemory(lpvPtr2, lpbSoundData+dwBytes1, dwBytes2); 

        } 

        // 釋放

        hr = lpDsb->lpVtbl->Unlock(lpDsb, lpvPtr1, dwBytes1, lpvPtr2, 

            dwBytes2); 

        if(DS_OK == hr) { 

            // 成功 

            return TRUE; 

        } 

    } 

    // 某步驟失敗

    return FALSE; 

er

    我們将讨論每一個接口,并随後讨論它們的成員函數。但我們并不讨論每個函數的細節,因為我們并不是向您提供一份參考手冊。相反,我們将讨論每個函數是幹什麼的,為什麼這樣使用,以及你有可能如何去使用它。

    當DirectX首次推出的時候(早先它被稱作Games SDK),DirectDraw核心函數性以DirectDraw接口表示。當DirectX2推出的時候,DirectDraw也已經被更新了。DirectDraw遵守COM規格而未被改變。新的函數性可能通過DirectDraw2接口存取。

    特别要注意的是,DirectDraw2接口是DirectDraw接口的超級設定。DirectDraw2接口可提供DirectDraw接口的所有函數,另外還增加了一些新的函數。如果你正在使用DirectX或更高版高,那麼你可以随意選用DirectDraw接口或DirectDraw2接口。但是,由于DirectDraw2接口較DirectDraw接口的功能更強,是以沒有必要使用DirectDraw接口。同樣,Microsoft并不主張使用這些無組織的、網絡可變的接口。是以,在本書以後的程式中我們隻使用DirectDraw2接口。

    DirectDraw和DirectDraw2接口提供的成員函數如下(按字母順序排列):

●Compact()

●CreateClipper()

●CreatePalette()

●CreateSurface()

●DuplicateSurface()

●EnumDisplayModes()

●EnumSurfaces()

●FlipToGDISurface()

●GetAvailableVidMem()

●GetCaps()

●GetDisplayMode()

●GetFourCCCodes()

●GetGDISurface()

●GetMonitorFrequency()

●GetScanline()

●GetVerticalBlankStatus()

●RestoreDisplayMode()

●SetCooperativeLevel()

●SetDisplayMode()

●WaitForVerticalBlank()

    接下來我們讨論DirectDraw接口函數。注意,在本章以後的内容中,DirectDraw接口既表示DirectDraw接口,也表示DirectDraw2接口。隻有在區分DirectDraw接口和DirectDraw2接口的函數時,才加以差別。

    1. 接口建立函數

    DirectDraw接口表示DirectDraw本身。該接口在被用于建立其他DirectDraw接口執行個體時,是一個主接口。DirectDraw接口提供三個這樣的接口執行個體建立函數:

●CreateClipper()

●CreatePalette()

●CreateSurface()

    CreateClipper()函數用于建立DirectDrawClipper接口執行個體。并非所有的DirectDraw應用程式都用到剪裁器,是以該函數并不是在所有的程式中都出現。我們将很快讨論DirectDrawClipper的細節。

    CreatePalette()函數用于建立DirectDrawPalette接口執行個體。同DirectDrawClipper一樣,并非所有的DirectDraw應用程式都用到調色闆。比如,應用程式使用16位顯示模式時,就不用調色闆。但是,當應用程式使用8位顯示模式時,就必須建立至少一個DirectDrawPalette執行個體。

    CreateSurface()函數用于建立DirectDrawSurface接口執行個體。任何一個DirectDraw應用程式都要用表面來生成圖像資料,是以經常要用到這一函數。

    DirectDraw接口自己的執行個體是由DirectDrawCreate()函數建立的。DirectDrawCreate()是DirectDraw函數中少有的幾個正常函數之一,但并不是COM接口成員函數。

    2. GetCaps()函數

    DirectDraw接口允許準确确定軟硬體都支援的特征。GetCaps()函數可以對兩個DDCAP結構執行個體進行初始化。一個結構表明哪些特征由顯示硬體直接支援,另一個結構表明哪些特征由軟體仿真支援。最好是用GetCaps()函數來決定你将用到的特征是否被支援。

提示:DirectX浏覽器

    DirectX SKD是與DXVIEW程式同時推出的。DXVIEW說明了DirectX元件的功能,包括DirectDraw。大多數系統中,有兩個DirectDraw項目:主顯示驅動器和硬體仿真層。第一項說明了顯示硬體的功能。第二項說明了在缺乏硬體支援的情況下,DirectDraw将要仿真的一些特征。在具有兩個以上的DirectDraw支援的顯示卡的計算機中,DXVIEW會顯示卡的功能。

    3. SetCooperativeLevel()函數

    SetCooperativeLevel()函數用于指定應用程式所要求的對顯示硬體的控制程度。比如,一個正常合作度意味着應用程式既改變不了目前顯示模式,也不能指定整個系統調色闆的内容。而一個專有的合作度允許顯示模式切換,并能完全控制調色闆。不管你決定使用哪種合作度,都必須調用SetCooperativeLevel()函數。

    4. 顯示模式函數

    DirectDraw接口提供4種顯示模式操作函數。它們是:

●EnumDisplayModes()

●GetDisplayMode()

●RestoreDisplayMode()

●SetDisplayMode()

    EnumDisplayModes()函數可用于查詢DirectDraw使用何種顯示模式。通過設定EnumDisplayModes()函數預設值可以得到所有的顯示模式,而且可以通過顯示模式描述消除那些不感興趣的模式。進行顯示模式切換的過程中最好使用EnumDisplayModes()函數。現在市場上有各種各樣的顯示裝置,每種顯示裝置都有自己的特征和局限。除了預設的640×480×8視窗顯示模式,最好不要依靠任何給定的顯示模式的支援。

  GetDisplayMode()函數可以檢索到有關目前顯示模式的資訊,并在DDSURFACEDESC結構執行個體中顯示目前顯示模式的寬度、高度、像素深度以及像素格式等資訊。還有别的途徑可以檢索到同樣的資訊(比如檢索主表面描述),是以該函數并不出現在所有的程式中。

  SetDisplayMode()函數用于激活所支援的顯示模式。SetDisplayMode()函數的DirectDraw2版本還允許設定顯示模式的重新整理率。而DirectDraw接口版本的SetDisplayMode()函數隻能進行顯示模式寬度、高度和像素深度的設定。任何一個要進行顯示模式切換的程式都要用到SetDisplayMode()函數。

    RestoreDisplayMode()函數用于存儲調用SetDisplayMode()函數之前的顯示模式。SetDisplayMode()和RestoreDisplayMode()函數都要求優先使用SetCooperativeLevel()函數得到的專有合作存取。

5. 表面支援函數

    除了CreateSurface()函數之外,DirectDraw接口還提供了以下向個表面相關函數:

●DuplicateSurface()

●EnumSurfaces()

●FlipToGDISurface()

●GetGDISurface()

●GetAvailableVidMem()

●Compact()

    DuplicateSurface()函數用于考貝目前表面。該函數隻複制表面接口,不複制記憶體。被複制的表面與源表面共享記憶體,是以改變記憶體的内容就同時改變了兩個表面的圖像。

    EnumSurfaces()函數可用于疊代所有滿足指定标準的表面。如果沒有指定标準,那麼所有目前表面都被枚舉。

    FlipToGDISurface()函數的作用是在終止頁面翻轉應用程式前確定主表面得以正确存儲。取消頁面翻轉時,有兩個表面交替顯示。這就是說,在終止應用程式之前有可能沒有儲存最初的可見表面。這種情況下,Windows通過繪制一個不可見表面來恢複。利用FlipToGDISurface()函數就可以輕而易舉地避免發生這種情況。

    GetGDISurface()函數可以向隻被GDI認可的表面傳回一個提針。GDI表面是Windows用于輸出的表面。在進行螢幕捕捉時,這個函數非常有用,DirectDraw可以捕捉到Windows桌面的任一部分。

    GetAvailableVidMem()函數用于檢索正在使用中的視訊存儲器(顯示RAM)的數量。這一函數由DirectDraw2接口提供,而不是由DirectDraw接口提供。該函數用于确定應用程式利用顯示RAM可建立表面的數量。

    Compact()函數不是通過DirectX5實作的,但它可以為視訊存儲器提供碎片整理技巧。在基于顯示RAM的表面被不斷建立或受到破壞的時候,可以釋放大量記憶體。

6. 螢幕重新整理函數

    DirectDraw接口提供了4種适于計算機顯示裝置或螢幕的函數,但這些函數不适于顯示卡,它們是:

●GetMonitorFrequency()

●GetScanLine()

●GetVerticalBlankStatus()

●WaitForVerticalBlank()

    這些函數尤其與螢幕的重新整理機制緊密機連。這在確定生成動畫時盡可能不産生閃爍和圖像撕裂現象時是至關重要的。但必須注意,并非所有的顯示卡/螢幕組合都支援這些函數。

    GetMonitorFrequency()函數用于檢索螢幕目前的重新整理率。重新整理率通常用赫茲表示,縮寫為Hz。例如,60Hz的重新整理率表示螢幕每秒更新60次。

    GetScanLine()函數用于向螢幕傳回目前正在被重新整理的掃描行(水準像素行)。不是所有的顯示裝置/螢幕組合都支援該函數。如果這一功能得不到支援,該函數将傳回DDERR-UNSUPPORTED。

    對于高性能圖形應用程式來說,通常要求利用垂直重新整理同步地更新螢幕。尤其是,當顯示器剛完成螢幕重新整理時,最好能夠更新主表面。否則,螢幕的一部分顯示新的圖像資料,而另一部分仍顯示舊的圖像資料,這種現象就是所謂的圖像撕裂。DirectDraw預設利用垂直重新整理同步更新螢幕。如果不是這樣還可以利用GetVerticalBlankStatus()和WaitForVerticalBlank()函數實作同步重新整理。

7. GetFourCCCodes()函數

    DirectDraw接口提供的最後一個函數是GetFourCCCodes()函數。該函數用于傳回顯示卡所支援的FourCC代碼。FourCC代碼用于描述非RGB或YUV表面。我們不在此讨論YOV表面,它們已超出本書的範圍。

第五節 DirectDrawSurface接口函數

    同DirectDraw接口一樣,DirectDrawSurface接口也遵守COM規格.最初,表面支援是由DirectDrawSurface接口提供的。DirectX2介紹了DirectDrawSurface2接口的新的函數性,DirectX5介紹了DirectDrawSurface3接口。

    盡管本軟體中讨論的是DirectDraw2接口,而不是DirectDraw接口,但我們仍忠于最初的DirectDrawSurface接口,因為DirectDrawSurface2和DirectDrawSurface3接口新增的函數并不十分重要。在以後的内容裡,我們将用DirectDrawSurface接口表示這3種接口,除非特别注明。

    DirectDrawSurface是最大的DirectDraw接口,它允許表面内容的拷貝、清除以及被調用程式直接存取。DirectDrawSurawSurface接口總共提供36個成員函數,按字母順序排列如下:

●AddAttachedSurface()

●AddOverlayDirtyRect()

●Blt()

●BltBatch()

●BltFast()

●DeleteAttachedSurface()

●EnumAttachedSurfaces()

●EnumOverlayZOrders()

●Flip()

●GetAttachedSurface()

●GetBltstatus()

●GetCaps()

●GetClipper()

●GetColorKey()

●GetDC()

●GetDDInterface()

●GetFlipStatus()

●GetOverlayPosition()

●GetPalette()

●GetPixelFormat()

●GetSurfaceDesc()

●IsLost()

●Lock()

●PageLock()

●PageUnlock()

●ReleaseDC()

●Restore()

●SetClipper()

●SetColorKey()

●SetOverlayPosition()

●SetPalette()

●SetSurfaceDesc()

●Unlock()

●UpdateOverlay()

●UpdateOverlayDisplay()

●UpdateOverlayZOrder()

1. 表面描述函數

    我們首先讨論的個可用于檢索表面自身資訊的函數,它們是:

●GetCaps()

●GetPixelFormat()

●GetSurfaceDesc()

●SetSurfaceDesc()

    同DirectDraw接口提供的GetCaps()函數一樣,DirectDrawSurface接口提供的GetCaps()函數用于輸出表征哪些特征可被表面支援的資料。該資訊包括:表面是主表面還是離屏表面;表面使用的存儲器定位于顯示RAM還是系統RAM。

    GetPixelFormat()函數在用于高彩和真彩表面時是非常重要的,這是由于像素格式因顯示卡的不同而不同。該函數傳回表征碼,這些表征碼表明每一種顔色部件是如何存儲的。

    GetSurfaceDesc()函數傳回一個表面描述。該資訊包括表面的寬度、高度和深度。表面像素格式(同樣被GetPixelFormat()函數檢索)也包含在其中。

    SetSurfaceDesc()函數(對于DirectX5)來講是新增的,隻由DirectDrawSurface3接口提供)允許設定某些表面屬性。該函數可用于指定表面使用的記憶體。這一點在設計定制表面存儲器管理器政策時非常有用。

2。 表面Blt函數

    DirectDrawSurface接口提供3個支援blt操作的函數:

●Blt()

●BltBatch()

●BltFast()

    Blt()函數是一個主要函數。Blt()函數能夠進行正常的blting(無特殊影響的簡單的表面到表面的blt),同時支援延伸、旋轉、鏡像和顔色填充的操作。當用于同剪裁器關聯的表面時,Blt()可進行剪裁blt操作。

    BltBatch()函數不是在DirectX3下實作的(你可以調用該函數,但什麼也不會發生)。執行BltBatch()函數時,如果可能,它可同時進行多blt操作。

    BltFast()函數是Blt()函數的優化版本。BltFast()函數的效率提高了,但性能卻下降了。BltFast()函數不能進行一些特殊的blt操作,而Blt()函數可以。而且,BltFast()函數不能用于剪裁。但是BltFast()函數支援源和目标色彩鍵碼blt的操作。在遵循定制剪裁例程的情況下,BltFast()函數可進行DirectDraw能夠提供的最快捷、靈活的blt操作。在下章中我們将執行一個定制剪裁例程。

    以上3個blt函數均将源表面和目标表面作為變量。其他的資料,例如blt在目标表面上的理想定位,是通過指定理想blt操作的确切屬性來提供的。一旦可能,這3個函數将進行硬體加速blt。

3. Flip()函數

    Flip()函數用于頁面翻轉操作。調用Flip()函數可隐藏螢幕上先前可見的表面,并使一個後備緩沖區顯現。隻有被明确地建立為翻轉表面的表面,才響應該函數的調用。

    必須牢記,真正的翻轉操作不可能總是成功。頁面翻轉要求有足夠的顯示RAM容納兩整屏有效資料。如果滿足不了這一要求,系統RAM中将建立一個後備緩沖區。這時調用Flip()函數進行的是blt操作而不是頁面翻轉。基于系統RAM的後備緩沖區中的内容被拷貝到主表面上。這樣會嚴重影響性能,但是,在真正的頁面翻轉中如果沒有足夠的顯示RAM,又不退出程式,也隻能如此了。如果你的應用程式要求最佳性能,就得設法避免激活不能進行真正頁面翻轉的顯示模式。

4. 表面狀态函數

    下面讨論兩個能檢索有關操作和翻轉操作資訊的函數,它們是:

    ●GetBltStatus()

    ●GetFlipStatus()

    GetBltStatus()函數用于确定目前是否進行blt操作。這一點很重要,因為正被blt的表面不能進行其他操作。該函數表明,給定的表面是否正是一個進行blt操作的源表面或目标表面。

    同樣地,GetBltStatus()函數表明是否正在進行翻轉操作。即使DirectDraw通過blt操作仿真頁面翻轉,該函數而不GetBltStatus()也必須用于由Flip()函數初始化的螢幕頁面翻轉。

5. 色彩鍵碼函數

    DirectDrawSurface接口提供了以下兩個函數,來設定和檢查表面色彩鍵碼或色彩鍵碼的範圍,它們是:

    ●GetColorKey()

    ●SetColoKey()

    預設狀态下,表面沒有色彩鍵碼。一個色彩鍵碼隻對應一種顔色,但某些硬體支援色彩鍵碼範圍。色彩鍵碼和色彩鍵碼範圍是DDCOLORKEY結構定義的。GetColorKey()和SetColoKey()函數都将該結構的指針作為變量。在要求表面的一部分透明時或需要進行目标色彩鍵碼操作時,可以使用這兩個函數。

6. Lock和Unlock()函數

    DirectDraw的一個主要特點,就是能夠提供對圖像資料的直接存取。直接存取可以提供最佳性能和更好的靈活性,因為沒有中間API影響運作速度,并且開發人員呆任意使用圖像資料。對表面存儲器的中間存取通過以下出衆個函數實作:

    ●Unlock()

    ●Lock()

    Lock()函數向組成表面的存儲器傳回一個指針,不管表面存儲器位于顯示RAM還是系統RAM。存儲器一般按線性風格排列,以便能簡單地進行圖像 資料存取。Unolock()函數在完成表面存儲器的存取之後指定給DirectDraw。

    對圖像資料的直接存取必須付出代價。為了支援這種存取方式,DirectDraw在表面鎖定的時候必須關閉基本的Windows機構。在Windows95狀态下,如果忘記解鎖表面,必定會損壞機器。

    是以,表面鎖定的時間應盡量縮短。測試前應仔細檢查Lock()和Unlock()函數之間的程式調用。因為這一程式無法用傳統的調試程式進行調試。

    鎖定表面不能被blt和翻轉,是以試圖保持表面處于鎖定狀态沒有任何有益之處。而且,一旦表面解鎖,由Lock()函數檢索的指針就失效了。

    表面鎖定後不能再次被鎖定。在表面鎖定時也就無法調用Lock()函數。

7. GetDC()ReleaseDC()函數

    對表面的直接存取占用很大記憶體,有時候把表面作為一個正常的Windows裝置會更好。在此,DirectDrawSurface接口提供以下兩個函數:

●GetDC()

●ReleaseDC()

    GetDC()函數提供了一個DC(裝置環境),可以用正常的Win32函數寫到表面上。例如,DC可以用Win32的TextOut()函數在表面上繪制文本。用完DC後必須馬上調用ReleaseDC()函數。

    同Lock()和Unlock()函數使用一樣,在調用完GetDC()函數後必須馬上調用ReleaseDC()函數。這是因為GetDC()函數内部調用Lock函數,而ReleaseDC()函數内部調用Unlock()函數。

8. PageLock()和PageUnlock()函數

    接下來,我們讨論另外兩個與Lock()函數和Unlock()函數看上去非常相像的函數:

●PageLock()

●PageUnlock()

    盡管這兩個函數看上去很像Lock()和Unlock()函數,但它們卻有完全不同的作用。PageLock()和PageUnlock()函數用于控制Windows對基于系統RAM的表面的處理方式。這兩個函數由DirectDrawSurface2接口提供,而不是由DirectDrawSurface接口提供。

    當Windows認為目前正在運作的其他應用程式或程序更适于使用記憶體時,Windows會向硬碟釋放部分記憶體。這種緩沖對于整個系統記憶體都起作用,是以駐留在系統記憶體中的DirectDraw表面有可能被存到硬碟上。如果要用到這樣的表面,Windows需要花費一定的時間從硬碟上讀取表面資料。

    PageLock()函數提示Windows哪些給定的表面不應該釋放到硬碟上。這樣,在使用表面時就不用耗費時間進行硬碟存取了。相反地,PageUnlock()函數用于告知Windows哪些表面記憶體可被釋放。

    過程調用PageLock()函數會減少緩沖記憶體的總量,進而導緻Windows的速度大大降低。至于這種情況何時發生,取決于頁面鎖定系統記憶體量及機器提供的系統記憶體量。

    PageLock()和PageUnlock()函數主要是由DirectDraw提供而非DirectDraw應用程式。舉個例子來說,DirectDraw自動使用PageLock()函數,以確定運作blt操作時,基于系統RAM的表面不被釋放到硬碟。

    PageLock()函數可以被同一個表面多次調用。DirectDraw用參考計數法記錄PageLock()函數被調用的次數,是以多次調用PageUnlock()函數就必須避免多次調用PageLock()函數。

    PageLock()和PageUnlock()函數對于駐留在顯示RAM中的表面不起作用。

9. IsLost()的Restore()函數

    現在讨論兩個與使用駐留在顯示RAM中的表面有關的函數:

●IsLost()

●Restore()

    讓我們來看一看下面這種情況。應用程式正在運作時,盡量把表面配置設定到顯示RAM中,剩下的建立到系統RAM中。應用程式在運作一段時間之後,使用者執行或切換到另一個應用程式。該應用程式是任意的,可以是一個正常的Windows程式,如Windows開發程式或記事本。它也可以是另外的DirectDraw應用程式,該程式也試圖将盡可能多地配置設定顯示RAM。如果DirectDraw不接受顯示RAM,那麼新的應用程式就很可能根本運作不了。相反的情況就意味着,應用程式不允許配置設定到任何顯示RAM中。

    是以,DirectDraw可以随意将任何一個或者所有的基于顯示RAM的表面從非激活應用程式中移走。這種情況就是所謂的表面丢失。從技術上講,程式仍具有表面,但它們不再同任何記憶體相關。要使用丢失的表面會導緻DDERR-SURFACELOST錯誤。IsLost()函數可用于确定一個表面是否丢失了記憶體。

    表面記憶體丢失後可通過Restore()函數恢複,但隻能在應用程式被重新激活之後才可恢複。這會導緻應用程式無法将處于最小化狀态的所有表面複原。

    Restore()函數可恢複附屬于表面的任一記憶體,但并不恢複記憶體的内容。表面被複原後,應用程式就可以恢複表面内容了。

    注意,這種用法不适合利用系統RAM建立的表面。如果需要用到基于系統RAM的表面所占記憶體,那麼Windows會立即将這些表面釋放到硬碟上。Windows自動地處理存儲和恢複,包括恢複表面的内容。

10. GetDDInterface()函數

    GetDDInterface()函數可檢索用于建立給定表面的DirectDraw接口的指針。由于程式中大多數情況下隻有一個DirectDraw接口執行個體,是以GetDDInterface()函數并不常用。但是一個應用程式中有可能使用多個DirectDraw接口,在這種情況下,GetDDInterface()函數會起到重要作用。

11. 表面連接配接函數

    DirectDrawSurface接口提供以下4個函數,用來維持表面間的連接配接:

●AddAttachedSurface()

●DeleteAttachedSurface()

●EnumAttachedSurfaces()

●GetAttachedSurface()

    DirectDraw支援大量的用于表面間的連接配接情況。最常見的情況就是頁面翻轉。進行頁面翻轉時,兩個或兩個以上的表面連接配接成環狀,每次調用Flip()函數時,都會使連成環狀的表面中的下一個表面顯現。

    表面連接配接函數用于建立、檢查或消除表面間的連接配接,但這些函數并非必不可少的。DirectDraw往往是自動建立連接配接表面。比如,當建立一個主翻轉表面時,可以指定用于連接配接表面的後備緩沖區的數量。DirectDraw就會建立這些表面,并将它們連接配接起來。

12. 重疊函數

    DirectDrawSurface接口用于支援重疊的函數如下:

●AddOverlayDirtyRect()

●EnumOverlayZOrders()

●GetOverlayPosition()

●SetOverlayPosition()

●UpdateOverlay()

●UpdateOverlayDisplay()

●UpdateOverlayZOrder()

    GetOverlayPosition()和SetOverlayPosition()函數用于控制重疊的位置。UpdateOverlay()函數用于更新大量的重疊設定,包括重疊是否可見,以及重疊是以色彩鍵碼還是用alpha混合到背景表面上。

    UpdateOverlayDisplay()函數用于更新顯示設定。該函數用于更新整個重疊顯示,或者隻更新由AddOverlayDirtyRect()函數指定的矩形重疊部分。EnumOverlayZOrders()函數可根據重疊的Z值(Z值控制哪一個重疊位于最上面)重複重疊。重疊可按從前到後或從後到前的順序枚舉。

13. 剪裁器函數

    DirectDraw支援的剪裁是将DirectDrawClipper接口(該接口我們尚未讨論)的一個執行個體連接配接到表面。一旦連接配接完畢,剪裁器對象就會有規律地blt到表面。剪裁器/表面的連接配接由以下兩個DirectDrawSurface函數控制:

●GetClipper()

●SetClipper()

    SetClipper()函數用來将剪裁器對象連接配接到表面。GetClipper()函數用于向前一個連接配接的剪裁器對象傳回一個指針。SetClipper()函數還可用于解除表面同剪裁器的連接配接,具體的做法是通過指定NULL來替代DirctDrawClipper接口指針。

14。 調色闆函數

    像剪裁器一樣,調色闆也可連接配接到表面。DirctDrawSurface接口為此提供以下兩個函數:

●GetPalette()

●SetPalette()

    SetPalette()函數用來将DirctDrawPalette接口(該接口我們接下來就要讨論)的一個執行個體連接配接到表面。GetPalette()函數用于檢索前一個連接配接調色闆的指針。

    調色闆可被連接配接到任何表面,但隻有連接配接到主表面時,調色闆才起作用。當與主表面連接配接時,調色闆決定顯示硬體調色闆的設定。

第六節 DirectDrawPlette接口函數

    DirctDraw提供DirctDrawPalette接口用于調色闆顯示模式和表面。盡管Windows支援幾種低于8位像素深度的顯示模式,但DirctDraw所支援的唯一的調色闆顯示模式是8位模式。

    DirctDrawPalette接口執行個體由DirctDraw CreatePalette()函數建立。CreatePalette()函數用大量的标志來定義調色闆屬性。

    DirctDrawPalette接口隻提供以下3個函數:

●GetCaps()

●GetEntries()

●SetEntries()

    GetCaps()函數用于檢索有關調色闆的資訊,包括調色闆項目數量,調色闆是否支援同步垂直重新整理,以及在8位調色闆狀态下是否所有的256個調色闆項目都能被設定。 

    SetEntries()函數允許在程式中設定調色闆的色彩值。該資料從檔案中讀取。而這些項目在運作過程中可被計算和設定。GetEntries()函數用于檢索先前設定的調色闆項目。

    DirctDrawPalette()接口執行個體可利用DirctDrawSurface SetPalette()函數連接配接到表面。将不同調色闆連接配接到主表面或利用SetEntries()函數改變調色闆項目都可激活調色闆。

第七節 DirectDrawClipper接口函數

    DirctDrawClipper接口支援剪裁。将剪裁器對象連接配接到表面并在blt操作中将其當作目标表面就可以進行剪裁。

    directDrawClipper執行個體由DirectDraw CreateClipper()函數建立。DirectDrawClipper接口支援以下5個函數:

●SetHWnd()

●GetHWnd()

●IsClipListChanged()

●SetClipList()

●GetClipList()

    剪裁器對象一般用于出現在視窗中的DirctDraw應用程式必需的剪裁。要求剪裁器必須確定在blt操作過程中考慮到桌面上其他的視窗。比如,當應用程式的全部或一部分被另一個視窗遮蔽,剪裁就必須確定被遮蔽的視窗不被DirctDraw應用程式破壞。

    桌面剪裁由SetWnd()函數完成。SetHWnd()函數将剪裁器對象連接配接到一個視窗句柄。這樣就初始化了Windows和剪裁器對象之間的通訊。當桌面上的任何一個視窗發生變化時,剪裁器對象就會得到通知,并作出反應。GetHWnd()函數用于決定剪裁器同哪一個視窗句柄連接配接。IsClipListChanged()函數用于決定内部剪裁清單是否因桌面的改變而被更新。

    SetClipList()和GetClipList()函數為DirectDrawClipper接口提供便利的定制使用。SetClipList()函數用于定義一些矩形區域,這些矩形區域用來定義blt操作的合法區域。GetClipList()函數用于檢索剪裁器的内部剪裁資料。

    一旦被連接配接到表面,Blt(),BltBatch()以及UpdateOverlay()函數所進行的blt操作将根據DirctDrawCliper接口中包含的資料被自動地剪裁。注意,Blt Fast()函數在此被忽略。BltFast()函數不支援剪裁。

第八節 附加DirectDraw接口

    DirctDraw還提供了另外個我們沒有讨論的接口,它們是:

●DDVideoPortContainer

●DirectDrawColorControl

●DirectDrawVideoport

    這些接口由DirectX5介紹,它們提供低水準視訊端口控制。這些接口還向DirctDraw表面提供流活動視訊的方法。盡管利用這些接口可以為DirctDraw應用程式增加視訊支援,但除非高水準視訊APIs不能滿足需要,否則最好不用這一方法。

第九節 DirectDraw結構

我們已讨論過DirctDraw接口及其成員函數了,接下來再看看DirctDraw定義的結構。DirctDraw總共定義了8全結構:

●DDBLTFX

●DDCAPS

●DDOVERLAYFX

●DDPIXELFORMAT

●DDSURFACEDESC

●DDSCAPS

●DDBLTBATCH

●DDCOLORKEY

    我們已經見過其中的一些結構了,比如在讨論DirctDrawSurface SetColorKey()函數時我們就接觸過DDCOLORKEY結構。在此,我們不詳細讨論每一個結構的細節,但必須指出,DirctDraw quirk被忘記時會導緻失敗。

    以上所列結構中的前5個有一個稱作dwSize的字段。該字段用于存儲結構的大小,設定該字段的工作由你來做。另外,該字段如果沒有被正确設定的話,那麼任何一個将這5個結構作為變量的DirctDraw函數都會失效。

    以DDSURFACEDESC結構為例,使用結構的代碼如下:

    DDSURFACEDESC surfdesc;

    surfdesc.dwSize=sizeof(surfdesc);

    surf->GetSurfaceDesc(&surfdesc);

    首先聲明結構,然後用sizeof()關鍵字設定dwSize字段。最後結構傳遞給DirctDrawSurface GetSurfaceDesc()函數。忘記設定dwSize字段将導緻這段代碼失效。

    到底為什麼DirctDraw堅持要求給出它所定義的結構的大小?原因在于這5個包含dwSize字段的結構将來有可能會改變。DirctDraw會檢查結構的大小,以便确定正在使用的版本。DirctDraw堅持要求給出一個有效的大小值,是為了讓開發者提供有效的結構大小。這樣做是有好處的,因為DirctDraw的新版本可以正确運用舊版本的DirctDraw程式。

    在使用結構之前,最好将結構初始化為零。這樣,前面的代碼就變成:

    DDSURFACEDESC surfdesc;

    ZeroMemory (&surfdesc,sizeof(surfdesc));

    surfdesc.dwSize=sizeof(surfdesc);

    surf->GetSurfaceDesc(&surfdesc);

ZeroMemory()函數是一個Win32函數,它将作為第一個參數的存儲器設定為零.ZeroMemory()函數的第二個參數表明有多少存儲器應被初始化。這一做法的好處是,通過GetSurfaceDesc()函數調用可以知道結構的哪些字段被更新了。如果沒有對結構進行初始化,就有可能将結構字段中不可預測的值當作DirectDraw的設定值。

第十節 視窗應用程式

DirctDraw應用程式主要有兩種型式:視窗的和全屏的。視窗DirctDraw應用程式看起來就像一個正常的Windows程式。我們很快将讨論到全屏應用程式。

    視窗應用程式包括視窗邊界、标題框以及菜單,這些都是傳統的Windows應用程式中常見的部分。由于視窗應用程式同其他視窗一起出現在桌面上,是以它們被迫使用Windows目前所使用的分辨率和比特深度。

    視窗程式有一個主表面,但隻在進行真實頁面翻轉時才顯現。而且,主表面并不代表視窗的客戶區域(該區域在視窗邊界内)。主表面還代表整個桌面。這就是說,你的程式必須追蹤視窗的位置和大小,以便在視窗内正确顯示可見的輸出。換言之,利用視窗化的應用程式中可以在整個桌面上進行繪圖。

    如果不允許頁面翻轉,那麼圖像就必須從離屏緩沖區blt到主表面上。這就增加了圖像撕裂的可能性,因為blt比頁面翻轉速度慢。為了避免圖像撕裂,blt操作可以與監視的重新整理率保持同步。

    如果與視窗客戶區域同樣大小的離屏緩沖區被建立到顯示RAM中,視窗應用程式就可以很好地運作。這樣,視窗的内容可利用離屏表面合成。然後離屏表面可以通過硬體加速很快地到主表面上。

    由于顯示存儲器的缺乏而不得不将離屏緩沖區建立到系統RAM中時,會嚴重影響性能。不幸的是,這種情況常常發生,尤其是在隻有2MB顯示卡的時候,這是因為人們總希望為自己Windows的桌面設定高分辨的顯示模式。例如,采用1024×768×16顯示模式的主表面自己就要占用2MB的RAM。在一個2MB顯示卡上,幾乎沒有顯示RAM留給離屏表面。

    視窗應用程式的另一個問題是剪裁。一個性能良好的應用程式必須有一個連接配接到主表面的剪裁對象。這是有損性能的,原因在于為了檢查剪裁器的剪裁清單内容,blt操作隻能在一個小的矩形部分内進行。而且,不能使用優化的BltFast()函數。Bltting必須用較慢的,(而且更笨重的)Blt()函數。

    最後要講的是,視窗應用程式不允許全調色闆控制。由于Windows保留了20個調色闆項,是以在256種顔色隻有236種顔色可被設定。被Windows保留的顔色隻用系統調色闆的前10個項和後10個項。是以在給圖像上色時,隻能使用中間236個調色闆項。

第十一節全屏應用程式

包含對顯示裝置進行專有存取的DirctDraw應用程式就是全屏應用程式。這種應用程式可以任意選取顯示卡所支援的顯示模式,并享有全調色闆控制。另外,全屏應用程式還可進行頁面翻轉。是以同視窗應用程式相比,全屏應用程式速度更快,靈活性更好。

    典型的全屏應用程式首先檢查所支援的顯示模式,并激活其中一個。然後建立具有一個或更多後備緩沖區的可翻轉主表面,剩下的顯示RAM用于建立離屏表面。當顯示RAM耗盡時,就啟用系統RAM。螢幕被在後備緩沖區中合成的第一個場景更新,然後進行頁面翻轉。即使主表面占用了所有的可用的顯示RAM,全屏應用程式還可輸出執行其視窗化的副本,這是因為全屏應用程式可進行真實頁面翻轉。

    由于全屏應用程式不一定非得使用Windows的目前顯示模式,是以顯示RAM的可用與否不存在多少問題。如果隻檢測到2MB的顯示RAM,就可使用低分辨率顯示模式來保留記憶體。如果檢測到4MB的顯示RAM,應用程式就可使用要求的顯示模式并仍有保持良好性能。

    全調色闆控制也是個有利因素。這樣可以使用所有256個調色闆項而無需根據Windows保留的20中顔色重新配置設定位圖。

第十二節混合應用程式

混合應用程式既可以在全屏方式下運作也可在視窗方式下運作。混合應用程式内部非常複雜,但卻可以提供良好的性能。使用者可在視窗方式下運作,如果太慢,則可切換到全屏方式下運作。

    編寫混合應用程式最好的方法是編寫一個定制庫,該庫包括那些與應用程式使用全屏方式還是視窗方式無關的函數。dows下的消息系統負責在多任務環境中分解資訊。從應用程式的角度來看,消息是關于發生的事件的通知。使用者可以通過按下或移動滑鼠來産生這些事件,也可以是通過改變視窗大小或選擇一個菜單項等。這些事件也可以由應用程式本身産生。Windows本身也能産生消息。如“關閉Windows”消息,Windows通過這個消息來通知所有的應用程式,Windows将被關閉。

  記憶體管理

  在Windows系統中系統記憶體是最重要的共享資源之一。當同一時刻有多個應用程式在運作時,為了不耗盡系統資源,每個應用程式必須合作以共享記憶體。同時,當啟動新的程式和關閉老的程式時,記憶體會變得碎片化。通過移動記憶體中的代碼和資料塊,Windows能夠使記憶體空閑空間連起來。在Windows下也有可能超量使用記憶體。例如,應用程式可以比記憶體容量大。Windows能夠廢棄目前不使用的代碼,在以後需要時再從應用程式中将之讀入記憶體。Windows應用程式可以共享可執行檔案中的例程。包含可共享的例程的檔案稱為動态連結庫(DLL)。Windows包括了運作時将DLL例程鍊入程式的機制。

    硬體無關性

  Windows同時提供了硬體或裝置無關性,使你免于在生成程式的時候不得不考慮所有可能使用的顯示器、列印機或輸入裝置。在Windows下面,每種硬體裝置的驅動程式隻編寫一次。硬體無關性使程式設計對應用程式開發者來說更為簡單。應用程式與Windows而不是各種裝置打交道。

    動态鍵接庫

  動态鍵接庫提供了更多的Windows功能。它們通過一個有力而靈活的圖形使用者界面增強了基本的作業系統。動态鍵接庫包括一些預定義的函數,它們可以在一個應用程式被調入時與之鍵接(動态地),而不是在應用程式被建立時(靜态地)。動态鍵接庫使用DLL字尾。函數庫将每一個程式員從重複開發諸如讀取字元或格式化輸出之類的通用例程中解放出來。程式員可以友善地構造它們自己的庫以包含更多的功能,比如改變字型或檢驗文本。把函數變為通用工具減少了備援設計,這是OOP的一個關鍵特性。

       Windows的庫是被動态地鍵接的。或者說,鍵接器并不把函數拷貝到程式的可執行檔案中去。相反,當程式運作時,它産生對庫函數的調用。自然,這樣做節約了記憶體。不管有多少應用程式在運作,在RAM中總是隻有庫的一份考貝,而這個庫可以被共享。

       Windows的可執行檔案格式 

       Windows具有一種新的可執行檔案的格式,稱為New Excutable格式。它包括新型的檔案頭,能夠儲存有關DLL函數的資訊。

  第四節 windows的視窗

  Windows的視窗

  視窗看起來就是顯示裝置中的一個矩形區域,它的外觀與特定的應用程式無關,可是,對于一個應用程式來說,視窗是螢幕上應用程式能夠直接控制的矩形區域。應用程式能夠建立并控制主視窗的一切,如大小和形狀。當使用者啟動一個程式時,一個視窗就被建立了。使用者每次單擊視窗,應用程式作出響應。關閉一個視窗會使應用程式結束。多視窗帶給使用者Windows的多任務能力。通過将螢幕分為不同的視窗,使用者能夠使用鍵盤或滑鼠選擇一個并行運作的應用程式,以此對多任務環境中的一個特定程式進行輸入,Windows截取了使用者的輸入并配置設定必要的資源(例如微處理器)。

Windows的布局

  所有的Windows應用程式都具有諸如邊框、控制菜單、About對話框之類的共同特征。這些特征使得各個Windows應用程式非常類似。

邊框

    Windows的視窗被邊框所包圍。邊框由圍出視窗的線條組成。對于新手而言,邊框看起來僅僅是為了将一個應用程式的螢幕視口與其它的差別開。但是,對于熟練者,邊框有着不同的作用。例如,如果将滑鼠指針放在邊框上并按下滑鼠的左鍵,使用者就可以改變視窗的大小。

标題條

    應用程式的名字顯示在視窗頂部的标題條中。标題條總是在相關視窗頂部的中央。标題條非常有用,它可以幫助你記住正在運作哪個應用程式。活動應用的标題條以不同于非活動應用程式的顔色顯示。

控制圖示

    控制圖示是每個視窗左上方的小圖檔,每個應用程式都使用它。在控制圖示上單擊滑鼠鍵會使Windows顯示系統菜單。

系統菜單

    當用滑鼠單擊控制圖示時就打開了控制菜單。它提供了諸如Restore,Move,Size,Minimize,Maximize以及Close這樣的标準操作。

最小化圖示

    每個Windows 95或Windows NT應用程式都在視窗的右上角顯示三個圖示。最左邊的圖示是一段短下劃線,這就是最小化圖示。它可以使用程式被最小化。

最大化圖示

    最大化圖示是三個圖示中中間的那一個,看起來象兩個小視窗。使用最大化圖示可以使用應用程式占滿整個螢幕。如果選擇了這個圖示,其它應用程式視窗都會被蓋住。

垂直滾動條

    如果有必要,應用程式可以顯示一個垂直滾動條。垂直流動條顯示在應用程式視窗的右邊,在兩端有兩個方向相反的箭頭。它還有一個着色的棒和一個透明的視窗塊。後者被用于顯示目前顯示内容與整個文檔(着色的棒)的關系。你可以用滾動條來選擇顯示哪一頁。一般在任何一個箭頭上單擊一下會使顯示内容移動一行。單擊向上箭頭下方的視窗塊并拖動它會使螢幕輸出快速更新到應用程式螢幕輸出的任意位置。

水準滾動條

    也可以顯示一個水準滾動條。水準滾動條顯示在視窗的底部,具有與垂直滾動條類似的功能。你用它來選擇要顯示哪些列。一般在任何一個箭頭上單擊一個會使顯示内容移動一列。單擊向左箭頭右邊的視窗塊并拖動它會使螢幕輸出快速更新到應用程式螢幕輸出的任意位置。

菜單條

    一個可選擇的菜單條可以顯示在标題條的下方。通過菜單條來選擇菜單和子菜單。這種選擇可以通過用滑鼠單擊,也可以用熱鍵組合來實作。熱鍵組合經常是ALT與指令中帶下劃線的字母的組合,比如File指令中的“F”。

使用者區

    通常使用者區占據了視窗最大的部分。這是應用程式的基本輸出區域。應當由應用程式來複雜管理使用者區。另外,應用程式可以輸出到使用者區。

  第五節 windows的類

      視窗的基本元件有助于說明應用程式的外觀。有的時候應用程式需要建立兩個外觀和表現都相似的視窗。Windows的Paint就是一個例子。借助于同時運作Paint的兩個執行個體(或拷貝),Paint允許使用者剪貼或拷貝圖檔的一部分。然後資訊就可以從一個執行個體拷貝到另一個執行個體。Paint的每個運作執行個體的外觀和表現都與其他的相同。這就需要每個執行個體建立自己的外觀和功能類似的視窗。

    在這種情況下被建立的外觀和功能都很類似的視窗被稱為是屬于同一個視窗類的。但是,你建立的視窗可以有不同的特征。它們可以有不同的大小,不同的位置,不同的顔色或不同的标題,也可以使用不同的光标。

    每個被建立的窗都基于一個視窗類。在用C語言開發撕于的基于傳統的函數調用方式的應用程式中,一些視窗為在Windows應用程式初始化的進修注冊。你的應用程式可以注冊屬于自己的視窗類。為了能夠使幾個視窗在同一個視窗類的基礎上建立,Windows定義了一些視窗特征,如CreateWindows()的參數,另一些定義的視窗類的結構。當你注冊一個視窗類的時候,這個類可以被Windows下運作着的任何程式所使用。對于使用MFC的應用程式來說,多數注冊工作已經由預定義的對象完成了。

    具有相似的外觀和表現的視窗可以被組合成一個類,以此來減少需要維護的資訊。因為每個視窗類都有自己的可共享的類結構,不需要複制不必要的視窗類參數。同時,同類的兩個視窗使用相同的函數以及相關的例程。這樣可以節省時間和空間,因為不存在代碼複制。

  第六節 windows中的面向對象程式設計

      在Windows下傳統的C程式吸收了一些面向對象程式設計的特性。對象是一種包含資料結構和對這些資料結構進行操作的函數的抽象資料類型。而且,對象接收會引起它們不同動作的消息。

    比如,一個Windows的圖形對象是可以被作為一個實體來操縱的一些資料的集合,對于使用者它是可視界面的一部分。特别地,一個對象意味這資料和資料的功能。菜單、标題條、控制塊以及滾動條等都是圖形對象的例子。下一部分描述一些影響應用程式外觀的新的圖形對象。

    圖示

  圖示是用來使用記住特定操作、想法或産品的小圖形對象。比如,一個電子表格程式被最小化時可以顯示一個很小的柱狀圖以提醒使用者這個程式還在運作之中。在柱狀圖上輕按兩下滑鼠會使Windows激活這個應用程式。圖示是非常有力的工具。它很适合用來引起使用者的注意,比如在發出錯誤警告或者是向使用者提供選擇時。

    光标

  光标是Windows用來跟蹤指點裝置的運動的圖形符号。這種圖形符号可以改變形狀以指明特定的Windows操作。比如,當标準的箭頭光标變為沙漏光标時說明Windows正在執行一個指令,需要暫停。

    編輯光标

  應用程式在視窗中顯示編輯光标以告訴使用者在哪兒輸入。編輯光标與其他螢幕符号顯然不同,因為它是閃爍的。多數時候,滑鼠輸入與光标相連,而鍵盤輸入與編輯光标相連。但是,可以用滑鼠來改變編輯光标的輸入點。

    消息框

  消息框是另一類Windows圖形對象。消息框是一種包含标題、圖示和消息的彈出式視窗。圖(?)是關閉Windows Notepad程式時出現的一個标準的消息框。

  -------------------------------------------------------------------------

|                                                                          |  

    ------------------------------------------------------------------------

      Windows的對話框

  對話框與消息框相似的地方在于它也是一種彈出式視窗。但是對話框主要用于接受使用者輸入而不僅僅是顯示一些輸出。對話框允許應用程式接受輸入,每次一個域或是一個框的内容,而不是每次一個字元。圖(?)顯示了一個典型的Windows對話框。對知框的圖形設計由Windows為你自動完成。對話框的布局通常用編譯器中的資源編輯器完成。

    -----------------------------------------------------------------------           

   |                                                                       |

    -----------------------------------------------------------------------      

    字型

  字型是一種圖形對象或資源,它定義了完整的字元集合的字樣。這些字元都有一個特定的大小和風格,可以使文本具有不同的外觀。字樣是字元的一種基本屬性,它定義了字元的襯線和筆畫寬度。

    位圖

       位圖是一種顯示圖檔(按像素組織),存儲于記憶體。當應用程式需要快速顯示圖檔時可以使用位圖。因為位圖直接從記憶體中傳送,是以它比用程式重新畫出圖檔要快得多。位圖有兩個基本用途。首先,它可以在螢幕上顯示圖檔。其次位圖也用于建立刷子。刷子使你可以在螢幕上畫出并填充對象。

  使用位圖有兩個缺點。首先,與其尺寸有關,位圖會占據難以預估的大量記憶體。每個被顯示的像素都要在記憶體中占據相應的空間。在彩色顯示器上顯示一個像素會比在單色顯示器上占據更多的記憶體。在單色顯示器上,隻需一位(bit)就可以表示出像素的狀态。可是在可以顯示16種顔色的彩色顯示器上,需要四位才能表示一個像素的特征。同樣地,随着顯示裝置分辨率的增加,位圖對記憶體的需求也增加了。位圖的另一個缺點是它隻包括靜态的圖檔。比如,如果用位圖來代表一輛汽車,就沒有辦法來通路圖檔的不同部分,如輪踏、頂蓋、窗等。但是,如果汽車是有一系列基本繪圖例程來生成的,應用程式就可以改變向這些例程傳送的資料進而改變圖檔的不同部分。例如,應用程式可以修飾頂蓬線并把一輛轎車變為敞蓬車。

    畫筆

  當Windows在螢幕上顯示一個圖形時,它使用目前畫筆的資訊。畫筆用于畫出線條或輪廊。畫筆具有三個基本特征:線寬、線型(虛線、短線、實線)以及顔色。Windows永遠保留着用于畫白線和黑線的畫筆,任何應用程式可以使用它。你也可以建立自己的畫筆。

   刷子

  Windows用刷子來畫出顔色并以預定義的樣式來填充一個區域。刷子至少有8×8個像素大小。刷子有三個基本特征:樣式和顔色。由于它們至少有8×8的大小,刷子被稱作具有樣式而不象畫筆,稱為線型。樣式可以是純的顔色,也可以是陰影線、斜線或其它使用者自定義的組合 

  第七節 windows的消息

   Windows的消息

  在Windows中,應用程式并不直接寫螢幕、處理硬體中斷或直接對列印機輸出。相反,應用程式使用合适的Windows函數或者等待一個适當的消息被發出。

  Windows消息系統負責在多任務環境中分派消息。從應用程式的角度來看,消息可以看作是發生的事件的通知,有些需要作出特定的反應,有些就不需要。這些事件可能由使用者産生,比如按下了滑鼠或移動了滑鼠,改變了視窗的大小或者選擇了一個菜單。同時,這些事件也可能由應用程式本身所産生。

  這個過程使你的應用程式必須完全面向消息處理。當接收到消息時,應用程式必須能激活并決定正确的動作,完成這個動作之後回到等待狀态。

  通過檢查消息的格式和來源,下一部分将更仔細地讨論消息系統。

消息的格式

  消息通知一個應用程式發生了一個事件。從技術上來講,消息不僅僅是與應用程式相關,而且是與應用程式的某一特定視窗有關。是以,所有的消息都被發往視窗。

  在Windows下隻有一個消息系統-即系統消息隊列。但是,每個正在Windows下運作的應用程式都有它自己的消息隊列。系統消息隊列中的每個消息最終都要被USER子產品傳送到應用程式的消息隊列中去。應用程式的消息隊列中存儲了程式的所有視窗的全部消息。

  不管消息具有什麼類型,它們都有四個參數:一個視窗句柄,一個消息類型,兩個附加的32位參數。視窗消息中定義的第一個參數是消息所關聯的視窗句柄。

  在編寫Windows應用程式的時候經常使用句柄。句柄是一個唯一的數字,它被用于辨別許多類型的對象,如菜單、圖示、畫筆和刷子、記憶體配置設定、輸出裝置甚至視窗執行個體。在Windows 95和Windows NT下面,程式的每個運作着的拷貝叫做執行個體。

  因為Windows 95和Windows NT允許你同時運作一個程式的多個執行個體,作業系統就有必要保持對這些執行個體的追蹤。這是通過賦予每個運作執行個體一個唯一的執行個體句柄來實作的。

  執行個體句柄通常被用作一個内部維護着的表的索引。通過引用表中的元素而不是實際的記憶體位址,Windows 95和Windows NT可以動态地調整所有的資源,而隻需在此資源所對應的表格位置中插入一個新的位址。

  根據一個應用程式的多個執行個體被處理的方式,記憶體資源由Windows 95和Windows NT儲存。

  應用程式的執行個體具有很重要的作用。應用程式的執行個體定義了程式的函數所需的所有對象。這包括控件、菜單、對話框以及更多的新Windows類。

  消息中的第二個參數是消息類型。這是在Windows獨有的一些頭檔案中定義的辨別符。這些頭檔案可以通過WINDOWS.H來使用。在Windows下,每個消息由兩個字元的助記符開始,跟着是下劃線,最後是一個描述符。

  最後的兩個參數提供了解釋消息所需的附加資訊。是以最後兩個參數的内容依賴于消息的類型。

    産生消息

  消息傳送概念使Windows能夠實作多任務。消息有四個基本來源。應用程式可以從使用者那兒接受消息,也可以是Windows本身,應用程式本身或者是其它應用程式。

  使用者消息包括按鍵消息、滑鼠移動、滑鼠指點或單擊、菜單選擇、滾動條的定位等。應用程式必須花費大量的時間來處理使用者消息。使用者産生的消息表明運作程式的人希望改變應用程式的表現方式。

  無論何時,如果狀态發生改變,将會有一個消息被發往應用程式。一個例子是使用者單擊了應用程式的圖示,表明他們想要将此應用程式變為活動的應用程式。在這種情況下,Windows告訴應用程式它的主視窗被打開了,它的大小和位置被改變了等等Windows産生的消息可以被處理,也可以被忽略,這跟應用程式目前的狀态有關。

       相應消息

       在傳統的面向過程的C語言Windows應用程式中,對于遇到的每一種消息,它都有一個相應的過程來處理這消息。不同的視窗對相同的消息會産生不同的響應。Windows把每個消息發送到應用程式的不同視窗,而不同的視窗對相同的消息會有不同解釋。不令應用程式需要不同的過程來處理每一種消息,每一個視窗也應有不同的過程來處理不同的消息。視窗過程集合了應用程式的所有消息處理過程。

       消息循環

       所有Windows應用程式的一個基本組成就是消息處理循環。每一個C應用程式都在内部執行這個操作。C應用程式包含了建立并初始化視窗的過程,随後是消息處理循環,最後是結束應用程式所需的一些代碼。消息循環負責處理Windows發給主程式的消息。在這兒,程式知道有了消息,并且要求Windows将消息發送到合适的視窗過程以供處理。當消息被接受時,視窗過程就執行希望的動作。

  第八節 windows的函數

  Windows向應用程式開發人員提供了數以百計的函數。這些函數的例子包括DispatchMes-sage(),PostMessage(),RegisterWindowMessage()以及SetActiveWindow()。對于使用基礎類庫的C++程式員,許多函數自動被運作。

    在16位的Windows 3.x下的函數聲明包括一個pascal修飾符,這在DOS下更為有效Windows95和Windows NT下的32位應用程式不再使用這個修飾符。如你所知,所有Windows函數的參數是通過系統來傳遞的。函數的參數從最右邊的參數開始向左壓入棧,這是标準的C方式。在從函數傳回之前,調用過程必須按原來壓入棧的位元組數調整棧指針。

  第九節 windows應用程式架構

  Windows頭檔案:WINDOWS.H

       WINWS.H頭檔案(以及其它相關檔案)是所有程式的内在部分。傳統上,WINDOWS.H是所有C語言編寫的Windows應用程式必需的一部分。當在C++中使用基礎類庫時,WINDOWS.H包括在AFXWIN.H頭檔案中。

        Windows應用程式的組成

  在開發Windows應用程式的過程中有一些重要的步驟:

  *用C語言編寫WinMain()函數和相關的視窗函數,或者在C++中使用基礎類,比如CWinApp等。

      *建立菜單、對話框和其它資源并把它們放入資源描述檔案。

      *(可選)使用Vinsual C++編譯器中的企業編輯器來建立對話框。

      *(可選)使用Vinsual C++編譯器中的企業編輯器來建立對話框。

      *用項目檔案來編譯并連結所有的C/C++源程式和資源檔案  

  Windows應用程式中的組成部分

      1. WinMain()函數

       Windows 95和Windows NT需要一個WinMain()函數。這是應用程式開始執行和結束的地方。

      從Windows向WinMain()傳遞四個參數。下面的代碼段示範了這些參數的使用:

     int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPreInst,

          LPSTR 1pszCmdLine, int nCmdShow)

      第一個參數hInst包含了應用程式的執行個體句柄。當應用程式在Windows下運作時,這個數字唯一辨別了應用程式。

      第二個參數hPreInst将始終是一個NULL值,表明沒有這個應用程式的其它執行個體正在運作,因為在Windows 95和Windows NT下每個應用程式都在它自己單獨的位址空間中運作。

       第三個參數1pszCmdLine是指向一個以'/0'結尾的字元串的長指針,這個字元串代表了應用程式的指令行參數。

       WinMain()的第四個參數是nCmdShow。在nCmdShow中存儲的整數代表了Windows預定義的許多常量中的一個,它決定了視窗顯示的方式。

       2. WNDCLASS

       WinMain()負責注冊應用程式的主視窗類。每個視窗類都建立在一些使用者選擇的風格、字型、标題字、圖示、大小、位置等的基礎上。視窗類實際上是定義這些屬性的一個模闆。

  基本上,所有的Windows類定義都使用相同的标準C/C++結構。下面的例子是一個說明WNDCLASSW結構的typedef語句,WNDCLASS是從這兒繼承的:

     typedef struct tagWNDCLASSW

          UINT      style;

          WNDPROC   1pfnWndProc;

          int       cbClsExtra;

          int       cbWndExtra;

          HANDLE    hInstance;

          HICON     hIcon;

          HCURSOR   hCursor;

          HBR8USH   hbrBackground;

          LPCWSTR   1pszMenuName;

          LPCWSTR   1pszClassName;

          WNDCLASSW,*PWNDCLASSW,NEAR*NPWNDCLASSW,            FAR*LPWNDCLASSW;

    下面的部分讨論了WNDCLASS結構中的不同的域。其中有些域可以被賦予NULL,告訴Windows使用預設的預定義值。

    style:style域指明了類風格。

  1pfnWndProc:接受一個指向視窗函數的指針,它将執行所有的視窗任務。

  cbClsExtra:指定了必須在視窗類結構後面配置設定的位元組數,它可以是NULL。

  cbWndExtra:指定了必須在視窗執行個體後面配置設定的位元組數,它可以是NULL。

  hInstance:定義了注冊視窗類的應用程式執行個體。它必須是一個執行個體句柄,不得是NULL。

  hIconhIcon:劃定利用視窗最小化時顯示的圖示。它可以是NULL。

  hCursorhCursor:定義了應用程式使用的光标。這個句柄可以是NULL。

       hbrBackground:提供了背景刷子的辨別符。

       1pszMenuName:是指向一個以空字元結尾的字元串的指針。這個字元串是菜單的資源名。這一項可以為NULL。

       1pszClassName:是指向一個以空字元結尾的字元串的指針。這個字元串是視窗類的名字。

       3.WNDCLASSEX

       Windows提供了一種擴充的WNDCLASS定義,名為WNDCLASSEX,它允許應用程式使用小圖示。下面是WNDCLASSEX結構的定義:

      typedef struct WNDCLASSEX

          UINT      style;

          WNDPROC   1pfnWndProc;

          int       cbClsExtra;

          int       cbWndExtra;

          HANDLE    hInstance;

          HICON     hIcon;

          HCURSOR   hbrBackground;

          LPCTSTR   1pszMenuName;

          LPCTSTR   1pszClassName;

          HICON     hIconSm;

       WNDCLASSEX;

      你可以看到這兩個結構是相同的,除了WNDCLASSEX包括了hIconSm成員,這是與視窗類有關的小圖示的句柄。

   4.定義視窗類

     應用程式可以定義它們自己的視窗類,隻要先定義一個合适類型的結構,然後用視窗類的資訊來填充結構的域。

    下面的代碼示範了如何定義并初始化一個WNDCLASS結構。

     char szProgName[]="ProgName";

          .

          .

          .

     WNDCLASS wcApp;

          .

          .

          .

     wcApp.1pszClassName=szProgName;

     wcApp.hInstance=hInst;

     wcApp.1pfnWndProc=WndProc;

     wcApp.hCursor=LoadCursor(NULL,IDC-ARROW);

     wcApp.hIcon=NULL;

     wcApp.1pszMenuName=szAppIName;

     wcApp.hbrBackground=GetStockObject(WHITE-BRUSH);

     wcApp.style=CS-HREDRAW| CS-VREDRAW;

     wcApp.cbClsExtra=0;

     wcApp.cbWndExtra=0;

     if(!RegisterClass (&wcApp))

          return 0;

       WNDCLASS結構中的第二個域是wcApp.hInstance,它被賦予了WinMain()被激活後傳回的hInst的值。這指明了應用程式的目前執行個體。1pfnWndProc被賦予執行所有視窗任務的視窗函數的指針位址。對于大部分應用程式,這個函數叫做WndProc()。

  注意:WndProc()是一個使用者定義而不是預定義的函數名。在指派語句之前必須給出函數原型。

       wcApp.hCursor域被賦予執行個體的光标句柄。

       當wcApp.1pszMenuName被賦予NULL值的時候,Windows就認為這個視窗類沒有菜單。如果有,菜單必須有一個名字,它必須出現在引号裡面。GetStockOject()函數傳回一個刷子句柄,用于在這個類建立的視窗使用者區中畫出背景色。

    wcApp.style視窗類風格被設為CS-HREDRAW或CS-VREDRAW。

    最後的兩個域,weApp.cbClsExtra以及wcApp.cbWndExtra經常被設為0。這些域可以被選用以指明視窗結構和視窗資料結構後面應該保留的附加位元組數。

   下面這段代碼用于注冊視窗類:

     if(!hpreInst)

          .

          .

          .

     if(! RegisterClass(&wcApp))

          return FALSE;

    Windows 95和Windows NT通過檢查hPreInst的值來确定多少個執行個體,而hPreInst總是NULL,是以就注冊視窗類.

       5.建立視窗

       視窗通過調用CreateWindow()函數來建立。這個過程對所有版本的Windows都是一樣的。視窗類定義了視窗的一般特征,允許同一個視窗類被用于多個不同的視窗,CreateWin-dow()函數的參數指明了關于視窗的更詳細的資訊。

    CreateWindow()函數的參數資訊包括以下内容:視窗類、視窗标題、視窗風格、螢幕位置、視窗的父句柄、菜單句柄、執行個體句柄以及32位的附加資訊。在大部分應用程式中 ,這個函數會是下面這個樣子:

     hWnd=CreateWindow(szProgName,"Simple Windows Program",

          WS-OVERLAPPEDWINDOW,CW-USEDEFAULT,

          CW-USEDEFAULT,CW-USEDEFAULT,

          CW-USEDEFAULT,(HWND)NULL,(HMENU)NULL,

          (HANDLE)hInst,(LPSTR)NULL);

    第一個域szProgName(已賦過值)定義了視窗的類,後面是視窗标題條上使用的标題。視窗的風格是第三個參數

    下面的六個參數代表了視窗的x、y坐标和x、y方向的大小,然後是父視窗句柄和視窗菜單句柄。每個域都被賦予一個預設值。hInst域包含了程式的執行個體句柄,後面是一個附加參數(NULL)。

    顯示和更新視窗

  在Windows下,ShowWindow()函數被用來實際顯示一個視窗。下面的代碼示範了這個函數:

     Show Window(hWnd,nCmdShow);

    在調用CreateWindow()時生成的視窗句柄被用作hWnd參數。ShowWindow()的第二個參數是nCmdShow,決定了視窗被如何顯示。這個顯示狀态也被稱為視窗的可視狀态。

    顯示視窗的最後一步是調用Windows的Update Window()函數。

     UpdateWindow(hWnd);

    6.消息循環

     一旦調用Win-Main()函數并顯示了視窗,應用程式就需要一個消息處理循環。最常用的實作方法是使用一個标準的while循環:

     while (GetMessage (&lpMsg,NULL,0,0))

     {

          TranslateMessage(&lpMsg);

          DispatchMessage(&lpMsg);

     }

       GETMESSAGE()函數:應用程式要處理的下一個消息可以通過調用Windows的GetMessage()函數來取得。

       NULL參數訓示函數取回這個應用程式的任何視窗的任何消息。最後兩個參數0和0告訴GetMessage()不要使用任何消息過濾器。消息過濾器能夠将接收到的消息限制在一個明确的範圍之内,如鍵盤消息或滑鼠消息等。

    一般應用程式應該确認通向消息循環的所有步驟都已經正确地執行過了。這包括确認每個視窗類都已經注冊過,都已經被建立。否則,一旦進入了消息循環,隻有一個消息能夠結束這個循環。無論何時處理了WM-QUIT消息,傳回值是FALSE。這會引發主循環關閉例程。WM-QUIT消息是應用程式退出消息循環的唯一途徑。

    TRANSLATEMESSAGE()函數:通過TranslateMessage()函數,虛拟消息可以被轉換為字元消息。

    DISPATCHMESSAGE()函數:Windows通過DispatchMessage()函數将目前的消息發送到正确的視窗過程。

*******    視窗函數

       所有的應用程式都必須包括一個WinMain()函數和一個回調視窗函數。因為一Win-dows應用程式從不直接通路任何視窗函數,每個應用程式都必須向Windows提出請求以執行規定的操作。

       一個回調函數在Windows中注冊,當Windows要對一個視窗進行操作時,它就被調用。各個應用程式的回調函數的實際代碼長度會大不相同。視窗函數本身可以非常小,隻處理一個或兩個消息,也可以非常大而且複雜。

    下面的代碼段(不完整的應用程式說明語句)顯示了在應用程式中的回調視窗函數WndProc()的一個範例:

     LRESULT CALLBACK WndProc(HWND hWnd,UNIT messg,

               WPARAM wParam,LPARAM 1Param)

          HDC hdc;

          PAINTSTRUCT ps;

          switch(messg)

               case WM-PAINT:

                    hdc=BeginPaint(hWnd,&ps);

                         .

                         .

                         .

                    ValidateRect(hWnd,NULL);

                    EndPaint(hWnd,&ps);

                    break;

               case WM-DESTROY:

               postQuitMessage(0);

               break;

          default:

               return(DefWindowProc(hWnd,messg,wParam,1param));

          return(0);

    Windows希望視窗類結構定義中wcApp,1pfnWndProc域的名字能夠與回調函數的名

字比對。後面用這個視窗類建立的所有視窗的回調函數都應該用WndProc()的名字。

    下面的代碼段讨論一個視窗類結構中回調函數名的位置和指派:

               .

               .

               .

          wcApp.1pszClassName=szProgName;

          wcApp.hInstance=hInst;

          wcApp.1pfnWndProc=WndProc;

               .

               .

               .

        Windows有向百個消息可以發送給視窗函數。這些消息用“WM-”打頭的辨別符來

辨別。

       WndProc()的第一個參數是hWnd。hWnd包含了Windows發送消息的視窗句柄。

        函數的第二個參數messg按WINUSER.H中的定義指明了即将被處理的實際消息。最後的兩個參數wParam以及1Param,指明了處理每個消息所需的附加資訊。

    WndProc()函數繼續定義了兩個變量:hdc指明了顯示裝置句柄,ps指明了存儲使用者區

資訊所需的一個PAINTSTRUCT結構。

    回調函數被用于檢查将被處理的消息并選擇執行适當的動作。這個選擇過程通常在一個标準的C語言的    ?  <N`                   F:\遊戲資料集合\遊戲核心程式設計\Chapter6.txt                                                                                                                                                                                                                           F:\遊戲資料集合\遊戲核心程式設計\.Chapter6.txt.map                                                                                                                                                                                                                      F:\遊戲資料集合\遊戲核心程式設計\.Chapter6.txt.blk                                                                                                                                                                                                                      qm     ㄡg         槝  罉  ㄡg         l  X  ㄡg       ,  d  ㄡg         寴  悩  ㄡg         4  4                       等待下載下傳           等待下載下傳          M   F:\遊戲資料集合\Visual C++ 6_0進階程式設計技術·Visual C++ 6__10672634\000073.pdg                                                                                                                                                                                                                                                                                                                                                                                                                                           v  ?p      ?  DirectSound

第一節 關于聲音

聲音是空氣的一系列振蕩,稱為聲波,一般可以用二維的波形圖來表示。數字音頻是指使用某種裝置将聲波記錄下來并儲存為一種數字化的檔案。播放相應的檔案就可以産生某種聲音效果。數字音頻的音質随着采樣頻率及所使用的位數不同而有很大的差異。是以,了解所使用音頻檔案格式的有關标準是很有必要的。例如,CD中的音頻是16位,采樣頻率達到44.1MHz的立體聲數字音頻。

在所有聲音檔案的格式中,WAV是最普遍的。這是Windows平台上最常見的格式,由微軟公司創造。支援8位和16位的音質、多樣本、對立體聲和單聲道音頻均可播放。它還支援多種音頻壓縮算法。

要在遊戲中取得好的聲音效果,例如,使用3D音效,可以有兩種方法來實作:一是使用一定的工具軟體對聲音檔案進行處理,生成播放效果足夠好的檔案,然後在遊戲程式中直接将這樣的檔案播放。顯然,這樣比較簡單,但是不靈活。如果需要音效随着遊戲場景的變化而不斷改變,且不受所具有聲音檔案數量的限制,就需要進行實時混音了。

第二節DirectSound結構

DirectSound的功能子產品包括播放、聲音緩沖區、三維音效、音頻抓獲、屬性集等。

DirectSound playback建構于IDirectSound COM接口之上。IDirectSoundBuffer,IDirectSound3DBuffer和IDirectSound3DListener接口則用以實作對聲音緩沖區和三維音效的操作。

DirectSound capture建構于IDirectSoundCapture和IDirectSoundCaptureBuffer COM接口之上。

其它的COM接口,如IKsPropertySet,使應用程式能夠從聲霸卡的擴充功能中最大地受益。

最後,IDirectSoundNotify接口用于在播放或音頻抓獲達到一定地方時向産生一個事件。

第三節 播放功能概述

DirectSound緩沖區對象表示一個包含聲音資料的緩沖區,這些資料以PCM格式被存儲。該對象不僅可以用于開始、停止或暫停聲音的播放,還能夠設定聲音資料中諸如頻率和格式等屬性。

緩沖區分為主緩沖區和副緩沖區。主緩沖區中是聽者将要聽到的音頻信号,一般是将副緩沖區中信号混音後的結果。而副緩沖區中存放着許多單獨的聲音信号,有的可以直接播放,有的要混音,有的循環播放。主緩沖區由DirectSound自動建立,而副緩沖區需由應用程式來建立。DirectSound将副緩沖區中的聲音混合後,存入主緩沖區,再輸出到相應播放裝置。

DirectSound中沒有解析聲音檔案的功能,需要您自己在應用程式中将不同格式的聲音信号改變過來(PCM)。

緩沖區可以在主機闆的RAM、波表存儲器、DMA通道或虛拟存儲器中。

多個應用程式可以用同一聲音裝置來建立DirectSound對象。當輸入焦點在應用程式中發生變化時,音頻輸出将自動在各個應用程式的流之間切換。于是,應用程式不用在輸入焦點改變中反複地播放和停止它們的緩沖區。

通過IDirectSoundNotify接口,當播放到了一個使用者指定的地方,或播放結束時,DirectSound将動态地通知擁護這一事件。

第四節 音頻抓獲概述

DirectSoundCapture對象可以查詢音頻抓獲裝置的性能,并為從輸入源抓獲音頻而建立緩沖區。

其實,在Win32中早已經有了抓獲音頻的功能,而目前的(版本5)DirectSoundCapture與隻比較并沒有什麼新的功能。不過,DirectSoundCapture API使您能夠編寫使用相同接口的播放和音頻抓獲程式,而且,這也為将來可能出現的API改進提供了原始模型,使您可以從中受益。

DirectSoundCapture還能夠抓獲壓縮格式的音頻。

DirectSoundCaptureBuffer對象表示一個用于抓獲音頻的緩沖區。它可以循環利用,也就是說,當輸入指針達到緩沖區的最後時,它會回到開始的地方。

DirectSoundCaptureBuffer對象的各種方式使您能夠設定緩沖區的屬性、開始或停止操作、鎖定某部分存儲器(這樣就可以安全地将這些資料儲存或用于其它目的)。

與播放類似,IDirectSoundNotify接口使在輸入指針到達一定地方時通知使用者。

第五節 初始化

對于一些簡單的操作,可以使用預設的首選裝置。不過,在遊戲的制作中,我們可能還是需要知道一些特定的聲音裝置。于是,您應該先列舉出可用的聲音裝置。

在此之前,您需要先設定一個回收函數,在每一次DirectSound發現新裝置後調用該函數。函數中您可以做任何事情,但您必須将它定義得與DSEnumCallback形式相同。如果希望列舉繼續,函數應傳回真,否則傳回假。

下面的例程來自CD光牒Example目錄下的Dsenum.c檔案。它列舉可用的裝置并在一個清單框中增加一條相應的資訊。首先是他的回收函數:

BOOL CALLBACK DSEnumProc(LPGUID lpGUID, 

                         LPCTSTR lpszDesc,

                         LPCTSTR lpszDrvName, 

                         LPVOID lpContext )

    {

    HWND   hCombo = *(HWND *)lpContext;

    LPGUID lpTemp = NULL;

    if( lpGUID != NULL )

繼續閱讀