日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

MASM32开发COM组件【二】

發(fā)布時間:2024/3/26 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 MASM32开发COM组件【二】 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

組件對象模型com是以win32 dll或exe形式發(fā)布的執(zhí)行代碼組成的。Com是由一些對象和對象的接口組成,在com里,接口提供對象操作的機制。 而接口是由一個或者多個相關(guān)的方法、屬性、事件組成的。在這里我們開發(fā)一個簡單的但是功能齊全的一個進程內(nèi)com組件(即以dll形式存在)。?
這里假設(shè)你已經(jīng)了解了com對象模型的基礎(chǔ)知識,了解什么是虛表,什么是虛函數(shù)表指針。如果你不熟悉這些,建議看看《com本質(zhì)論》這本書。?
我們先來分析下進程內(nèi)com服務(wù)的組成。由于它是一個dll形式發(fā)布的,其中包括5個重要的函數(shù)。其中后面的四個是要作為dll導出函數(shù)來導出的。?
DllMain: 這是動態(tài)鏈接庫德第一個入口函數(shù),它在庫被加載的時候被調(diào)用,通過這個函數(shù),可以對客戶端程序進行檢查。?
DllRegisterServer:通過這個函數(shù)能夠?qū)崿F(xiàn)組建的自我注冊,注冊信息作為資源保存在動態(tài)鏈接庫中,這個函數(shù)能夠讀取資源,把信息寫進注冊表,使用?
regsvr32.exe 注冊組建時,實際上是調(diào)用了組件輸出的這個函數(shù)。?
DllUnregisterServer: 當一個組件不再使用時,這個組件應(yīng)該能夠提供自我卸載,regsvr32.exe能調(diào)用這個函數(shù),實現(xiàn)這一步。?
DllCanUnloadNow: com服務(wù)中的全局變量用于保存它的狀態(tài),客戶端可以周期性的調(diào)用這個函數(shù),檢查組件服務(wù)器是否在使用,然后把它卸載。?
DllGetClassObject: 這是完成組件輸出的函數(shù),這個輸出需要3個參數(shù),創(chuàng)建組件的GUID,要創(chuàng)建組件接口的GUID以及創(chuàng)建后指向?qū)ο蟮闹羔槨H绻M件對象或者接口不被支持,執(zhí)行將失敗。?
到現(xiàn)在為止,我們應(yīng)該注意到一件事情,就是如果不是因為間接訪問,com將什么也不是。實際上,DllGetClassObject函數(shù)返回的對象不是我們要尋找的對象,它是類廠對象,一個類廠對象了解如何實例化其他任何的類。第一層的間接訪問允許組件創(chuàng)建的細節(jié)被指定,如果它僅僅是簡單而又直接的返回一個我們要尋找的對象指針,那么說明對象已經(jīng)存在,那樣,我們將不能設(shè)置和控制關(guān)于構(gòu)造對象的任何參數(shù)。?
DllGetClassObject返回一個IClassFactory接口,這個接口是從IUnknown派生的,另外他還有自己的兩個重要的成員函數(shù)。?
? 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用來控制類廠對象的引用計數(shù),系統(tǒng)檢查改計數(shù)以確定是否要卸載組件,即:控制類廠的生存期。?
CreateInstance是最重要的,類廠組件的唯一功能是創(chuàng)建其它組件。一個類廠組件可以對應(yīng)多種普通COM組件,但每個類廠組件的實例只能創(chuàng)建一種COM組件。?
它接收一個接口GUID,返回該接口的指針。它并不接受組件的CLSID,所以一個類廠實例只能夠創(chuàng)建一種COM組件,即傳給 CoGetClassObject的CLSID對應(yīng)的組件。?
客戶、COM庫、組件dll、類廠、組件之間的交互過程:?
1.??? 客戶首先調(diào)用COM庫的CoCreateInstance函數(shù)來創(chuàng)建COM組件。?
2.??? CoCreateInstance首先調(diào)用COM庫的CoGetClassObject獲取類廠。?
3.??? 該函數(shù)具體是通過調(diào)用了組件DLL輸出的DllGetClassObject來創(chuàng)建類廠。?
4.???? DllGetClassObject通過new函數(shù)產(chǎn)生一個Cfactory的對象,并通過QueryInterface獲取其接口指針(一般是IclassFactory指針)。?
5.???? 返回到COM庫的CoCreateInstance調(diào)用剛才獲得的接口指針(IclassFactory,類廠)的CreateInstance函數(shù)。?
6.???? 該函數(shù)new指定的組件類,通過QueryInterface獲得指定的接口?
7.???? CoCreateInstanse釋放掉IclassFactory指針(通過Release),然后向客戶程序返回獲得的指針。?
8.???? 可以在客戶中使用獲得的接口了。?
在第6步中,根據(jù)不同的CLSID創(chuàng)建不同的組件,可以實現(xiàn)一個類廠供該DLL中多個組件共用。但只是類共用,不是實例共用。一旦在創(chuàng)建類廠時通過CoGetClassObject指定了CLSID,則只能創(chuàng)建該COM組件的實例。?
在這里我們將深入c++對象模型,來看下一些內(nèi)部的實現(xiàn)細節(jié)。通常編譯器來處理這些。com的設(shè)計者充分利用了這些,因此,我們需要了解它。?
當我們用匯編寫一個常規(guī)的程序時,我依靠編譯器為我們創(chuàng)建代碼段和數(shù)據(jù)段,內(nèi)存中的一塊區(qū)域是我們執(zhí)行的代碼,另一塊區(qū)域保存了我們需要的數(shù)據(jù)。?
C++運行時動態(tài)內(nèi)存分配,給每一個類實例,每一個小的代碼段它自己的數(shù)據(jù)段。換句話講,一個類的實例就是這個數(shù)據(jù)段,每一個類實例的數(shù)據(jù)描述都是保存在一個動態(tài)的數(shù)據(jù)區(qū)域。?
或許你聽說過c++傳遞對象成員函數(shù)參數(shù)時,有一個隱藏的參數(shù),即this指針。當一個人為對象寫一個低層的代碼時(在c++中編譯器會作這個工作,你不需要考慮),?
你首先遇到的問題是”我在給哪個對象寫代碼?“?
This指針是一個簡單的指針,它指向這個動態(tài)數(shù)據(jù)內(nèi)存區(qū)域的這個類對象實例。當一個類對象函數(shù)被調(diào)用時,this指針就會被悄悄地傳遞過去。當這個對象的私有數(shù)據(jù)被訪問時,類的代碼區(qū)域就會使用this指針,來找到它的對象實例的數(shù)據(jù)。?
對于一個com接口指針跟this指針很類似。使用中,com是一個接口規(guī)范,讓你看不到它的代碼實現(xiàn)。?
; 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是一個虛表指針,它指向一個虛函數(shù)表,我用它來控制每個接口的私有數(shù)據(jù)。就像這里的nRefCount和nValue。?
這些結(jié)構(gòu)所在的動態(tài)內(nèi)存是通過CoTaskMemAlloc和CoTaskMemFree這兩個API函數(shù)來分配和釋放的。這兩個函數(shù)是由 ole32.dll導出的。Ole32.dll還導出了很多的函數(shù),例如比對GUIDs值和把轉(zhuǎn)換GUIDs為字符串,或者把字符串轉(zhuǎn)換為GUIDs。?
為了舉例說明com接口的工作原理,我們創(chuàng)建一個簡單接口IMyCom(注:所有的com接口都有一個“I”前綴。同其他接口一樣,他派生于 IUnknown接口,也就是說他的前三個函數(shù)是QueryInterface, AddRef, 和Release。下面我們添加幾個接口函數(shù)。下面看到的是c風格的函數(shù)原形:?
HRESULT SetValue(long *pVal);??
HRESULT GetValue(long newVal);??
HRESULT RaiseValue(long newVal);??
其中,SetValue 和 GetValue用于讀,設(shè)置我們接口的數(shù)據(jù)成員。RaiseValue用于增加這個數(shù)據(jù)的值。?
這個結(jié)構(gòu)在內(nèi)存中的形式如下:??
客戶端僅僅擁有一個分布式結(jié)構(gòu)的指針(ppv)這個名字來源于它的c++形式的定義("pointer to pointer to (void)."),當創(chuàng)建類實例的時候,這個對象數(shù)據(jù)塊是動態(tài)分配和初始化的,虛函數(shù)表vtable 和server functions是靜態(tài)的,他們在編譯時定義好。?
有一點需要注意的是,虛函數(shù)表擁有的是函數(shù)指針,而并非是函數(shù)本身。因此,我們可以修改虛函數(shù)表中指向的例程,就可以簡單的"override“一個派生函數(shù)。?
在例子中,IClassFactory和IMyCom都是派生于IUnknown接口,都繼承了QueryInterface,但是他們支持不同的接口,它們需要指向不同的例程,返回不同的結(jié)果。?
因此,它們有各自的QueryInterface例程(QueryInterfaceCF 和 QueryInterfaceMC)被不同的虛函數(shù)表指向。?
同樣的,AddRef和Release也要被不同的支持他們的接口來定制。?
類型庫:?
每一個com接口都是從系統(tǒng)注冊表中得到信息,這些接口的定義都是由一個被稱為接口定義語言(IDL)來描述的,在windows平臺下,使用MIDL進行編譯。我們可以利用vc開發(fā)環(huán)境,通過向?qū)韯?chuàng)建一個原始的接口定義文件。?
———————————————————————————————————————On WinTel platforms,??
我建一個ATL工程,命名為MyComApp,然后選擇“insert a new ATL object“,然后選擇“Simple Object”,命名為:MyCom。這樣就創(chuàng)建了一個空的IMyCom接口,然后通過右鍵菜單,我們添加屬性SetValue和GetValue,并增加一個 RaiseValue方法。然后我們保存退出工程,拷貝MyComApp.idl文件到我的匯編工程目錄。?
下面就是這個idl文件的內(nèi)容:?
// 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,一個是為類型庫。對于新的應(yīng)用,它們的值一定不同。?
透過這個定義的文件結(jié)構(gòu),我們很容易了解他的內(nèi)容。??
[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來使用。?
為了創(chuàng)建類型庫,可以使用MIDL命令行來編譯idl文件 :?
MIDL MyCom.idl??
編譯產(chǎn)生的幾個文件,除了MyCom.tlb外,其他的都可以忽略,接下來我們需要把類型庫添加到dll資源文件中。例如:?
1 typelib MyCom.tlb??
讓他作為資源文件中的第一個元素是很重要的,后續(xù)我們將會使用LoadTypeLib API函數(shù)來使用這個庫,同時這個函數(shù)也是希望在第一位置發(fā)現(xiàn)這個庫。?
注冊組件:?
DllRegisterServer 和 DllUnregisterServer 為我們注冊組件和注銷組件用.內(nèi)容如下:?
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??
(Default) (value not set)??
HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.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"??
有一個鍵值是變化的,它是服務(wù)dll自身的路徑和文件名,在我的系統(tǒng)上,我把它放置在 "C:\MASM32\COM\MYCOM \MYCOM.DLL",當我注冊組件的時候,這個可以被檢測到,DllRegisterServer通過調(diào)用GetModuleFileName可以發(fā)現(xiàn)dll自身的存儲位置。?
這里有大量的信息是關(guān)于這個com服務(wù)的,但是我們僅僅需要傳遞{A21A8C43-1266-11D4-A324-0040F6D487D9}這個ID 和一個有效的接口ID給CoCreateInstance函數(shù)來實例化我們的com服務(wù)。這個函數(shù)將會跟蹤注冊表設(shè)置,利用CLSID來發(fā)現(xiàn)創(chuàng)建組件需要的東西,一旦它創(chuàng)建了組件,它將加載類型庫,以獲取更多需要的信息。?
對我們來說非常幸運,最后的5個注冊入口項通過RegisterTypeLib函數(shù)可以完成。在DllRegisterServer中,我們通過一些列的注冊表函數(shù)來設(shè)置前面5項鍵值。然后調(diào)用RegisterTypeLib。 DllUnregisterServer函數(shù)刪除 DllRegisterServer中的注冊表項,然后調(diào)用UnRegisterTypeLib。注意不要完全刪除 HKEY_CLASSES_ROOT\CLSID\?
實現(xiàn) Unknown?01? AddRef_MC?proc?this_:DWORD?
02??mov?eax, this_?
03??inc?(MyComObject?ptr?[eax]).nRefCount?
04??mov?eax, (MyComObject?ptr?[eax]).nRefCount?
05??ret?????????; note we return the object count?
06? AddRef_MC?endp?
07? Release_MC?proc?this_:DWORD?
08??????mov?eax, this_?
09??????dec?(MyComObject?ptr?[eax]).nRefCount?
10??????mov?eax, (MyComObject?ptr?[eax]).nRefCount?
11??????.IF?(eax?== 0)?
12??????????; the reference count has dropped to zero?
13??????????; no one holds reference to the object?
14??????????; so let’s delete it?
15??????????invoke? CoTaskMemFree, this_?
16??????????dec?MyCFObject.nRefCount?
17????xor?eax,?eax????; clear eax (count = 0)?
18?????.ENDIF?
19??????ret?????????; note we return the object count?
20? Release_MC?endp?
21??;MyCom自己的成員實現(xiàn):?
22? GetValue?proc?this_:DWORD, pval:DWORD?
23??????mov?eax, this_?
24??????mov?eax, (MyComObject?ptr?[eax]).nValue?
25??????mov?edx, pval?
26??????mov?[edx],?eax?
27??????xor?eax,?eax????????; return S_OK?
28??????ret?
29? GetValue?endp?
30? SetValue?proc?this_:DWORD, val:DWORD?
31??????mov?eax, this_?
32??????mov?edx, val?
33??????mov?(MyComObject?ptr?[eax]).nValue,?edx?
34??????xor?eax,?eax????????; return S_OK?
35??????ret?
36? SetValue?endp?
37? RaiseValue??PROC?this_:DWORD, val:DWORD?
38??????mov?eax, this_?
39??????mov?edx, val?
40??????add?(MyComObject?ptr?[eax]).nValue,?edx?
41??????xor?eax,?eax????????; return S_OK????
42??????ret?
43? RaiseValue??ENDP?
MyCom.dll, 這個com服務(wù)工程需要以下5個文件來編譯:?
MyCom.asm? 匯編源程序?
MyCom.idl? IDL文件,用于編譯產(chǎn)生MyCom.tlb??
MyCom.tlb? 類型庫,需要一個rc資源文件?
rsrc.rc??? 資源文件,從中可以獲得類型庫信息?
MyCom.DEF? 標準的dll輸出文件?
編譯后,代碼不會做任何事情,直到我們注冊它,我們可以使用命令行:?
regsvr32 MyCom.dll注冊。

?

源代碼:mycom.rar

總結(jié)

以上是生活随笔為你收集整理的MASM32开发COM组件【二】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。