WinForm引用ActiveX組件,對Com組件的學習

1、WinForm引用Adobe PDF Reader

工作中寫WinForm程序經常會引用第三方的組件,包括引用Com組件,做了一個桌面程序需要展示PDF,看了些其它的開源組件對PDF的兼容性都不是很好,有些看着PDF是正常的但是複製出來的字有很多亂碼。然後就直接引用了adboe pdf reader來顯示,測試了不同pdf兼容性算是不錯的。那如何引用呢?

  • 在工具欄選擇項

  • 添加Com組件
    找到Adobe PDF Reader勾選,然後點擊確定之後組件就被添加到工具箱裏面了。

  • 使用Com組件
    新建一個窗體或者用戶控件,將剛纔添加的Adobe PDF Reader 組件拖入到窗體中就可以像winform控件一樣操作該控件了。

在該窗體類中生成了一個AxAcroPDFLib.AxAcroPDF的控件,進入該控件類可以看到控件類對外提供的方法,包括用於加載顯示pdf的 LoadFile 方法,gotoFirstPage 等翻頁的方法。

而該控件有一個父類AxHost類,進入Axhost類有一個摘要:

包裝 ActiveX 控件,並將它們作爲功能完整的 Windows 窗體控件公開

對此我陷入了沉思,ActiveX控件到底是什麼,com組件如何被使用,AxAxAcroPDFLib.AxAcroPDF類是如何生成的,Winform和Com如何互操作?於是我進行了一番資料查找和學習。

2、ActiveX控件

ActiveX控件技術基於由COM,可連接對象,複合文檔,屬性頁,OLE自動化,對象持久性以及系統提供的字體和圖片對象組成的基礎。
控件本質上是一個COM對象,它公開IUnknown接口,客戶端可以通過該對象獲取指向其其他接口的指針。控件可以通過IClassFactory2和自我註冊來支持許可。
也就是說ActiveX控件是基於COM對象的,使用COM技術讓不同語言編寫的控件可以進行互相調用,而如何編寫ActiveX控件呢,可以使用ATL 和 MFC,但是兩個我都沒使用過!並且沒編寫過,所以我就略過,只先了解其概念。既然它是基於COM,那接下來看看COM是什麼東東。

3、COM技術

Microsoft組件對象模型(COM)定義了一個二進制互操作性標準,用於創建在運行時進行交互的可重用軟件庫。您可以使用COM庫,而無需將其編譯到應用程序中。COM是許多Microsoft產品和技術(例如Windows Media Player和Windows Server)的基礎。
COM定義了適用於許多操作系統和硬件平臺的二進制標準。對於網絡計算,COM爲在不同硬件平臺上運行的對象之間的交互定義了標準的有線格式和協議。COM獨立於實現語言,這意味着您可以使用其他編程語言(例如C ++和.NET Framework中的編程語言)創建COM庫。
COM規範提供了支持跨平臺軟件重用的所有基本概念:
組件之間的函數調用的二進制標準。
將功能強類型分組到接口中的規定。
提供多態性,功能發現和對象生存期跟蹤的基本接口。
唯一標識組件及其接口的機制。
組件加載器,可從部署中創建組件實例。
COM具有多個部分,這些部分可以一起工作以創建由可重用組件構建的應用程序:
一個主機系統提供了一個運行時環境符合的COM規範。
定義要素合同的接口和實現接口的組件。
爲系統提供組件的服務器,以及使用組件提供的功能的客戶端。
一個註冊表,用於跟蹤組件在本地和遠程主機上的部署位置。
一個服務控制管理器,可以在本地和遠程主機上找到組件,並將服務器連接到客戶端。
一種結構化的存儲協議,它定義瞭如何導航主機文件系統上文件的內容。
跨主機和平臺啓用代碼重用對於COM至關重要。可重用的接口實現被稱爲組件,組件對象或COM對象。組件實現一個或多個COM接口。
您可以通過設計庫實現的接口來定義自定義COM庫。圖書館的使用者可以發現和使用其功能,而無需瞭解圖書館的部署和實施細節。

這是官方的定義,當然還有很多細節說明可以看看https://docs.microsoft.com/zh-cn/windows/win32/com/com-technical-overview 其中包括實現的定義和方式,對象和接口、接口實現、IUnknown接口等等。

那是如何實現如何調用呢,引用一段有趣的概括性的描述:

