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

XSpringSun發表於2020-12-20

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元件呼叫引用控制元件的功能。

相關文章