masm32开发com组件介绍[一][二][三]
生活随笔
收集整理的這篇文章主要介紹了
masm32开发com组件介绍[一][二][三]
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
標 題:
?【原創】masm32開發com組件介紹[一][二][三]
作 者:?combojiang
時 間:?2007-12-10,14:09:39
鏈 接:?http://bbs.pediy.com/showthread.php?t=56328
聲明:本貼參考網站:http://ourworld.compuserve.com/?
[一]基礎知識篇
組件對象模型(Com)在windows操作系統中應用越來越廣泛。com因為大量的技術細節顯得很復雜,但是正是這種復雜才使com組件的調用顯得十分簡單。?com和使用程序采用server/client架構。下面我們將在后續的兩篇中介紹com組件的編寫與調用。
com編程時當前程序開發的熱點,各種編程語言都為組件編寫提供了很好的支持,但是匯編語言例外,匯編語言開發組件沒有優勢。但是透過匯編開發的了解,可以使我們了解com組件的工作原理。好了,閑話少說,開始介紹:)
所有的inc頭文件都要滿足如下特點:
??
1)?masm32松散的類型定義約定將繼續使用。就是說參數可以被定義為他們的基本類型,代表性的如:DWORD
2)?里面不能創建任何的代碼,僅僅包含定義信息,頭文件里面需要包含代碼,則必須定義為宏。
??
3)?結構體應該參照他們的C原形來定義。
4)?GUID?結構定義在windows.inc文件中,GUID的值應該通過textequ宏來定義,這樣不會直接產生任何代碼。
5)?接口定義分為兩步:
???1.一個通用的宏產生一個通用的接口結構。
???2.使用接口名字來修飾結構自身的方法名字。
???這種方式可以有效地避免namespace沖突,并且方便接口定義結構繼承。
??
6)COM接口函數調用使用coinvoke宏。
GUIDS
EXAMPLE:?
sIID_IUnknown??TEXTEQU??<{000000000H,?00000H,?00000H,?\
????????????????????????????{0C0H,?000H,?000H,?000H,?000H,?000H,?000H,?046H}}?
可以被用來定義
IID_IUnknown???GUID????sIID_IUnknown?
??
接口:
由于MASM32原形約定松散的類型檢查,主要檢查編譯時函數的參數的個數。因此可以非常簡單的定義接口函數,如下所示,用下表值來表示函數參數的個數。
comethod1Proto???typedef?proto?:DWORD?
comethod2Proto???typedef?proto?:DWORD,?:DWORD?
comethod3Proto???typedef?proto?:DWORD,?:DWORD,?:DWORD?
comethod4Proto???typedef?proto?:DWORD,?:DWORD,?:DWORD,?:DWORD?
comethod4Proto???typedef?proto?:DWORD,?:DWORD,?:DWORD,?:DWORD,?:DWORD?
函數指針如下:
comethod1????????typedef?ptr?comethod1Proto?
comethod2????????typedef?ptr?comethod2Proto?
comethod3????????typedef?ptr?comethod3Proto?
comethod4????????typedef?ptr?comethod4Proto?
comethod5????????typedef?ptr?comethod4Proto
IUnknown接口:?IUnknown接口是基本接口,其他所有接口都是派生于它。函數原形定義如上,其定義如下:
_vtIUnknown?MACRO?CastName:REQ?
????;?IUnknown?methods?
????&CastName&_QueryInterface?comethod3????
????&CastName&_AddRef?????????comethod1????
????&CastName&_Release????????comethod1????
ENDM?
IUnknown????????????????????????STRUCT?
????_vtIUnknown?IUnknown?
IUnknown????????????????????????ENDS
其展開如下:?
IUnknown????????????????????????STRUCT?
????IUnknown_QueryInterface?comethod3??????
????IUnknown_AddRef?????????comethod1??????
????IUnknown_Release????????comethod1??????
IUnknown????????????????????????ENDS
IClassFactory?接口
IClassFactory派生于?IUnknown.它的結構開始是?IUnknown?的方法,?后面添加了?2個自己的方法.?
_vtIClassFactory?MACRO?CastName:REQ?
????;?IUnknown?methods??
????_vtIUnknown?CastName?
????;?IClassFactory?methods?
????&CastName&_CreateInstance?comethod4???
????&CastName&_LockServer?????comethod2???
ENDM?
??
IClassFactory???????????????????STRUCT?
????_vtIClassFactory?IClassFactory?
IClassFactory???????????????????ENDS
展開如下:?
IClassFactory???????????????????STRUCT?
????IClassFactory_QueryInterface?comethod3???
????IClassFactory_AddRef?????????comethod1???
????IClassFactory_Release????????comethod1???
????IClassFactory_CreateInstance?comethod4???
????IClassFactory_LockServer?????comethod2???
IClassFactory???????????????????ENDS
Coinvoke宏
;---------------------------------------------------------------------
;?coinvoke?MACRO?
;?pInterface????pointer?to?a?specific?interface?instance
;?Interface?????the?Interface's?struct?typedef
;?Function??????which?function?or?method?of?the?interface?to?perform
;?args??????????all?required?arguments?
;???????????????????(type,?kind?and?count?determined?by?the?function)
;
coinvoke?MACRO?pInterface:REQ,?Interface:REQ,?Function:REQ,?args:VARARG
????LOCAL?istatement,?arg
????FOR?arg,?<args>?????;;?run?thru?args?to?see?if?edx?is?lurking?in?there
????????IFIDNI?<&arg>,?<edx>
????????????.ERR?<edx?is?not?allowed?as?a?coinvoke?parameter>
????????ENDIF
????ENDM
????istatement?CATSTR?<invoke?(Interface?PTR[edx]).&Interface>,<_>,<&Function,?pInterface>
????IFNB?<args>?????;;?add?the?list?of?parameter?arguments?if?any
????????istatement?CATSTR?istatement,?<,?>,?<&args>?
????ENDIF?
????mov?edx,?pInterface
????mov?edx,?[edx]
????istatement
ENDM
;---------------------------------------------------------------------
例如:QueryInterface方法調用如下:
coinvoke?ppv?,IUnknown,?QueryInterface,?ADDR?IID_SomeOtherInterface,?
ADDR?ppnew
HRESULTS
任何一個com接口函數的返回值類型都是一個hResult,?4個字節長。返回值在eax寄存器中。可以用這個值來判斷函數調用是否成功。
.IF?!SIGN??
;?function?passed?
.ELSE?
;?function?failed?
.ENDIF
接下來,我們定義了宏來簡化它:?
.IF?SUCCEEDED???;?TRUE?if?SIGN?bit?not?set
.IF?FAILED?????;?TRUE?is?SIGN?bit?set
結論:
組件對象模型com是以win32?dll或exe形式發布的執行代碼組成的。Com是由一些對象和對象的接口組成,在com里,接口提供對象操作的機制。?而接口是由一個或者多個相關的方法、屬性、事件組成的。在這里我們開發一個簡單的但是功能齊全的一個進程內com組件(即以dll形式存在)。
這里假設你已經了解了com對象模型的基礎知識,了解什么是虛表,什么是虛函數表指針。如果你不熟悉這些,建議看看《com本質論》這本書。
我們先來分析下進程內com服務的組成。由于它是一個dll形式發布的,其中包括5個重要的函數。其中后面的四個是要作為dll導出函數來導出的。
DllMain:?這是動態鏈接庫德第一個入口函數,它在庫被加載的時候被調用,通過這個函數,可以對客戶端程序進行檢查。
DllRegisterServer:通過這個函數能夠實現組建的自我注冊,注冊信息作為資源保存在動態鏈接庫中,這個函數能夠讀取資源,把信息寫進注冊表,使用
regsvr32.exe?注冊組建時,實際上是調用了組件輸出的這個函數。
DllUnregisterServer:?當一個組件不再使用時,這個組件應該能夠提供自我卸載,regsvr32.exe能調用這個函數,實現這一步。
DllCanUnloadNow:?com服務中的全局變量用于保存它的狀態,客戶端可以周期性的調用這個函數,檢查組件服務器是否在使用,然后把它卸載。
DllGetClassObject:?這是完成組件輸出的函數,這個輸出需要3個參數,創建組件的GUID,要創建組件接口的GUID以及創建后指向對象的指針。如果組件對象或者接口不被支持,執行將失敗。
到現在為止,我們應該注意到一件事情,就是如果不是因為間接訪問,com將什么也不是。實際上,DllGetClassObject函數返回的對象不是我們要尋找的對象,它是類廠對象,一個類廠對象了解如何實例化其他任何的類。第一層的間接訪問允許組件創建的細節被指定,如果它僅僅是簡單而又直接的返回一個我們要尋找的對象指針,那么說明對象已經存在,那樣,我們將不能設置和控制關于構造對象的任何參數。
DllGetClassObject返回一個IClassFactory接口,這個接口是從IUnknown派生的,另外他還有自己的兩個重要的成員函數。
??HRESULT?CreateInstance(
IUnknown?*?pUnkOuter,???//Pointer?to?outer?object?when?part?of?an?
//?aggregate?REFIID?riid,?
REFIID?riid,???????????//Reference?to?the?interface?identifier
oid**?ppvObject);???????//Address?of?output?variable?that?receives?
//?the?interface?pointer?requested?in?riid
HRESULT?LockServer(BOOL?fLock);?
//Increments?or?decrements?the?lock?count?
LockServer用來控制類廠對象的引用計數,系統檢查改計數以確定是否要卸載組件,即:控制類廠的生存期。
CreateInstance是最重要的,類廠組件的唯一功能是創建其它組件。一個類廠組件可以對應多種普通COM組件,但每個類廠組件的實例只能創建一種COM組件。
它接收一個接口GUID,返回該接口的指針。它并不接受組件的CLSID,所以一個類廠實例只能夠創建一種COM組件,即傳給?CoGetClassObject的CLSID對應的組件。
客戶、COM庫、組件dll、類廠、組件之間的交互過程:
1.????客戶首先調用COM庫的CoCreateInstance函數來創建COM組件。
2.????CoCreateInstance首先調用COM庫的CoGetClassObject獲取類廠。
3.????該函數具體是通過調用了組件DLL輸出的DllGetClassObject來創建類廠。
4.?????DllGetClassObject通過new函數產生一個Cfactory的對象,并通過QueryInterface獲取其接口指針(一般是IclassFactory指針)。
5.?????返回到COM庫的CoCreateInstance調用剛才獲得的接口指針(IclassFactory,類廠)的CreateInstance函數。
6.?????該函數new指定的組件類,通過QueryInterface獲得指定的接口
7.?????CoCreateInstanse釋放掉IclassFactory指針(通過Release),然后向客戶程序返回獲得的指針。
8.?????可以在客戶中使用獲得的接口了。
在第6步中,根據不同的CLSID創建不同的組件,可以實現一個類廠供該DLL中多個組件共用。但只是類共用,不是實例共用。一旦在創建類廠時通過CoGetClassObject指定了CLSID,則只能創建該COM組件的實例。
在這里我們將深入c++對象模型,來看下一些內部的實現細節。通常編譯器來處理這些。com的設計者充分利用了這些,因此,我們需要了解它。
當我們用匯編寫一個常規的程序時,我依靠編譯器為我們創建代碼段和數據段,內存中的一塊區域是我們執行的代碼,另一塊區域保存了我們需要的數據。
C++運行時動態內存分配,給每一個類實例,每一個小的代碼段它自己的數據段。換句話講,一個類的實例就是這個數據段,每一個類實例的數據描述都是保存在一個動態的數據區域。
或許你聽說過c++傳遞對象成員函數參數時,有一個隱藏的參數,即this指針。當一個人為對象寫一個低層的代碼時(在c++中編譯器會作這個工作,你不需要考慮),
你首先遇到的問題是”我在給哪個對象寫代碼?“
This指針是一個簡單的指針,它指向這個動態數據內存區域的這個類對象實例。當一個類對象函數被調用時,this指針就會被悄悄地傳遞過去。當這個對象的私有數據被訪問時,類的代碼區域就會使用this指針,來找到它的對象實例的數據。
對于一個com接口指針跟this指針很類似。使用中,com是一個接口規范,讓你看不到它的代碼實現。
;?declare?the?ClassFactory?object?structure?
ClassFactoryObject?STRUCT?
lpVtbl???DWORD?0?;?function?table?pointer?
nRefCount???DWORD?0?;?object?and?reference?count
ClassFactoryObject?ENDS?
;?declare?the?MyCom?object?structure?
MyComObject?STRUCT?
lpVtbl???DWORD?0?;?function?table?pointer
nRefCount???DWORD?0?;?reference?count
nValue???DWORD?0?;?interface?private?data
MyComObject?ENDS?
第一個lpVtbl是一個虛表指針,它指向一個虛函數表,我用它來控制每個接口的私有數據。就像這里的nRefCount和nValue。
這些結構所在的動態內存是通過CoTaskMemAlloc和CoTaskMemFree這兩個API函數來分配和釋放的。這兩個函數是由ole32.dll導出的。Ole32.dll還導出了很多的函數,例如比對GUIDs值和把轉換GUIDs為字符串,或者把字符串轉換為GUIDs。
為了舉例說明com接口的工作原理,我們創建一個簡單接口IMyCom(注:所有的com接口都有一個“I”前綴。同其他接口一樣,他派生于IUnknown接口,也就是說他的前三個函數是QueryInterface,?AddRef,?和Release。下面我們添加幾個接口函數。下面看到的是c風格的函數原形:
?
HRESULT?SetValue(long?*pVal);?
HRESULT?GetValue(long?newVal);?
HRESULT?RaiseValue(long?newVal);?
其中,SetValue?和?GetValue用于讀,設置我們接口的數據成員。RaiseValue用于增加這個數據的值。
這個結構在內存中的形式如下:?
??????????
客戶端僅僅擁有一個分布式結構的指針(ppv)這個名字來源于它的c++形式的定義("pointer?to?pointer?to?(void)."),當創建類實例的時候,這個對象數據塊是動態分配和初始化的,虛函數表vtable和server?functions是靜態的,他們在編譯時定義好。
有一點需要注意的是,虛函數表擁有的是函數指針,而并非是函數本身。因此,我們可以修改虛函數表中指向的例程,就可以簡單的"override“一個派生函數。
在例子中,IClassFactory和IMyCom都是派生于IUnknown接口,都繼承了QueryInterface,但是他們支持不同的接口,它們需要指向不同的例程,返回不同的結果。
因此,它們有各自的QueryInterface例程(QueryInterfaceCF?和?QueryInterfaceMC)被不同的虛函數表指向。
同樣的,AddRef和Release也要被不同的支持他們的接口來定制。
類型庫:
每一個com接口都是從系統注冊表中得到信息,這些接口的定義都是由一個被稱為接口定義語言(IDL)來描述的,在windows平臺下,使用MIDL進行編譯。我們可以利用vc開發環境,通過向導來創建一個原始的接口定義文件。
---------------------------------------------------------------------------------------------------------------------On?WinTel?platforms,?
我建一個ATL工程,命名為MyComApp,然后選擇“insert?a?new?ATL?object“,然后選擇“Simple?Object”,命名為:MyCom。這樣就創建了一個空的IMyCom接口,然后通過右鍵菜單,我們添加屬性SetValue和GetValue,并增加一個RaiseValue方法。然后我們保存退出工程,拷貝MyComApp.idl文件到我的匯編工程目錄。
下面就是這個idl文件的內容:
//?MyCom.idl?:?IDL?source?for?MyCom.dll?
//?
//?This?file?will?be?processed?by?the?MIDL?tool?to?
//?produce?the?type?library?(MyCom.tlb)?and?marshalling?code
import?"oaidl.idl";?
import?"ocidl.idl";?
[?
object,?
uuid(F8CE5E41-1135-11d4-A324-0040F6D487D9),?
helpstring("IMyCom?Interface"),?
pointer_default(unique)?
]?
interface?IMyCom?:?IUnknown?
{?
[propget,?helpstring("property?Value")]?
HRESULT?Value([out,?retval]?long?*pVal);?
[propput,?helpstring("property?Value")]?
HRESULT?Value([in]?long?newVal);?
[helpstring("method?Raise")]?
HRESULT?Raise(long?Value);?
};?
[?
uuid(F8CE5E42-1135-11d4-A324-0040F6D487D9),?
version(1.0),?
helpstring("MyComApp?1.0?Type?Library")?
]?
library?MyComLib?
{?
importlib("stdole32.tlb");?
importlib("stdole2.tlb");?
[?
uuid(F8CE5E43-1135-11d4-A324-0040F6D487D9),?
helpstring("MyCom?Class")?
]?
coclass?MyCom?
{?
[default]?interface?IMyCom;?
};?
};?
這個文件可以被用來作為原型進一步進行接口定義。注意這里面有三個GUIDs,一個是為接口,一個是為coclass,一個是為類型庫。對于新的應用,它們的值一定不同。
透過這個定義的文件結構,我們很容易了解他的內容。?
[propget,?helpstring("property?Value")]?HRESULT?Value([out,?retval]?long?*pVal);?[propput,?helpstring("property?Value")]?HRESULT?Value([in]?long?newVal);?[helpstring("method?Raise")]?HRESULT?Raise(long?Value);?
下面是這些接口在masm32中的定義:?
GetValue?PROTO?:DWORD,?:DWORD?
SetValue?PROTO?:DWORD,?:DWORD?
RaiseValue?PROTO?:DWORD,?:DWORD?
??
他們有很大的不同,但是原因很簡單。類型庫中的接口是作為通用的,可以直接被客戶端象VB來使用。
為了創建類型庫,可以使用MIDL命令行來編譯idl文件?:
MIDL?MyCom.idl?
編譯產生的幾個文件,除了MyCom.tlb外,其他的都可以忽略,接下來我們需要把類型庫添加到dll資源文件中。例如:
1?typelib?MyCom.tlb?
讓他作為資源文件中的第一個元素是很重要的,后續我們將會使用LoadTypeLib?API函數來使用這個庫,同時這個函數也是希望在第一位置發現這個庫。
注冊組件:
DllRegisterServer?和?DllUnregisterServer?為我們注冊組件和注銷組件用.內容如下:
?
HKEY_CLASSES_ROOT\CMyCom?
(Default)?"CMyCom?simple?client"?
HKEY_CLASSES_ROOT\CMyCom\CLSID?
(Default)?"{A21A8C43-1266-11D4-A324-0040F6D487D9}"?
HKEY_CLASSES_ROOT\CLSID\{A21A8C43-1266-11D4-A324-0040F6D487D9}?
(Default)?"CMyCom?simple?client"?
HKEY_CLASSES_ROOT\CLSID\{A21A8C43-1266-11D4-A324-0040F6D487D9}\CMyCom?
(Default)?"CMyCom"?
HKEY_CLASSES_ROOT\CLSID\{A21A8C43-1266-11D4-A324-0040F6D487D9}\InprocServer32?
(Default)?"C:\MASM32\MYCOM\MYCOM.DLL"?
ThreadingModel?"Single"?
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}?
(Default)?(value?not?set)?
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0?
(Default)?"MyCom?1.0?Type?Library"?
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\0?
(Default)?(value?not?set)?
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\0\win32?
(Default)?"?C:\masm32\COM\MyCom?\MYCOM.DLL"?
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\FLAGS?
(Default)?"O"?
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\HELPDIR?
(Default)?"C:\masm32\COM\MyCom"?
有一個鍵值是變化的,它是服務dll自身的路徑和文件名,在我的系統上,我把它放置在?"C:\MASM32\COM\MYCOM\MYCOM.DLL",當我注冊組件的時候,這個可以被檢測到,DllRegisterServer通過調用GetModuleFileName可以發現dll自身的存儲位置。
這里有大量的信息是關于這個com服務的,但是我們僅僅需要傳遞{A21A8C43-1266-11D4-A324-0040F6D487D9}這個ID和一個有效的接口ID給CoCreateInstance函數來實例化我們的com服務。這個函數將會跟蹤注冊表設置,利用CLSID來發現創建組件需要的東西,一旦它創建了組件,它將加載類型庫,以獲取更多需要的信息。
對我們來說非常幸運,最后的5個注冊入口項通過RegisterTypeLib函數可以完成。在DllRegisterServer中,我們通過一些列的注冊表函數來設置前面5項鍵值。然后調用RegisterTypeLib。?DllUnregisterServer函數刪除DllRegisterServer中的注冊表項,然后調用UnRegisterTypeLib。注意不要完全刪除HKEY_CLASSES_ROOT\CLSID\
實現?Unknown
AddRef_MC?proc?this_:DWORD
mov?eax,?this_
inc?(MyComObject?ptr?[eax]).nRefCount
mov?eax,?(MyComObject?ptr?[eax]).nRefCount
ret?????????;?note?we?return?the?object?count
AddRef_MC?endp
Release_MC?proc?this_:DWORD
????mov?eax,?this_
????dec?(MyComObject?ptr?[eax]).nRefCount
????mov?eax,?(MyComObject?ptr?[eax]).nRefCount
????.IF?(eax?==?0)
????????;?the?reference?count?has?dropped?to?zero
????????;?no?one?holds?reference?to?the?object
????????;?so?let's?delete?it
????????invoke??CoTaskMemFree,?this_
????????dec?MyCFObject.nRefCount
??xor?eax,?eax????;?clear?eax?(count?=?0)
???.ENDIF
????ret?????????;?note?we?return?the?object?count
Release_MC?endp
MyCom自己的成員實現:
GetValue?proc?this_:DWORD,?pval:DWORD
????mov?eax,?this_
????mov?eax,?(MyComObject?ptr?[eax]).nValue
????mov?edx,?pval
????mov?[edx],?eax
????xor?eax,?eax????????;?return?S_OK
????ret
GetValue?endp
SetValue?proc?this_:DWORD,?val:DWORD
????mov?eax,?this_
????mov?edx,?val
????mov?(MyComObject?ptr?[eax]).nValue,?edx
????xor?eax,?eax????????;?return?S_OK
????ret
SetValue?endp
RaiseValue??PROC?this_:DWORD,?val:DWORD
????mov?eax,?this_
????mov?edx,?val
????add?(MyComObject?ptr?[eax]).nValue,?edx
????xor?eax,?eax????????;?return?S_OK????
????ret
RaiseValue??ENDP
MyCom.dll,?這個com服務工程需要以下5個文件來編譯:
MyCom.asm??匯編源程序
MyCom.idl??IDL文件,用于編譯產生MyCom.tlb?
MyCom.tlb??類型庫,需要一個rc資源文件
rsrc.rc????資源文件,從中可以獲得類型庫信息
MyCom.DEF??標準的dll輸出文件
編譯后,代碼不會做任何事情,直到我們注冊它,我們可以使用命令行:
regsvr32?MyCom.dll注冊。?
上傳的附件
//
大量的細節使得Com看上去很復雜,但是使用起來卻很簡單。最難的部分就是理解里面的數據結構,盡管COM是語言無關的,但是他借用了很多c++的術語來描述自己。
為了能使用某個對象的com接口函數,你必須首先要從類廠中創建這個對象,并且讓他來返回接口指針。這個過程被CoCreateInstance這個API函數完成。當你使用完接口時,要調用Release方法。一個COM對象可以看作是一個服務,調用com的應用程序就是他的客戶端。
在調用com接口函數之前,你需要了解接口是什么,一個com接口就是一個函數指針表,我們還是從IUnknown接口開始,如果你創建了一個組件導出了IUnknown接口,那么你就有了一個全功能的com對象。IUnknown有三個基本的幾口方法,既然所有的接口都是從它派生出來,那么我們一定要記住,一個接口實際上就是一個函數指針成員組成的結構體。
例如:
IUnknown?STRUCT?DWORD?
;?IUnknown?methods?
IUnknown_QueryInterface???QueryInterface_Pointer???
IUnknown_AddRef?????AddRef_Pointer???
IUnknown_Release?????Release_Pointer??
IUnknown?ENDS
它只有12個字節長,它具有3個DWORD指針來指向實際的實現函數。對于虛函數表,你一定聽說過,這些指針定義如下,因此我們可以讓masm在編譯我們的調用時進行一些類型檢查。
QueryInterface_Pointer???typedef?ptr?QueryInterface_Proto
AddRef_Pointer?????typedef?ptr?AddRef_Proto?
Release_Pointer?????typedef?ptr?Release_Proto
最后我們定義我們的函數如下:
QueryInterface_Proto???typedef?PROTO?:DWORD,?:DWORD,?:DWORD?
AddRef_Pointer?????typedef?PROTO?:DWORD?
Release_Pointer?????typedef?PROTO?:DWORD?
為了保持masm32松散的類型檢查一致,函數參數都定義為dword
定義接口是一個相當大的編輯就是,masm不支持前向引用。因此,我們不得不顛倒一下定義的順序。先定義函數頭,再定義函數指針,最后定義接口。實際上在使用接口時,你需要一個指向它的指針。
CoCreateInstance函數能用來直接返回一個接口指針。它實際上指向了擁有接口的對象。這個結構看上去如圖所示:
這個結構里有大量的間接訪問,使用宏可以簡化它。
當客戶端調用COM庫創建com組件時,它傳進了一個地址用于存放對象指針。這個就是我們所說的ppv.?從c++的角度來講,叫做指向指針的指針,void類型代表無類型。它保存了另一個指針pv的地址。pv指向了虛函數表。
例如:我們使用CoCreateInstance函數成功的返回了一個接口指針ppv,我們想看下它是否支持其他的接口,我們可以調用QueryInterface方法。用c++的方法描述QueryInterface如下:
(HRESULT)?SomeObject::QueryInterface?(this:pObject,?IID:pGUID,?ppv2:pInterface)
用匯編寫法如下:?
;?get?pointer?to?the?object?
mov?eax,?ppv?
;?and?use?it?to?find?the?interface?structure
mov?edx,?[eax]?
;?push?the?function?parameters?onto?the?stack
push?OFFSET?ppv2
push?OFFSET?IID_ISomeOtherInterface
push?dword?ppv
;?and?then?call?that?method?
call?dword?ptr?[edx?+?0]
使用invoke調用簡化如下:
;?get?pointer?to?the?object?
mov?eax,?ppv?
;?and?use?it?to?find?the?interface?structure
mov?edx,?[eax]?
;?and?then?call?that?method?
invoke?(IUnknown?PTR?[edx]).IUnknown_QueryInterface,?ppv,
????ADDR?IID_SomeOtherInterface,?ADDR?ppv_new
注意IUnknown?PTR?[edx]這個類型轉換,是讓編譯器知道使用哪個結構來得到QueryInterface函數在虛表中的正確偏移。其中有一個模糊的地方,注意我修改了函數名字為"IUnknown_QueryInterface",這個名字修飾時必要的。當你有一個大的com工程,有許多相似的接口,你就會遇到麻煩。不同的接口對應不同的方法表示,是非常有效的。
coinvoke?宏,這個宏定義在oaidl.inc文件中。使用它,可以進一步簡化com調用。
;---------------------------------------------------------------------
;?coinvoke?MACRO?
;
;
;?pInterface????pointer?to?a?specific?interface?instance
;?Interface?????the?Interface's?struct?typedef
;?Function??????which?function?or?method?of?the?interface?to?perform
;?args??????????all?required?arguments?
;???????????????????(type,?kind?and?count?determined?by?the?function)
;
coinvoke?MACRO?pInterface:REQ,?Interface:REQ,?Function:REQ,?args:VARARG
????LOCAL?istatement,?arg
????FOR?arg,?<args>?????;;?run?thru?args?to?see?if?edx?is?lurking?in?there
????????IFIDNI?<&arg>,?<edx>
????????????.ERR?<edx?is?not?allowed?as?a?coinvoke?parameter>
????????ENDIF
????ENDM
????istatement?CATSTR?<invoke?(Interface?PTR[edx]).&Interface>,<_>,<&Function,?pInterface>
????IFNB?<args>?????;;?add?the?list?of?parameter?arguments?if?any
????????istatement?CATSTR?istatement,?<,?>,?<&args>?
????ENDIF?
????mov?edx,?pInterface
????mov?edx,?[edx]
????istatement
ENDM
;---------------------------------------------------------------------
?
因此,前面的QueryInterface方法調用就可以簡化成:?
coinvoke?ppv?,IUnknown,?QueryInterface,?ADDR?IID_SomeOtherInterface,?
ADDR?ppnew
注意這里名字修飾是隱藏在宏中處理的。
代碼后面附上。。。?
上傳的附件
作 者:?combojiang
時 間:?2007-12-10,14:09:39
鏈 接:?http://bbs.pediy.com/showthread.php?t=56328
聲明:本貼參考網站:http://ourworld.compuserve.com/?
[一]基礎知識篇
組件對象模型(Com)在windows操作系統中應用越來越廣泛。com因為大量的技術細節顯得很復雜,但是正是這種復雜才使com組件的調用顯得十分簡單。?com和使用程序采用server/client架構。下面我們將在后續的兩篇中介紹com組件的編寫與調用。
com編程時當前程序開發的熱點,各種編程語言都為組件編寫提供了很好的支持,但是匯編語言例外,匯編語言開發組件沒有優勢。但是透過匯編開發的了解,可以使我們了解com組件的工作原理。好了,閑話少說,開始介紹:)
所有的inc頭文件都要滿足如下特點:
??
1)?masm32松散的類型定義約定將繼續使用。就是說參數可以被定義為他們的基本類型,代表性的如:DWORD
2)?里面不能創建任何的代碼,僅僅包含定義信息,頭文件里面需要包含代碼,則必須定義為宏。
??
3)?結構體應該參照他們的C原形來定義。
4)?GUID?結構定義在windows.inc文件中,GUID的值應該通過textequ宏來定義,這樣不會直接產生任何代碼。
5)?接口定義分為兩步:
???1.一個通用的宏產生一個通用的接口結構。
???2.使用接口名字來修飾結構自身的方法名字。
???這種方式可以有效地避免namespace沖突,并且方便接口定義結構繼承。
??
6)COM接口函數調用使用coinvoke宏。
GUIDS
EXAMPLE:?
sIID_IUnknown??TEXTEQU??<{000000000H,?00000H,?00000H,?\
????????????????????????????{0C0H,?000H,?000H,?000H,?000H,?000H,?000H,?046H}}?
可以被用來定義
IID_IUnknown???GUID????sIID_IUnknown?
??
接口:
由于MASM32原形約定松散的類型檢查,主要檢查編譯時函數的參數的個數。因此可以非常簡單的定義接口函數,如下所示,用下表值來表示函數參數的個數。
comethod1Proto???typedef?proto?:DWORD?
comethod2Proto???typedef?proto?:DWORD,?:DWORD?
comethod3Proto???typedef?proto?:DWORD,?:DWORD,?:DWORD?
comethod4Proto???typedef?proto?:DWORD,?:DWORD,?:DWORD,?:DWORD?
comethod4Proto???typedef?proto?:DWORD,?:DWORD,?:DWORD,?:DWORD,?:DWORD?
函數指針如下:
comethod1????????typedef?ptr?comethod1Proto?
comethod2????????typedef?ptr?comethod2Proto?
comethod3????????typedef?ptr?comethod3Proto?
comethod4????????typedef?ptr?comethod4Proto?
comethod5????????typedef?ptr?comethod4Proto
IUnknown接口:?IUnknown接口是基本接口,其他所有接口都是派生于它。函數原形定義如上,其定義如下:
_vtIUnknown?MACRO?CastName:REQ?
????;?IUnknown?methods?
????&CastName&_QueryInterface?comethod3????
????&CastName&_AddRef?????????comethod1????
????&CastName&_Release????????comethod1????
ENDM?
IUnknown????????????????????????STRUCT?
????_vtIUnknown?IUnknown?
IUnknown????????????????????????ENDS
其展開如下:?
IUnknown????????????????????????STRUCT?
????IUnknown_QueryInterface?comethod3??????
????IUnknown_AddRef?????????comethod1??????
????IUnknown_Release????????comethod1??????
IUnknown????????????????????????ENDS
IClassFactory?接口
IClassFactory派生于?IUnknown.它的結構開始是?IUnknown?的方法,?后面添加了?2個自己的方法.?
_vtIClassFactory?MACRO?CastName:REQ?
????;?IUnknown?methods??
????_vtIUnknown?CastName?
????;?IClassFactory?methods?
????&CastName&_CreateInstance?comethod4???
????&CastName&_LockServer?????comethod2???
ENDM?
??
IClassFactory???????????????????STRUCT?
????_vtIClassFactory?IClassFactory?
IClassFactory???????????????????ENDS
展開如下:?
IClassFactory???????????????????STRUCT?
????IClassFactory_QueryInterface?comethod3???
????IClassFactory_AddRef?????????comethod1???
????IClassFactory_Release????????comethod1???
????IClassFactory_CreateInstance?comethod4???
????IClassFactory_LockServer?????comethod2???
IClassFactory???????????????????ENDS
Coinvoke宏
;---------------------------------------------------------------------
;?coinvoke?MACRO?
;?pInterface????pointer?to?a?specific?interface?instance
;?Interface?????the?Interface's?struct?typedef
;?Function??????which?function?or?method?of?the?interface?to?perform
;?args??????????all?required?arguments?
;???????????????????(type,?kind?and?count?determined?by?the?function)
;
coinvoke?MACRO?pInterface:REQ,?Interface:REQ,?Function:REQ,?args:VARARG
????LOCAL?istatement,?arg
????FOR?arg,?<args>?????;;?run?thru?args?to?see?if?edx?is?lurking?in?there
????????IFIDNI?<&arg>,?<edx>
????????????.ERR?<edx?is?not?allowed?as?a?coinvoke?parameter>
????????ENDIF
????ENDM
????istatement?CATSTR?<invoke?(Interface?PTR[edx]).&Interface>,<_>,<&Function,?pInterface>
????IFNB?<args>?????;;?add?the?list?of?parameter?arguments?if?any
????????istatement?CATSTR?istatement,?<,?>,?<&args>?
????ENDIF?
????mov?edx,?pInterface
????mov?edx,?[edx]
????istatement
ENDM
;---------------------------------------------------------------------
例如:QueryInterface方法調用如下:
coinvoke?ppv?,IUnknown,?QueryInterface,?ADDR?IID_SomeOtherInterface,?
ADDR?ppnew
HRESULTS
任何一個com接口函數的返回值類型都是一個hResult,?4個字節長。返回值在eax寄存器中。可以用這個值來判斷函數調用是否成功。
.IF?!SIGN??
;?function?passed?
.ELSE?
;?function?failed?
.ENDIF
接下來,我們定義了宏來簡化它:?
.IF?SUCCEEDED???;?TRUE?if?SIGN?bit?not?set
.IF?FAILED?????;?TRUE?is?SIGN?bit?set
結論:
以上這些是你用匯編開發com需要用到的,這些適用于activex的開發。?
[二]用匯編語言編寫com組件
組件對象模型com是以win32?dll或exe形式發布的執行代碼組成的。Com是由一些對象和對象的接口組成,在com里,接口提供對象操作的機制。?而接口是由一個或者多個相關的方法、屬性、事件組成的。在這里我們開發一個簡單的但是功能齊全的一個進程內com組件(即以dll形式存在)。
這里假設你已經了解了com對象模型的基礎知識,了解什么是虛表,什么是虛函數表指針。如果你不熟悉這些,建議看看《com本質論》這本書。
我們先來分析下進程內com服務的組成。由于它是一個dll形式發布的,其中包括5個重要的函數。其中后面的四個是要作為dll導出函數來導出的。
DllMain:?這是動態鏈接庫德第一個入口函數,它在庫被加載的時候被調用,通過這個函數,可以對客戶端程序進行檢查。
DllRegisterServer:通過這個函數能夠實現組建的自我注冊,注冊信息作為資源保存在動態鏈接庫中,這個函數能夠讀取資源,把信息寫進注冊表,使用
regsvr32.exe?注冊組建時,實際上是調用了組件輸出的這個函數。
DllUnregisterServer:?當一個組件不再使用時,這個組件應該能夠提供自我卸載,regsvr32.exe能調用這個函數,實現這一步。
DllCanUnloadNow:?com服務中的全局變量用于保存它的狀態,客戶端可以周期性的調用這個函數,檢查組件服務器是否在使用,然后把它卸載。
DllGetClassObject:?這是完成組件輸出的函數,這個輸出需要3個參數,創建組件的GUID,要創建組件接口的GUID以及創建后指向對象的指針。如果組件對象或者接口不被支持,執行將失敗。
到現在為止,我們應該注意到一件事情,就是如果不是因為間接訪問,com將什么也不是。實際上,DllGetClassObject函數返回的對象不是我們要尋找的對象,它是類廠對象,一個類廠對象了解如何實例化其他任何的類。第一層的間接訪問允許組件創建的細節被指定,如果它僅僅是簡單而又直接的返回一個我們要尋找的對象指針,那么說明對象已經存在,那樣,我們將不能設置和控制關于構造對象的任何參數。
DllGetClassObject返回一個IClassFactory接口,這個接口是從IUnknown派生的,另外他還有自己的兩個重要的成員函數。
??HRESULT?CreateInstance(
IUnknown?*?pUnkOuter,???//Pointer?to?outer?object?when?part?of?an?
//?aggregate?REFIID?riid,?
REFIID?riid,???????????//Reference?to?the?interface?identifier
oid**?ppvObject);???????//Address?of?output?variable?that?receives?
//?the?interface?pointer?requested?in?riid
HRESULT?LockServer(BOOL?fLock);?
//Increments?or?decrements?the?lock?count?
LockServer用來控制類廠對象的引用計數,系統檢查改計數以確定是否要卸載組件,即:控制類廠的生存期。
CreateInstance是最重要的,類廠組件的唯一功能是創建其它組件。一個類廠組件可以對應多種普通COM組件,但每個類廠組件的實例只能創建一種COM組件。
它接收一個接口GUID,返回該接口的指針。它并不接受組件的CLSID,所以一個類廠實例只能夠創建一種COM組件,即傳給?CoGetClassObject的CLSID對應的組件。
客戶、COM庫、組件dll、類廠、組件之間的交互過程:
1.????客戶首先調用COM庫的CoCreateInstance函數來創建COM組件。
2.????CoCreateInstance首先調用COM庫的CoGetClassObject獲取類廠。
3.????該函數具體是通過調用了組件DLL輸出的DllGetClassObject來創建類廠。
4.?????DllGetClassObject通過new函數產生一個Cfactory的對象,并通過QueryInterface獲取其接口指針(一般是IclassFactory指針)。
5.?????返回到COM庫的CoCreateInstance調用剛才獲得的接口指針(IclassFactory,類廠)的CreateInstance函數。
6.?????該函數new指定的組件類,通過QueryInterface獲得指定的接口
7.?????CoCreateInstanse釋放掉IclassFactory指針(通過Release),然后向客戶程序返回獲得的指針。
8.?????可以在客戶中使用獲得的接口了。
在第6步中,根據不同的CLSID創建不同的組件,可以實現一個類廠供該DLL中多個組件共用。但只是類共用,不是實例共用。一旦在創建類廠時通過CoGetClassObject指定了CLSID,則只能創建該COM組件的實例。
在這里我們將深入c++對象模型,來看下一些內部的實現細節。通常編譯器來處理這些。com的設計者充分利用了這些,因此,我們需要了解它。
當我們用匯編寫一個常規的程序時,我依靠編譯器為我們創建代碼段和數據段,內存中的一塊區域是我們執行的代碼,另一塊區域保存了我們需要的數據。
C++運行時動態內存分配,給每一個類實例,每一個小的代碼段它自己的數據段。換句話講,一個類的實例就是這個數據段,每一個類實例的數據描述都是保存在一個動態的數據區域。
或許你聽說過c++傳遞對象成員函數參數時,有一個隱藏的參數,即this指針。當一個人為對象寫一個低層的代碼時(在c++中編譯器會作這個工作,你不需要考慮),
你首先遇到的問題是”我在給哪個對象寫代碼?“
This指針是一個簡單的指針,它指向這個動態數據內存區域的這個類對象實例。當一個類對象函數被調用時,this指針就會被悄悄地傳遞過去。當這個對象的私有數據被訪問時,類的代碼區域就會使用this指針,來找到它的對象實例的數據。
對于一個com接口指針跟this指針很類似。使用中,com是一個接口規范,讓你看不到它的代碼實現。
;?declare?the?ClassFactory?object?structure?
ClassFactoryObject?STRUCT?
lpVtbl???DWORD?0?;?function?table?pointer?
nRefCount???DWORD?0?;?object?and?reference?count
ClassFactoryObject?ENDS?
;?declare?the?MyCom?object?structure?
MyComObject?STRUCT?
lpVtbl???DWORD?0?;?function?table?pointer
nRefCount???DWORD?0?;?reference?count
nValue???DWORD?0?;?interface?private?data
MyComObject?ENDS?
第一個lpVtbl是一個虛表指針,它指向一個虛函數表,我用它來控制每個接口的私有數據。就像這里的nRefCount和nValue。
這些結構所在的動態內存是通過CoTaskMemAlloc和CoTaskMemFree這兩個API函數來分配和釋放的。這兩個函數是由ole32.dll導出的。Ole32.dll還導出了很多的函數,例如比對GUIDs值和把轉換GUIDs為字符串,或者把字符串轉換為GUIDs。
為了舉例說明com接口的工作原理,我們創建一個簡單接口IMyCom(注:所有的com接口都有一個“I”前綴。同其他接口一樣,他派生于IUnknown接口,也就是說他的前三個函數是QueryInterface,?AddRef,?和Release。下面我們添加幾個接口函數。下面看到的是c風格的函數原形:
?
HRESULT?SetValue(long?*pVal);?
HRESULT?GetValue(long?newVal);?
HRESULT?RaiseValue(long?newVal);?
其中,SetValue?和?GetValue用于讀,設置我們接口的數據成員。RaiseValue用于增加這個數據的值。
這個結構在內存中的形式如下:?
??????????
客戶端僅僅擁有一個分布式結構的指針(ppv)這個名字來源于它的c++形式的定義("pointer?to?pointer?to?(void)."),當創建類實例的時候,這個對象數據塊是動態分配和初始化的,虛函數表vtable和server?functions是靜態的,他們在編譯時定義好。
有一點需要注意的是,虛函數表擁有的是函數指針,而并非是函數本身。因此,我們可以修改虛函數表中指向的例程,就可以簡單的"override“一個派生函數。
在例子中,IClassFactory和IMyCom都是派生于IUnknown接口,都繼承了QueryInterface,但是他們支持不同的接口,它們需要指向不同的例程,返回不同的結果。
因此,它們有各自的QueryInterface例程(QueryInterfaceCF?和?QueryInterfaceMC)被不同的虛函數表指向。
同樣的,AddRef和Release也要被不同的支持他們的接口來定制。
類型庫:
每一個com接口都是從系統注冊表中得到信息,這些接口的定義都是由一個被稱為接口定義語言(IDL)來描述的,在windows平臺下,使用MIDL進行編譯。我們可以利用vc開發環境,通過向導來創建一個原始的接口定義文件。
---------------------------------------------------------------------------------------------------------------------On?WinTel?platforms,?
我建一個ATL工程,命名為MyComApp,然后選擇“insert?a?new?ATL?object“,然后選擇“Simple?Object”,命名為:MyCom。這樣就創建了一個空的IMyCom接口,然后通過右鍵菜單,我們添加屬性SetValue和GetValue,并增加一個RaiseValue方法。然后我們保存退出工程,拷貝MyComApp.idl文件到我的匯編工程目錄。
下面就是這個idl文件的內容:
//?MyCom.idl?:?IDL?source?for?MyCom.dll?
//?
//?This?file?will?be?processed?by?the?MIDL?tool?to?
//?produce?the?type?library?(MyCom.tlb)?and?marshalling?code
import?"oaidl.idl";?
import?"ocidl.idl";?
[?
object,?
uuid(F8CE5E41-1135-11d4-A324-0040F6D487D9),?
helpstring("IMyCom?Interface"),?
pointer_default(unique)?
]?
interface?IMyCom?:?IUnknown?
{?
[propget,?helpstring("property?Value")]?
HRESULT?Value([out,?retval]?long?*pVal);?
[propput,?helpstring("property?Value")]?
HRESULT?Value([in]?long?newVal);?
[helpstring("method?Raise")]?
HRESULT?Raise(long?Value);?
};?
[?
uuid(F8CE5E42-1135-11d4-A324-0040F6D487D9),?
version(1.0),?
helpstring("MyComApp?1.0?Type?Library")?
]?
library?MyComLib?
{?
importlib("stdole32.tlb");?
importlib("stdole2.tlb");?
[?
uuid(F8CE5E43-1135-11d4-A324-0040F6D487D9),?
helpstring("MyCom?Class")?
]?
coclass?MyCom?
{?
[default]?interface?IMyCom;?
};?
};?
這個文件可以被用來作為原型進一步進行接口定義。注意這里面有三個GUIDs,一個是為接口,一個是為coclass,一個是為類型庫。對于新的應用,它們的值一定不同。
透過這個定義的文件結構,我們很容易了解他的內容。?
[propget,?helpstring("property?Value")]?HRESULT?Value([out,?retval]?long?*pVal);?[propput,?helpstring("property?Value")]?HRESULT?Value([in]?long?newVal);?[helpstring("method?Raise")]?HRESULT?Raise(long?Value);?
下面是這些接口在masm32中的定義:?
GetValue?PROTO?:DWORD,?:DWORD?
SetValue?PROTO?:DWORD,?:DWORD?
RaiseValue?PROTO?:DWORD,?:DWORD?
??
他們有很大的不同,但是原因很簡單。類型庫中的接口是作為通用的,可以直接被客戶端象VB來使用。
為了創建類型庫,可以使用MIDL命令行來編譯idl文件?:
MIDL?MyCom.idl?
編譯產生的幾個文件,除了MyCom.tlb外,其他的都可以忽略,接下來我們需要把類型庫添加到dll資源文件中。例如:
1?typelib?MyCom.tlb?
讓他作為資源文件中的第一個元素是很重要的,后續我們將會使用LoadTypeLib?API函數來使用這個庫,同時這個函數也是希望在第一位置發現這個庫。
注冊組件:
DllRegisterServer?和?DllUnregisterServer?為我們注冊組件和注銷組件用.內容如下:
?
HKEY_CLASSES_ROOT\CMyCom?
(Default)?"CMyCom?simple?client"?
HKEY_CLASSES_ROOT\CMyCom\CLSID?
(Default)?"{A21A8C43-1266-11D4-A324-0040F6D487D9}"?
HKEY_CLASSES_ROOT\CLSID\{A21A8C43-1266-11D4-A324-0040F6D487D9}?
(Default)?"CMyCom?simple?client"?
HKEY_CLASSES_ROOT\CLSID\{A21A8C43-1266-11D4-A324-0040F6D487D9}\CMyCom?
(Default)?"CMyCom"?
HKEY_CLASSES_ROOT\CLSID\{A21A8C43-1266-11D4-A324-0040F6D487D9}\InprocServer32?
(Default)?"C:\MASM32\MYCOM\MYCOM.DLL"?
ThreadingModel?"Single"?
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}?
(Default)?(value?not?set)?
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0?
(Default)?"MyCom?1.0?Type?Library"?
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\0?
(Default)?(value?not?set)?
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\0\win32?
(Default)?"?C:\masm32\COM\MyCom?\MYCOM.DLL"?
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\FLAGS?
(Default)?"O"?
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\HELPDIR?
(Default)?"C:\masm32\COM\MyCom"?
有一個鍵值是變化的,它是服務dll自身的路徑和文件名,在我的系統上,我把它放置在?"C:\MASM32\COM\MYCOM\MYCOM.DLL",當我注冊組件的時候,這個可以被檢測到,DllRegisterServer通過調用GetModuleFileName可以發現dll自身的存儲位置。
這里有大量的信息是關于這個com服務的,但是我們僅僅需要傳遞{A21A8C43-1266-11D4-A324-0040F6D487D9}這個ID和一個有效的接口ID給CoCreateInstance函數來實例化我們的com服務。這個函數將會跟蹤注冊表設置,利用CLSID來發現創建組件需要的東西,一旦它創建了組件,它將加載類型庫,以獲取更多需要的信息。
對我們來說非常幸運,最后的5個注冊入口項通過RegisterTypeLib函數可以完成。在DllRegisterServer中,我們通過一些列的注冊表函數來設置前面5項鍵值。然后調用RegisterTypeLib。?DllUnregisterServer函數刪除DllRegisterServer中的注冊表項,然后調用UnRegisterTypeLib。注意不要完全刪除HKEY_CLASSES_ROOT\CLSID\
實現?Unknown
AddRef_MC?proc?this_:DWORD
mov?eax,?this_
inc?(MyComObject?ptr?[eax]).nRefCount
mov?eax,?(MyComObject?ptr?[eax]).nRefCount
ret?????????;?note?we?return?the?object?count
AddRef_MC?endp
Release_MC?proc?this_:DWORD
????mov?eax,?this_
????dec?(MyComObject?ptr?[eax]).nRefCount
????mov?eax,?(MyComObject?ptr?[eax]).nRefCount
????.IF?(eax?==?0)
????????;?the?reference?count?has?dropped?to?zero
????????;?no?one?holds?reference?to?the?object
????????;?so?let's?delete?it
????????invoke??CoTaskMemFree,?this_
????????dec?MyCFObject.nRefCount
??xor?eax,?eax????;?clear?eax?(count?=?0)
???.ENDIF
????ret?????????;?note?we?return?the?object?count
Release_MC?endp
MyCom自己的成員實現:
GetValue?proc?this_:DWORD,?pval:DWORD
????mov?eax,?this_
????mov?eax,?(MyComObject?ptr?[eax]).nValue
????mov?edx,?pval
????mov?[edx],?eax
????xor?eax,?eax????????;?return?S_OK
????ret
GetValue?endp
SetValue?proc?this_:DWORD,?val:DWORD
????mov?eax,?this_
????mov?edx,?val
????mov?(MyComObject?ptr?[eax]).nValue,?edx
????xor?eax,?eax????????;?return?S_OK
????ret
SetValue?endp
RaiseValue??PROC?this_:DWORD,?val:DWORD
????mov?eax,?this_
????mov?edx,?val
????add?(MyComObject?ptr?[eax]).nValue,?edx
????xor?eax,?eax????????;?return?S_OK????
????ret
RaiseValue??ENDP
MyCom.dll,?這個com服務工程需要以下5個文件來編譯:
MyCom.asm??匯編源程序
MyCom.idl??IDL文件,用于編譯產生MyCom.tlb?
MyCom.tlb??類型庫,需要一個rc資源文件
rsrc.rc????資源文件,從中可以獲得類型庫信息
MyCom.DEF??標準的dll輸出文件
編譯后,代碼不會做任何事情,直到我們注冊它,我們可以使用命令行:
regsvr32?MyCom.dll注冊。?
上傳的附件
| mycom.rar?(8.2 KB, 183 次下載) | [誰下載?] |
[三]用匯編語言訪問com對象
大量的細節使得Com看上去很復雜,但是使用起來卻很簡單。最難的部分就是理解里面的數據結構,盡管COM是語言無關的,但是他借用了很多c++的術語來描述自己。
為了能使用某個對象的com接口函數,你必須首先要從類廠中創建這個對象,并且讓他來返回接口指針。這個過程被CoCreateInstance這個API函數完成。當你使用完接口時,要調用Release方法。一個COM對象可以看作是一個服務,調用com的應用程序就是他的客戶端。
在調用com接口函數之前,你需要了解接口是什么,一個com接口就是一個函數指針表,我們還是從IUnknown接口開始,如果你創建了一個組件導出了IUnknown接口,那么你就有了一個全功能的com對象。IUnknown有三個基本的幾口方法,既然所有的接口都是從它派生出來,那么我們一定要記住,一個接口實際上就是一個函數指針成員組成的結構體。
例如:
IUnknown?STRUCT?DWORD?
;?IUnknown?methods?
IUnknown_QueryInterface???QueryInterface_Pointer???
IUnknown_AddRef?????AddRef_Pointer???
IUnknown_Release?????Release_Pointer??
IUnknown?ENDS
它只有12個字節長,它具有3個DWORD指針來指向實際的實現函數。對于虛函數表,你一定聽說過,這些指針定義如下,因此我們可以讓masm在編譯我們的調用時進行一些類型檢查。
QueryInterface_Pointer???typedef?ptr?QueryInterface_Proto
AddRef_Pointer?????typedef?ptr?AddRef_Proto?
Release_Pointer?????typedef?ptr?Release_Proto
最后我們定義我們的函數如下:
QueryInterface_Proto???typedef?PROTO?:DWORD,?:DWORD,?:DWORD?
AddRef_Pointer?????typedef?PROTO?:DWORD?
Release_Pointer?????typedef?PROTO?:DWORD?
為了保持masm32松散的類型檢查一致,函數參數都定義為dword
定義接口是一個相當大的編輯就是,masm不支持前向引用。因此,我們不得不顛倒一下定義的順序。先定義函數頭,再定義函數指針,最后定義接口。實際上在使用接口時,你需要一個指向它的指針。
CoCreateInstance函數能用來直接返回一個接口指針。它實際上指向了擁有接口的對象。這個結構看上去如圖所示:
這個結構里有大量的間接訪問,使用宏可以簡化它。
當客戶端調用COM庫創建com組件時,它傳進了一個地址用于存放對象指針。這個就是我們所說的ppv.?從c++的角度來講,叫做指向指針的指針,void類型代表無類型。它保存了另一個指針pv的地址。pv指向了虛函數表。
例如:我們使用CoCreateInstance函數成功的返回了一個接口指針ppv,我們想看下它是否支持其他的接口,我們可以調用QueryInterface方法。用c++的方法描述QueryInterface如下:
(HRESULT)?SomeObject::QueryInterface?(this:pObject,?IID:pGUID,?ppv2:pInterface)
用匯編寫法如下:?
;?get?pointer?to?the?object?
mov?eax,?ppv?
;?and?use?it?to?find?the?interface?structure
mov?edx,?[eax]?
;?push?the?function?parameters?onto?the?stack
push?OFFSET?ppv2
push?OFFSET?IID_ISomeOtherInterface
push?dword?ppv
;?and?then?call?that?method?
call?dword?ptr?[edx?+?0]
使用invoke調用簡化如下:
;?get?pointer?to?the?object?
mov?eax,?ppv?
;?and?use?it?to?find?the?interface?structure
mov?edx,?[eax]?
;?and?then?call?that?method?
invoke?(IUnknown?PTR?[edx]).IUnknown_QueryInterface,?ppv,
????ADDR?IID_SomeOtherInterface,?ADDR?ppv_new
注意IUnknown?PTR?[edx]這個類型轉換,是讓編譯器知道使用哪個結構來得到QueryInterface函數在虛表中的正確偏移。其中有一個模糊的地方,注意我修改了函數名字為"IUnknown_QueryInterface",這個名字修飾時必要的。當你有一個大的com工程,有許多相似的接口,你就會遇到麻煩。不同的接口對應不同的方法表示,是非常有效的。
coinvoke?宏,這個宏定義在oaidl.inc文件中。使用它,可以進一步簡化com調用。
;---------------------------------------------------------------------
;?coinvoke?MACRO?
;
;
;?pInterface????pointer?to?a?specific?interface?instance
;?Interface?????the?Interface's?struct?typedef
;?Function??????which?function?or?method?of?the?interface?to?perform
;?args??????????all?required?arguments?
;???????????????????(type,?kind?and?count?determined?by?the?function)
;
coinvoke?MACRO?pInterface:REQ,?Interface:REQ,?Function:REQ,?args:VARARG
????LOCAL?istatement,?arg
????FOR?arg,?<args>?????;;?run?thru?args?to?see?if?edx?is?lurking?in?there
????????IFIDNI?<&arg>,?<edx>
????????????.ERR?<edx?is?not?allowed?as?a?coinvoke?parameter>
????????ENDIF
????ENDM
????istatement?CATSTR?<invoke?(Interface?PTR[edx]).&Interface>,<_>,<&Function,?pInterface>
????IFNB?<args>?????;;?add?the?list?of?parameter?arguments?if?any
????????istatement?CATSTR?istatement,?<,?>,?<&args>?
????ENDIF?
????mov?edx,?pInterface
????mov?edx,?[edx]
????istatement
ENDM
;---------------------------------------------------------------------
?
因此,前面的QueryInterface方法調用就可以簡化成:?
coinvoke?ppv?,IUnknown,?QueryInterface,?ADDR?IID_SomeOtherInterface,?
ADDR?ppnew
注意這里名字修飾是隱藏在宏中處理的。
代碼后面附上。。。?
上傳的附件
| client.rar?(1.6 KB, 174 次下載) | [誰下載?] |
總結
以上是生活随笔為你收集整理的masm32开发com组件介绍[一][二][三]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (附源码)springboot仓库管理系
- 下一篇: 应用运维工程师(技术二面)面试笔记