COM主要是一套給C/C++用的接口,當然爲了微軟的野心,它也被推廣到了VB、Delphi以及其他一大堆奇奇怪怪的平臺上。它主要爲了使用dll發佈基於interface的接口。我們知道dll的接口是爲了C設計的,它導出的基本都是C的函數,從原理上來說,將dll加載到內存之後,會告訴你一組函數的地址,你自己call進去就可以調用相應的函數。
但是對於C++來說這個事情就頭疼了,現在假設你有一個類,我們知道使用一個類的第一步是創建這個類:new MyClass()。這裏直接就出問題了,new方法通過編譯器計算MyClass的大小來分配相應的內存空間,但是如果庫升級了,相應的類可能會增加新的成員,大小就變了,那麼使用舊的定義分配出來的空間就不能在新的庫當中使用。
要解決這問題,我們必須在dll當中導出一個CreateObject的方法,用來代替構造函數,然後返回一個接口。然而,接口的定義在不同版本當中也是有可能會變化的,爲了兼容以前的版本同時也提供新功能,還需要讓這個對象可以返回不同版本的接口。接口其實是一個只有純虛函數的C++類,不過對它進行了一些改造來兼容C和其他一些編程語言。
在這樣改造之後,出問題的還有析構過程~MyClass()或者說delete myClass,因爲同一個對象可能返回了很多個接口,有些接口還在被使用,如果其中一個被人delete了,其他接口都會出錯,所以又引入了引用計數,來讓許多人可以共享同一個對象。
其實到此爲止也並不算是很奇怪的技術,我們用C++有的時候也會使用Factory方法來代替構造函數實現某些特殊的多態,也會用引用計數等等。COM技術的奇怪地方在於微軟實在是腦洞太大了,它們構造了一個操作系統級別的Factory,規定所有人的Interface都統一用UUID來標識,以後想要哪個Interface只要報出UUID來就行了。這樣甚至連鏈接到特定的dll都省了。
這就好比一個COM程序員,只要他在Windows平臺上,調用別的庫就只要首先翻一下魔導書,查到了一個用奇怪文字寫的“Excel = {xxx-xxx-xxxx...}”的記號,然後它只要對着空中喊一聲:“召喚,Excel!CoCreateInstance, {xxx-xxx-xxxx...}”
然後呼的從魔法陣裏面竄出來了一個怪物,它長什麼樣我們完全看不清,因爲這時候它的類型是IUnknow,這是腦洞奇大無比的微軟爲所有接口設計的一個基類。我們需要進一步要求它變成我們能控制的接口形態,於是我們再喊下一條指令:
“變身,Excel 2003形態!QueryInterface, {xxx-xxx-xxxx...}”
QueryInterface使用的是另一個UUID,用來表示不同版本的接口。於是怪物就變成了我們需要的Excel 2003接口,雖然我們不知道它實際上是2003還是2007還是更高版本。
等我們使喚完這隻召喚獸,我們就會對它說“回去吧,召喚獸!Release!”但是它不一定聽話,因爲之前給它的命令也許還沒有執行完,它會忠誠地等到執行完再回去,當然我們並不關心這些細節。(引用地址:https://www.zhihu.com/question/49433640)

從這個概括理解,所有的COM類其實都繼承了IUnknown,當我們拿到IUnknown接口後還需要轉成我們需要使用的類型,而這個類型如果用強轉可能會出錯,但是微軟認爲,直接由用戶來轉型是不安全的需要唯一的一個標識符來確定一個類,那麼這個標識符就是GUID。類ID就叫作CLSID,接口ID就叫作IID,還需要一個轉型的函數叫QueryInterface。QueryInterface作爲IUnknown中的一個純虛函數,做的事情其實很簡單,判斷自己能不能轉成某個GUID所指向的類而已。如果不可以,則返回E_NOTIMPL,可以的話返回S_OK,並將轉換後的指針作爲參數返回。
COM組件並不需要名字,或者說不需要UUID,因爲我們總是使用他裏面的接口,而不是直接使用COM組件,所以接口也要UUID。說了這麼多,COM架構這麼複雜,肯定需要一箇中間層,或者說擺渡人,這就是COM Library(一堆dll) + 註冊表。A應用通知COM Library,並輸入接口的UUID,由COM Library裝入B應用的該組件對應的dll,並把接口指針返回給A應用,指針裏指示的是一堆函數指針,由這些指針,可以調用到B應用裏的函數功能。

注:上面有時說的UUID,有時說的GUDI,UUID即是GUID值。

4、Aximp.exe(Windows 窗體 ActiveX 控件導入程序)

有了上面的ActiveX控件和Com組件的介紹,我們再回到開始我們如何導入的ActiveX控件。
ActiveX 控件導入程序將 ActiveX 控件的 COM 類型庫中的類型定義轉換爲 Windows 窗體控件。
Windows 窗體只能承載 Windows 窗體控件,即從 Control 派生的類。 Aximp.exe 生成可承載於 Windows 窗體上的 ActiveX 控件的包裝器類。 這使你得以使用適用於其他 Windows 窗體控件的同一設計時支持和編程方法。
若要承載 ActiveX 控件,必鬚生成從 AxHost 派生的包裝器控件。 此包裝器控件包含基礎 ActiveX 控件的一個實例。 它知道如何與 ActiveX 控件通信,但它顯示爲 Windows 窗體控件。 這個生成的控件承載 ActiveX 控件並將其屬性、方法和事件作爲生成的控件的屬性、方法和事件公開。
由此可見當我們再工具箱裏面選擇添加com組件後實際隱含執行了該導入程序,爲我們生成了對應的AxAcroPDFLib.AxAcroPDF包裝器控件。而AxAcroPDFLib則如同第三點中講的那樣就是COM Library。

5、驗證

既然AxAcroPDFLib 是擺渡人(互操作程序集) 那麼我們可以看到這個COM Library的引用

有了互操作程序那麼這個互操作程序必然是去調用COM組件,調用COM組件那麼UUID呢?將這個程序集放到Dnspy反編譯可以看到在ClsidAttribute標記有{ca8a9780-280d-11cf-a24d-444553540000},構造函數裏面有UUID。

然後我們打開註冊表查詢下對應的值和註冊表的情況。

6、總結

所以通過上面的概念瞭解和猜想驗證,基本清楚了com的設計和想法,以及ActiveX控件的調用過程。

  1. Activex控件時COM實現的一種方式。
  2. Activex控件通過VS工具引用時調用了Aximp.exe 。
  3. Aximp.exe程序生成了互操作程序集AxAcroPDFLib,同時生成可承載於 Windows 窗體上的 ActiveX 控件的從 AxHost 派生的包裝器控件。
  4. 調用AxAcroPDF方法時通過com組件調用引用控件的功能。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章