用C完畢的一個基本COM接口IFoo(一)把該文中完畢的代碼收拾彙總到一個項目中。其時僅僅完畢到一箇中心時刻,關鍵在闡明COM接口的完畢原理,還沒有包括類廠的有些。往後還需連續添加類廠等高檔功用。
文件組成:
ifoo.h COM接口IFoo,接口ID IID_IFoo 聲明文件。
outside.c COM接口完畢。這裡完畢IFoo的是一個方案體COutside.
util.h 一些宏界說、大局函數、變量聲明文件。
main.c 筆者為完畢項目添加的文件。供給main函數、內存處置函數Alloc,Free的完畢(封裝C工作庫函數malloc和free.)、接口ID界說。
COM接口究竟是啥?
COM接口是一個指向虛函數表的指針。經過這個指針能夠拜訪內存中某處的各個功用塊,實施預界說的功用,完畢用戶的使命。這些功用塊以函數的辦法存在(想不出還有其他辦法:))並被調用。它們有一個一起點:都包括一個指針參數,指向這些功用要操作的數據地址。在C++中,這個地址就是政策的首地址,也就是類成員函數中隱含的this指針。在C函數中並沒有這種現成的便當,因而代碼完畢中在接口界說時仍運用了接口指針(HRESULT (__stdcall * QueryInterface) (IFoo * This, const IID * const, void **)),而在接口函數完畢時依據方案體方案方案,從這個接口指針核算得到政策實例指針。
typedef struct IFoo{ struct IFooVtbl * lpVtbl;} IFoo;typedef struct IFooVtbl IFooVtbl;struct IFooVtbl{
HRESULT (__stdcall * QueryInterface) (IFoo * This, const IID * const, void **) ; ULONG (__stdcall * AddRef) (IFoo * This) ; ULONG (__stdcall * Release) (IFoo * This) ;
HRESULT (__stdcall * SetValue) (IFoo * This, int) ; HRESULT (__stdcall * GetValue) (IFoo * This, int *) ;};
COM接口的需求:
每一個COM接口(指向的虛函數表)的頭三個函數有必要是IUnknown接口的函數:QueryInterface,AddRef和Release.在C++中,稱為從IUnknown接口承繼。
關於調用QueryInterface照顧查詢IID_IUnknwon得到的接口指針值,同一個政策完畢的悉數接口有必要一樣。這是差異兩個COM政策是不是是同一個政策的標準。
宏界說“#define IUNK_VTABLE_OF(x) ((IUnknownVtbl *)((x)->lpVtbl))”闡明在預處置輸出文件main.i中能夠找到IUnknownVtbl和IFooVtbl的聲明:
typedef struct IUnknownVtbl {
HRESULT ( __stdcall *QueryInterface )( IUnknown * This, const IID * const riid, void **ppvObject);
ULONG ( __stdcall *AddRef )( IUnknown * This);
ULONG ( __stdcall *Release )( IUnknown * This);
} IUnknownVtbl;
struct IUnknown { struct IUnknownVtbl *lpVtbl; };
struct IFooVtbl{
HRESULT (__stdcall * QueryInterface) (IFoo * This, const IID * const, void **) ; ULONG (__stdcall * AddRef) (IFoo * This) ; ULONG (__stdcall * Release) (IFoo * This) ;
HRESULT (__stdcall * SetValue) (IFoo * This, int) ; HRESULT (__stdcall * GetValue) (IFoo * This, int *) ;};
該宏界說的作用就是把IFoo接口中的IFooVtbl類型的指針拿出來((x)->lpVtbl)),並強逼轉換((IUnknownVtbl *))成IUnknownVtbl.
“強逼轉換”的作用是啥呢?是怎麼做到的呢?
很明顯,作用就是得到的指針不再是IFooVtbl *類型,而是變成了IUnknownVtbl *類型。至於做法,體系大概記載每一個變量、表達式的類型。當進行強逼類型轉換時,就(暫時地)修改其類型為轉換到的類型。
同理,QueryInterface, AddRef, Release宏界說中的(IUnknown *)也是這種用法。
能夠看到,宏“IUNK_VTABLE_OF”的作用是供宏QueryInterface,宏AddRef,宏Release引證,把IFooVtbl *類型轉換為IUnknownVtbl *類型,完畢抵達調用IUnknownVtbl中界說的三個QueryInterface,AddRef,Release函數。
那麼,這種大費周章的意圖是啥呢?為何不以IFooVtbl中三個函數的界說辦法(不經過強逼轉換來轉換成有必要的類型),直接調用IFooVtbl中界說的函數呢?儘管強逼轉換在參數值上並不會構成改動,完畢調用的也是IFooVtbl界說的函數(FooQueryInterface,FooAddRef,FooRelease)。
為何一定要經過IUnknown接口指針調用這三個函數呢?修改QueryInterface宏界說如下:
#define QueryInterface(pif, iid, pintf) \
(((pif)->lpVtbl)->QueryInterface(pif, iid, (void **)(pintf)))
即經過IFoo接口指針來調用由IUnknown引進的函數,有啥不對的當地嗎?
實驗標明,將QueryInterface宏界說如下也能夠編譯經過,實施起來也沒有呈現任何異常。
#define QueryInterface(pif, iid, pintf) \
(((pif)->lpVtbl)->QueryInterface(pif, iid, (void **)(pintf)))
關於IUnknown接口的三個函數,調用時傳遞的參數是IUnknown *類型(見QueryInterface, AddRef, Release宏界說),而函數界說中(FooQueryInterface, FooAddRef, FooRelease)聲明的參數是IFoo *類型,這種紛歧起的狀況是怎麼呈現的?這種紛歧起不會有疑問嗎?
這種紛歧起的發生是由於從紛歧樣的視點看待致使的。若是從IUnknown接口來看,那麼接口函數中的第一個參數類型就是IUnknown *;若是從IFoo來看,那麼第一個參數的類型就是IFoo *.
這種紛歧起性僅僅鍼關於編譯器關於類型的編譯需求有意義的,在接口完畢及運用時,傳遞給lpVtbl->QueryInterface, lpVtbl->AddRef,lpVtbl->Release的第一個參數在值上都是一樣的,都是完畢該接口的內存地址(在本例中是COutside政策的首地址)。一些語法表象回想
函數指針變量界說、賦值及調用。
HRESULT (__stdcall * pQI) (IFoo * This, const IID * const, void **) ;
界說一個函數指針變量pQI,該變量指向“回來HRESULT,取3個參數別離為類型IFoo *,const IID * const, void **”的函數。
typedef HRESULT (__stdcall * QIType) (IFoo * This, const IID * const, void **) ;
界說一個函數指針類型,該類型的指針指向“回來HRESULT,取3個參數別離為類型IFoo *,const IID * const, void **”的函數。
HRESULT __stdcall QueryInterface(IFoo * This, const IID * const, void **) ;//函數聲明示例pQI = 0; // 函數指針賦值,0標明不指向任何函數。pQI = QueryInterface; // 函數指針賦值,pQI指向QueryInterface。pQI = &QueryInterface; // 與上面等價。
QueryInterface(&this->ifoo, riid, ppv); // 運用函數名直接調用pQI(&this->ifoo, riid, ppv); // 函數指針調用(*pQI)(&this->ifoo, riid, ppv); // 第二種函數指針調用辦法
宏界說、翻開規矩
關於宏,一向有一種水中望月的感受,好像很隨意,怎麼來都行,比方:
#define AddRef(pif) \
(IUNK_VTABLE_OF(pif)->AddRef((IUnknown *)(pif)))
宏界說大概是能夠嵌套的,即宏界說的“內容”中還能夠包括(嵌套)宏,如本例,“IUNK_VTABLE_OF”就是嵌套宏。在翻開的時分,將嵌套的宏也一起翻開(替換成界說的內容),直到不再有宏間斷。
那麼就有兩個疑問:
1.若是被嵌套的宏包括(直接或直接)界說的宏,那麼翻開就沒完沒了,死循環了。
2.若是界說的內容中有跟界說的宏同名的字符串(比方上面的比方IUNK_VTABLE_OF),那麼怎麼差異這同名的東東是嵌套的宏(需求翻開),仍是通常的字符串(不需求翻開)?
函數調用標準約好、main函數調用標準。
一開始把幾個文件彙總到項目裡時,編譯通不過,過失提示大致意思是,不能把一種調用標準的函數指針轉換成另一種調用標準的函數指針。後來把調用標準改為 /Gz(__stdcall),編譯為(Compile As)改為/TC(Compile As C Code)就好了。
想來是關於。c文件,編譯器缺省運用的是__cdecl,而IFoo中的接口宏界說在win32下翻開成了__stdcall,所以呈現了仇視。而運用/Gz強逼未聲明調用標準的函數運用__stdcall,完畢就與聲明一起了。
(size_t)&(((s *)0)->m)
c++程序員或許都曉得,拜訪地址“0”處的成員是一大忌,會構成GP.可是,取地址“0”處的成員的地址,卻是個合法的操作。儘管地址“0”處並沒有啥內容,可是,若是在地址0處寄存一個內容,那麼該內容中的成員也是有地址的。本例中正是美好地運用這種辦法,從接口地址核算得出完畢該接口的實例地址,進而拜訪實例的內部變量。用C完畢的一個基本COM接口IFoo(二)在C完畢COM接口系列1中完畢的com接口IFoo與運用它的客戶耦合在一起,沒有完畢在各自別離的模塊,因而不符合模塊化編程思維。本期添加類廠支撐,以使接口的完畢與接口的運用相別離。
---------------------------------------------------
類廠的作用究竟是啥?
將接口的完畢與客戶運用別離開來嗎?
不盡然。運用CoCreateInstance,客戶能夠徹底不用曉得類廠的存在,而創立組件,獲取組件完畢的接口並運用。
即COM庫能夠徹底拋開類廠的概念,而是供給一個這樣的函數原型:CoCreateObject(REFID rclsid,……,REFID riid,void **ppItf);用戶在調用的時分能夠對riid供給IID_Unknown或許特定於該政策的一個接口,直接獲取該政策的IUnknown或特定的接口指針。
能夠看到,這正是CoCreateInstance所作的工作。
1 類廠供給了直接創立類政策的辦法:用戶能夠先獲取並持有類廠接口指針,經過該指針所指向的類廠接口創立類政策。適用於需求創立多個(或重複創立)類政策的當地,削減了每次都要定位政策庫並把政策庫裝入內存的開支。
2 類廠供給了確保組件庫留在內存不被卸載出去的另一種辦法:類廠接口函數LockServer.組件庫保護一個庫計劃計數器,只需該計數器為0時,組件庫才容許自個被卸載出內存。(與此相對,引證計數是類政策計劃的,經過該類完畢的各個接口來保護。若是一個類政策的引證計數抵達0,那麼該政策佔有的內存就被開釋,該政策上的接口指針也不再有用。)
除了調用LockServer判定組件庫以外,當創立的組件個數大於0時,組件庫也不能被卸載。也能夠說,調用一次LockServer()的作用相當於創立了一個組件。
-----------------------------------------------------------------------
客戶一側:1 運用一個接口需求曉得哪些信息?
備選:接口IID類政策(類廠)CLSID(或ProgID)
接口函數原型(參數個數,類型,回來值)
完畢接口組件的線程模型(進程內、進程外、長途)?
類型庫typelib信息?
效能一側:2 完畢一個組件和接口以供客戶調用,需求供給哪些東西?
備選:悉數客戶運用組件和接口所需的內容額定的還有:
--------------------------------------------------------------------
為dll添加。def文件與直接在需求導出的函數界說處指定_declspec( dllexport )有差異嗎?若是有是啥差異?我發如今outdll.c中這樣指定:
__declspec( dllexport ) HRESULT DllGetClassObject (REFCLSID rclsid, REFIID riid, void **ppv)
會發生編譯過失:
1>------ Build started: Project: outside, Configuration: Debug Win32 ------1>Compiling...1>outdll.c1>d:\outside-cf\outside\outdll.c(19) : error C2375: 'DllGetClassObject' : redefinition; different linkage1> c:\program files\microsoft visual studio 8\vc\platformsdk\include\objbase.h(833) : see declaration of 'DllGetClassObject'1>Build log was saved at "file://d:\outside-cf\outside\Debug\BuildLog.htm"1>outside - 1 error(s), 0 warning(s)========== Build: 0 succeeded, 1 failed, 1 up-to-date, 0 skipped ==========
c2375的闡明意思是犯錯的函數運用的聯接指示符與之前聲明的紛歧樣。
Compiler Error C2375
'function' : redefinition; different linkage
The function is already declared with a different linkage specifier.
objbase.h中聲明白DllGetClassObject()函數:
STDAPI DllGetClassObject(IN REFCLSID rclsid, IN REFIID riid, OUT LPVOID FAR* ppv);
而運用。def文件就沒有疑問。初度實施作用:
疑問就是總有一個分配的內存沒有開釋:
依據打印出來的內存地址能夠差異,大概是先創立的類廠政策的內存沒有開釋。
查看代碼,main()中並沒有忘記調用Release(pCF)開釋類廠政策。打印Release(pCF)的回來值,發現是1,即在類廠接口指針上少調用了一次Release,那麼,究竟是哪裡少的呢?
main()函數中有關類廠政策引證計數的當地就是CoGetClassObject和Release(CreateInstance跟類廠自個的引證計數無關),這是一對添加引證計數和削減引證計數的對應操作,所以,main()中大概沒有疑問。
那麼,就只需創立類廠政策的時分了。下面看一下類廠政策是如何創立的。
首要,main調用CoGetClassObject,該函數就調用dll中的DllGetClassObject.由於是初度調用(不思考其他客戶運用該dll的狀況),程序實施到CreateClassFactory(……),該函數實施完後,類廠政策的引證計數是1.
由於創立成功,因而持續向下實施到QueryInterface,此刻,類廠政策的引證計數變成了2.然後,DllGetClassObject回來,com庫函數CoGetClassObject也大概回來。注意,此刻的類廠政策引證計數已經是2了!因而,疑問就出在這裡。main調用一次CoGetClassObject後,類廠政策的引證計數是2,而不是我想向中的1.所以,後邊調用一次Release也就當然無法開釋掉類場政策了。
1 HRESULT DllGetClassObject (REFCLSID rclsid, REFIID riid, void **ppv)
2 {
3 *ppv = 0;
4 if (IsEqualCLSID (rclsid, &CLSID_Outside))
5 {
6
7 if (!vpcfOutside)
8
9 {
10
11 HRESULT hr = CreateClassFactory (&CLSID_Outside, CreateOutside,
12 &IID_IClassFactory, &vpcfOutside);
13
14 if (hr != NOERROR)
15
16 return hr;
17 }
18
19 return QueryInterface (vpcfOutside, riid, ppv);
20
21 }
22
23 return E_FAIL;
24 }找到了緣由,改正就很簡略了。這裡我覺得需求把DllGetClassObject作如下修改:
1 HRESULT DllGetClassObject (REFCLSID rclsid, REFIID riid, void **ppv)
2 {
3 *ppv = 0;
4 if (IsEqualCLSID (rclsid, &CLSID_Outside))
5 {
6
7 if (!vpcfOutside)
8
9 {
10
11 HRESULT hr = CreateClassFactory (&CLSID_Outside, CreateOutside,
12 &IID_IClassFactory, &vpcfOutside);
13
14 if (hr != NOERROR)
15
16 return hr;
17
18 if(IsEqualIID(riid,&IID_IClassFactory))
19 {
20 *ppv = vpcfOutside;// Set *ppv to vpcfOutside directly instead of QueryInterface if first time creation
21 return NOERROR;
22 }
23 else
24 {
25 Release(vpcfOutside);// Any interface requested (riid) other than IID_ClassFactory and IID_Unknown not support by class factory,
26 // call Release to free the memory.
27 return E_FAIL;
28 }
29
30 }
31
32 return QueryInterface (vpcfOutside, riid, ppv);
33
34 }
35
36 return E_FAIL;
37 }
修改後在實施,內存都正常開釋了。CreateClassFactory代碼闡明
1 HRESULT CreateClassFactory (REFCLSID rclsid,
2 HRESULT (*pfnCreate)(IUnknown *, REFIID, void **),
3 REFIID riid, void **ppv)
4 {
5 ClassFactory *this;
6 HRESULT hr;
7
8 *ppv = 0;
9 if (hr = Alloc (sizeof (ClassFactory), &this))
10 return hr;
11
12 this->icf.lpVtbl = &vtblClassFactory;
13 this->cRef = 1; // After this call, cRef==1
14
15 this->pfnCreate = pfnCreate;
16
17 hr = QueryInterface (&this->icf, riid, ppv); // After this call, cRef==2
18 Release (&this->icf); // Corresponds to "this->cRef = 1", ater this call, cRef==1
19
20 return hr;
21 }
能夠看到,兩行代碼的作用是對引證計數增1及減1,這兩行代碼實施後,對引證計數的影響相互抵消,等於沒有改動引證計數。那麼,把這兩行一起註釋掉,是不是能夠呢?
我的答覆是:在本例中能夠。由於這兩行代碼之間的QueryInterface總是能夠實施成功的(由於是用IDD_ClassFactory來調用該函數的)。所以,即便把這兩行代碼一起註釋掉,CreateClassFactory實施完畢後,類廠政策的引證計數也增了1,往後調用Release就能夠開釋掉類廠政策佔用的內存。
可是,若是CFQueryInterface的代碼編寫中除了過失,比方,像這樣寫:
1 static HRESULT CFQueryInterface (IClassFactory *pcf, REFIID riid, void **ppv)
2 {
3 ClassFactory *this = IMPL (ClassFactory, icf, pcf);
4
5 if (IsEqualIID (riid, &IID_IUnknown)
6 // IsEqualIID (riid, &IID_IClassFactory)) // Comment out this condition to create an error
7 *ppv = &this->icf;
8 else
9 {
10 *ppv = 0;
11 return E_NOINTERFACE;
12 }
13
14 AddRef ((IClassFactory *)*ppv);
15
16 return NOERROR;
17 }
那麼,這兩行代碼之間的QueryInterface就會實施犯錯,那麼類廠政策佔用的內存就永世沒有機緣開釋了。
也就是說,AddRef和Release儘管在作用上對引證計數來說相互抵消,但Release函數供給了開釋政策內存的機緣(當引證計數為0時),若是不成對的調用他們,也就失去了處置政策內存(開釋政策佔用的內存)的機緣。
組件庫outside文件闡明:
IFoo.h IFoo接口聲明
outside.c 組件政策、IFoo接口完畢
cf.c 類廠政策、IClassFactory接口完畢
outdll.c 組件庫導出函數完畢
outside.def 組件庫模塊界說文件,導出函數聲明
outside.reg 組件庫註冊文件