這陣子對於 DirectShow 的研究, 研究的越仔細, 就越覺得 DirectShow 的強大, DirectShow
簡化了多媒體的撥放, 格式的轉換, 也允許開發者可以自訂新的元件. 因 DirectShow 是基於
COM (Component Object Model) 所發展出來的, 其裡面的 Component 都是可以很簡單的被
使用, 但也因為是 COM, 所以要開發 DirectShow 相對的就必須要對 COM 有所了解.
COM 並不是某一種特別的應用, 而是一個你可以用來創造任何形式應用的 Model. 例如 ActiveX
, OLE 都是使用 COM 為基礎來開發, 而 windows 上常看到的 DLL 也是一個 COM 的應用,但
COM 的應用並不只有這樣而已.
"COM 定義了一個建立元件的標準". 原則上每個程式設計師都可以自訂屬於自己的元件標準,
但以這樣的前提下, 每個人所開發出來的元件就可能會有不相容的情形, 而軟體元件只有在我們
都同意使用相同定義時才會有其價值. 因為 COM 的標準, 所以只要所有程式設計師都採用此
規定的標準來實作軟體元件, 這樣這些軟體元件才可以一起運作.
COM 支援 OOP 的三個原則: Encapsulation, Polymorphism, Inheritance.
1. Encapsulation :
透過封裝, 開發者並不需要知道元件內的實作方式或是其演算法, 僅需知道如何與元件聯繫即
可, 對開發者來說, 元件模組是如何實作, 或是用哪種語言實作都不重要, 只有元件模組的
使用方式才重要.
2. Polymorphism :
"一個介面 , 多個方法". 它可以讓開發者自由的將模組給實行成他所適合的模組特性, 因此
你可以明確指示某個元件要如何工作, 而其他則由其他人自行決定要如何運作.
3. Inheritance :
允許一個元件去繼承其他元件功能的特性.
COM 所定義的 interface 中, 最重要的就是 IUnknown, 它是所有 COM 物件都必須要實作的
interface. IUnknown 定義了三個 interface : QueryInterface(), AddRef(), Release().
QueryInterface :
此介面會查詢元件是否實作了某個指定的介面, 若有實作, 則回傳此介面的指標. 如果指定的
interface 沒有被支持的話, QueryInterface() 會回傳 E_NOINTERFACE error code.
AddRef,Release :
在 COM 裡管理方式是透過 reference count 的方式, 當 count 為 0 時, 此物件就會被釋放
以下為 DirectShow 裡 CClassFactory 針對上述三個 interface 的實作
STDMETHODIMP CClassFactory::QueryInterface(REFIID riid,void **ppv)
{
CheckPointer(ppv,E_POINTER)
ValidateReadWritePtr(ppv,sizeof(PVOID));
*ppv = NULL;
// any interface on this object is the object pointer.
if ((riid == IID_IUnknown) || (riid == IID_IClassFactory)) {
*ppv = (LPVOID) this;
// AddRef returned interface pointer
((LPUNKNOWN) *ppv)->AddRef();
return NOERROR;
}
return ResultFromScode(E_NOINTERFACE);
}
STDMETHODIMP_(ULONG) CClassFactory::AddRef()
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CClassFactory::Release()
{
if (--m_cRef == 0) {
delete this;
return 0;
} else {
return m_cRef;
}
}
每一個 COM 都會有一個 GUID (Global Unique IDentifier), 譬如 :
B38847F0-4CBE-4705-B55A-D0F13552B385
GUID 可以透過 GuidGen.exe 來產生, 通常此程式會放置在 [VC/VS2005]/Common/Tools/Bin
當要使用 QueryInterface 來查詢需要的介面時, 第一個傳入參數就是此介面的 GUID
在任何 COM API 被呼叫之前, COM 的初始化是必須的, 可以透過 CoInitialize() 來達到目的.
//NotUsed 參數是被保留的, 且必須要為 NULL.
HRESULT CoInitialize(LPVOID *NotUsed);
另外當不在需要 COM 時, 需要呼叫下面函式釋放 COM.
void CoUninitialize();
除了 CoInitialize/CoUninitialize 以外, COM 也提供了 CoCreateInstance 來載入 COM
HRESULT __stdcall CoCreateInstance(
REFCLSID rclsid, //所要求元件的 CLSID
LPUNKNOWN pUnKOuter, //控制元件的 IUnknown
DWORD dwClsContext, //元件伺服器的執行背景
REFIID riid, //元件所要求的介面
LPVOID *ppv); //傳回元件介面的指標
對於 CoCreateInstance , 可能還需要在稍微簡單解釋一下, 他到底是如何運作的. 我們先大致
看一下 CoCreateInstance 的實作流程
HRESULT CoCreateInstance(...)
{
HINSTANCE hinstDll;
DLLGETCLASSOBJECT DllGetClassObject;
hinstDll = LoadLibrary("xxxx.dll");
if (successful)
{
DllGetClassObject = (DLLGETCLASSOBJECT) GetProcAddress(hinstDll,"DllGetClassObject");
if (DllGetClassObject)
hr = DllGetClassObject(...);
}
return hr;
}
上述會看到當呼叫 CoCreateInstance 時, 會有幾個動作須要完成
1. 載入所需要的 dll
2. 拿到 DllGetClassObject function pointer, 此 DllGetClassObject 是由 COM 所定義的 interface
HRESULT __stdcall DllGetClassObject(REFCLSID rclsid,
REFIID riid,
LPVOID *ppv);
呼叫完 CoCreateInstance 時, 理論上應該就可以獲得所需要介面的 pointer 進而操作此
COM 物件. 另外當我們使用 Microsoft 所提供的開發工具建立 COM 專案時, 上述的
DllGetClassObject 我們將不用自己實作, 在 COM 已經完成這些事情了, 我們只需要依照步
驟建立所需要的介面即可.