9/24/2010

無痛地開發DirectShow應用程式 (1)

說起DirectShow,我就有難以忘懷的經驗。剛進現在的公司時,就被指派了一支以DirectShow寫就的程式。集錄音、錄影、播放於一身,此外還具有人臉辨識功能。伴隨著這些強大又雞肋的功能的,就是像山一樣多的Bug了。整整一個多月,每天與Bug奮戰到凌晨三點的結果,就是與新人這種身份不相稱的,1.5倍左右的薪水跟成就感。

DirectShow的前身是VFW (Video for Windows)。本來是微軟開發出來要與QuickTime對打的產品。在加入DirectX家族好幾年後,又被獨立出來,整合進Windows產品線內。其重要性可見一斑。

在開發時,它稱為Quartz;而在剛推出時,又取名為ActiveMovie。雖然有這一段小小的不起眼的因緣,持續的時間也不是很長,卻深深地影響了其SDK內某些東西的命名方式。初學者看到這些符號時,常常丈二金剛摸不著頭腦。

DirectShow最讓人頭痛的就是那個很囉嗦的COM架構。有相關經驗的人應該對CoCreateInstance、QueryInterface或Release之類的函式不陌生。在DirectShow的世界裡,物件並不是直接曝露在設計者面前的,取而代之的是僅提供部份功能的介面。要使用某個物件,得要先呼叫CoCreateInstance將它建立,抽取出第一個介面。要使用另一個面向的的功能的話,要嘛使用之前抽取出來的介面所提供的函數,間接地抽取相關的介面;要麻利用IUnknown::QueryInterface來查詢所需介面。最後,用不著的介面還得要用IUnknown::Release來釋放掉。這些東西使用起來並不直觀,程式碼寫起來也繁瑣。又因為使用了指標及獨立的堆積,根本不能使用C/C++的堆疊框架來幫助管理記憶體。導致大量的Release呼叫,作者辛苦是當然,對讀者來說也是很大的負擔。

所幸,有種東西叫做聰明指標(Smart Pointer)。藉由類別模板,可以很快地做出一類特別的類別,它們可以攜帶某種型別的指標,並且在摧毀時釋放掉其所指向的空間。只要將聰明指標宣告在堆疊框架內,就可以保證其指向的空間的生命週期絶不超出函數外。大大地減少記憶體管理的瑣碎程式碼。COM的介面全都以指標的形式提供給應用程式,自然也能用聰明指標來管理它。微軟所提供的ATL就有這樣的一種聰明指標CComPtr,它提供管理介面的機制,使用起來也十分地方便。在不知道有CComPtr之前,我一直是自己寫一套聰明指標來管理介面的。

以下是很簡單的使用方式:

bool CSecRecDlg::GetDev(const CLSID &cls)
{
    CComboBox *ctrl=NULL;
    CComPtr cdenum;
    CComPtr emoniker;
    CComPtr filter;
    HRESULT hr=E_FAIL;

    if(cls==CLSID_AudioInputDeviceCategory)
    {
        ctrl=(CComboBox *)GetDlgItem(IDC_AUDCAP);
        m_aud.Release();
    }
    else if(cls==CLSID_VideoInputDeviceCategory)
    {
        ctrl=(CComboBox *)GetDlgItem(IDC_VIDCAP);
        m_vid.Release();
    }
    else
        ASSERT(FALSE);

    if(ctrl->GetCount()<1)
        return false;
       
    hr=cdenum.CoCreateInstance(CLSID_SystemDeviceEnum);
    ASSERT(SUCCEEDED(hr));
    if(cdenum->CreateClassEnumerator(cls, &emoniker, 0)==S_OK)
    {
        CComPtr moniker;
        int idx=ctrl->GetCurSel();

        emoniker->Skip(idx);
        if(emoniker->Next(1, &moniker, NULL)==S_OK)
        {
            hr=moniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void **)&filter);
            ASSERT(SUCCEEDED(hr));
            cls==CLSID_AudioInputDeviceCategory?
            m_aud=filter:
            m_vid=filter;

            return true;
        }
    }

    return false;
}


在上面的程式碼裡不太有令人討厭的指標宣告,也沒有用到IUnknown::Release,整個程式看起來神清氣爽,真是可喜可賀。

以下是MSDN對於CComPtr的說明:
http://msdn.microsoft.com/en-us/library/ezzw7k98%28VS.80%29.aspx

沒有留言: