windows -COM技術+DLL

Office中的COM對象簡介

開發者可以使用多種程序語言通過COM技術操作文檔對象而無需瞭解文件格式,大大豐富了應用程序功能。
COM技術的侷限性表現在較以來Windows操作系統,不支持其他類型操作系統平臺。
COM程序開發遵照下面的5個步驟:

  1. 安裝office軟件產品
  2. 創建程序項目
  3. 項目添加引用Office產品的COM庫
  4. 源代碼中添加相應的命名空間
  5. 使用類方法操作Office對象

動態鏈接庫

Windows操作系統提供給開發者的接口Applied Programming Interdface簡稱API,是程序使用操作系統功能和服務的途徑,它不僅幫助用戶程序執行Windows任務,還是Windows系統運行的重要組成部分。API聚集在動態鏈接庫(DLL)文件中,程序通過申明外部函數引用DLL中的函數。

分別編譯與鏈接

大多數高級語言都支持分別編譯(compiling),程序員可以顯式地把程序劃分爲獨立的模塊或文件,然後由編譯器(compiler)對每個獨立部分分別進行編譯。在編譯之後,由**鏈接器(Linker)把這些獨立編譯單元鏈接(Linking)**到一起。
鏈接方式有兩種:靜態鏈接、動態鏈接

鏈接方式

  • 靜態鏈接方式:
    在程序開發中,將各種目標模塊(.OBJ)文件、運行時庫(.LIB)文件,以及已編譯的資源(.RES)文件鏈接在一起,以便創建Windows的.EXE文件
  • 動態鏈接方式:
    在程序運行時,Windows把一個模塊中的函數調用鏈接到庫模塊中的實際函數上的過程。
    靜態鏈接庫(簡稱LIB)與動態鏈接庫(簡稱DLL)都是共享代碼的方式。

如果使用靜態鏈接庫(也稱靜態庫),則無論你願不願意,.LIB文件中的指令都會被直接包含到最終生成的.EXE文件中。
但是若使用.DLL文件,該.DLL文件中的代碼不必被包含在最終的.EXE文件中,.EXE文件執行時可以“動態”地載入和卸載這個與.EXE文件獨立的.DLL文件。

動態鏈接方式

鏈接一個DLL有兩種方式:
1、載入時動態鏈接(Load-Time Dynamic Linking)
2、運行時動態鏈接(Run-Time Dynamic Linking)

使用載入時動態鏈接,調用模塊可以像調用本模塊中的函數一樣直接使用導出函數名調用DLL中的函數。這需要在鏈接時將函數所在DLL的導入庫鏈接到可執行文件中,導入庫向系統提供了載入DLL時所需的信息及用於定位DLL函數的地址符號。(相當於註冊,當作API函數來使用,其實API函數就存放在系統DLL當中)。

使用運行時動態鏈接,運行時可以通過LoadLibrary或LoadLibraryEx函數載入DLL。DLL載入後,模塊可以通過調用GetProcAddress獲取DLL函數的入口地址,然後就可以通過返回的函數指針調用DLL中的函數了。如此即可避免導入庫文件了。

靜態鏈接與動態鏈接二者優點及不足

靜態鏈接庫的優點:
(1) 代碼裝載速度快執行速度略比動態鏈接庫快;
(2) 只需保證在開發者的計算機中有正確的.LIB文件,在以二進制形式發佈程序時不需考慮在用戶的計算機上.LIB文件是否存在及版本問題,可避免DLL地獄等問題。
動態鏈接庫的優點
(1) 更加節省內存並減少頁面交換;
(2) DLL文件與EXE文件獨立,只要輸出接口不變(即名稱、參數、返回值類型和調用約定不變),更換DLL文件不會對EXE文件造成任何影響,因而極大地提高了可維護性和可擴展性;
(3) 不同編程語言編寫的程序只要按照函數調用約定就可以調用同一個DLL函數。

不足之處
(1) 使用靜態鏈接生成的可執行文件體積較大,包含相同的公共代碼,造成浪費;
(2) 使用動態鏈接庫的應用程序不是自完備的,它依賴的DLL模塊也要存在,如果使用載入時動態鏈接,程序啓動時發現DLL不存在,系統將終止程序並給出錯誤信息。而使用運行時動態鏈接,系統不會終止,但由於DLL中的導出函數不可用,程序會加載失敗
(3) 使用動態鏈接庫可能造成DLL地獄

動態鏈接庫的詳細分析

獨特的動態鏈接庫(Dynamic Link Library,DLL)

DLL是Windows系統中非常有效的運行機制,通用功能的函數和數據聚集在DLL文件中。DLL減少程序文件的大小和對內存空間的需求,又使公共函數在多個應用程序之間共享。DLL文件是獨立編譯和測試的,多種編程語言和編譯器都支持生成和使用DLL。不同編程語言生成的DLL函數還可互相調用
Windows 系統中非常重要的動態鏈接文件有KERNEL32.DLL、USER32.dll和GDI32.dll,其中包含了大量的系統核心函數稱爲API。驅動程序文件如KEYBOARD.DRV、SYSTEM.DRV、MOUSE.DRV和打印機驅動程序也都是動態鏈接庫,還有以.FON、.SYS和許多以.EXE爲拓展名的系統文件都可以是DLL。

動態鏈接庫原理

動態鏈接庫(DLL)意思爲Dynamic Link Library,這是Windows系統平臺上提供的一種較有效的編程和運行機制,用戶可以將獨立的程序模塊創建爲較小的DLL(Dynamic Linkable Library)文件,並可對它們單獨編譯和測試,DLL就是一個包含可由多個程序同時使用的代碼和數據的庫
DLL模塊可以同時被多個應用程序使用,DLL實現了代碼封裝性,它的編制與具體的編程語言及編譯器無關不同編程語言生成的DLL函數可以互相調用
減少了EXE文件的大小和對內存空間的需求,是一種軟件複用技術。

動態鏈接庫運行機制/windows的虛地址映射

DLL文件中的內容由全局數據、服務函數和資源組成,程序運行時使用LoadLibrary或者LoadLibraryEx函數來加載DLL模塊,WIndows實現地址映射工作,例如一個DLL文件被加載後在物理內存中只佔一個固定區域,有多個進程使用同一個DLL文件,Windows將這個DLL的內存地址空間通過地址映射後提供給各個進程,進程代碼地址與DLL映射後地址構成的是進程的虛地址空間,進程在自己的虛地址空間中好像是自己獨立在使用這個DLL文件,使用DLL中函數的與自身的函數沒有區別。DLL有自己的數據段,沒有自己的堆棧,使用與調用它的應用程序相同的堆棧模式,它在運行時需要分配的內存是屬於調用它的進程的,不同程序即使調用相同函數所分配的內存互相也不會影響,DLL函數中的代碼所創建的任何對象(包括變量)都歸調用它的線程或進程所有

Windows中主要的dll

Windows API主要以dll的形式封裝並提供底層功能調用
各種驅動程序文件如KEYBOARD.DRV、SYSTEM.DRV和MOUSE.DRV和音視頻及打印機驅動程序也都是動態鏈接庫,還有以.FON、.SYS和許多以.EXE爲擴展名的系統文件都可以是DLL
在這裏插入圖片描述

dll中函數參數與返回值

函數設計考慮三個問題:輸入參數,輸出和函數功能,參數個數可以是0個或多個,還可在函數形參定義時採用params關鍵字定義可變數目參數。
獲取函數運算結果有return方式和參數引用方式(ref、out)
C#規定函數參數的使用有三種:a.傳值 b. ref 和 c.out
ref要求函數調用前已被賦初值,out則不需要

dll的引用計數

在Windows環境中,每個進程都複製了自己的讀/寫全局變量,如果想要與其他進程共享內存,可以使用內存映射文件,或者聲明一個共享數據段。一個DLL在內存中只有一個實例,系統爲每個DLL維護一個線程級的引用計數,一旦一個線程載入了該DLL,引用計數將會加1.而程序終止引用計數變爲0(僅指運行時動態鏈接庫),系統會釋放DLL佔用的虛地址空間。不指定DLL文件完整路徑時候,Windows將遵循一定的規則。

DLL文件的定位

包含EXE文件的目錄
進程的當前工作目錄
Windows系統目錄
Windows目錄
Path環境變量中的一系列目錄

動態鏈接庫相關工具

DLL地獄問題

DLL 地獄(DLL Hell)是指因爲系統文件被覆蓋而讓整個系統像是掉進了地獄。
簡單地講,DLL地獄是指當多個應用程序試圖共享一個公用組件時,如某個DLL或某個組件對象模型(COM)類,所引發的一系列問題。
重新編譯生成的DLL可能造成意想不到的後果,用戶程序使用新版本的DLL庫不能正常工作稱爲DLL低於(DLL Hell)問題,特別是DLL形式的COM組件在升級後,依賴它的軟件會無法使用。
在.Net 平臺中採用自我描述與版本管理功能,實現 Side by Side 技術,應用程序安裝成功就不必擔心 DLL 的更新問題,它允許一個 DLL 的多個編譯版本在同一臺機器上運行,每一個應用程序可使用指定的 DLL 編譯版本,不再發生 DLL Hell 問題。

託管與非託管

託管代碼與非託管代碼是微軟針對運行中的windows程序與公共語言運行庫的關係進行的一種劃分

託管代碼和非託管代碼

  1. 在運行時公共語言運行庫(CLR)控制下執行的代碼稱爲託管代碼。,通過高級語言(C#,VB,F#等)相應的編譯器將其編譯爲中間語言(IL),CLR將其編譯成機器代碼,然後執行。
    託管代碼應用程序可以獲得公共語言運行庫服務,例如自動垃圾回收、運行庫類型檢查和安全支持等。這些服務幫助提供獨立於平臺和語言的、統一的託管代碼應用程序行爲。
    託管代碼可以通過GC垃圾回收機制進行內存的管理和釋放。
  2. 在運行時以外運行的代碼成爲非託管代碼。如:COM組件、ActiveX接口和Win32 API函數都是非託管代碼的示例。非託管代碼需要手動釋放資源
    非託管代碼與公共語言運行庫環境無關。編寫這些程序代碼使用專用語言編譯工具如C++與VB,生非託管代碼與公共語言運行庫環境無關。編寫這些程序代碼使用專用語言編譯工具如C++與VB,生成的是機器可以直接執行的二進制代碼,在這些程序中,用戶必須自己提供內存的申請和釋放,要保證指針引用的正確性,進行類型檢查等功能,稍有不慎即容易發生地址越界,內存泄露等錯誤,而且機器也難由這些錯誤中恢復回來

最簡單的說呢,受託管的代碼不能直接寫內存,是安全的,而非託管代碼是非安全代碼,可以使用指針操作內存。

託管與非託管區別

託管代碼中不推薦使用指針,而非託管代碼可以使用指針來直接讀取內存。
託管DLL和非託管DLL的區別。狹義解釋講,託管DLL就在Dotnet環境生成的DLL文件。非託管DLL不是在Dotnet環境生成的DLL文件。託管DLL文件,可以在Dotnet環境通過 “添加引用” 的方式,直接把託管DLL文件添加到項目中。然後通過 Using DLL命名空間,來調用相應的DLL對象 。非託管DLL文件,在Dotnet環境應用時,通過 DllImport 調用。C# 調用非託管DLL文件。DLL文件是用C語言編寫的。

託管代碼動態鏈接庫

dll文件與調用程序是各自編譯的

提取DLL中的類方法和屬性

在沒有dll文件的源代碼的情況下,使用C#生成的類庫,可應用反射機制獲得dll文件中的類方法和屬性

反射

通過 System.Reflection 命名空間中的類以及 System.Type,可以獲取有關已加載的程序集和在其中定義的類型(如類、接口和值類型)的信息。也可以使用反射在運行時創建類型實例調用和訪問這些實例
使用 Assembly 定義和加載程序集,加載在程序集清單中列出的模塊,以及從此程序集中查找類型並創建該類型的實例。
使用 Module 發現以下信息:包含模塊的程序集以及模塊中的類等。
您還可以獲取在模塊上定義的所有全局方法或其他特定的非全局方法。
使用 ConstructorInfo 發現以下信息:構造函數的名稱、參數、訪問修飾符(如 public 或 private)和實現詳細信息(如 abstract 或 virtual)等。
使用 Type 的 GetConstructors 或 GetConstructor 方法來調用特定的構造函數。

使用 MethodInfo 發現以下信息:方法的名稱、返回類型、參數、訪問修飾符(如 public 或 private)和實現詳細信息(如 abstract 或 virtual)等。使用 Type 的 GetMethods 或 GetMethod 方法來調用特定的方法。
使用 FieldInfo 發現以下信息:字段的名稱、訪問修飾符和實現詳細信息(如 static)等;並獲取或設置字段值。
使用 EventInfo 發現以下信息:事件的名稱、事件處理程序\數據類型、自定義屬性、聲明類型和反射類型等;並添加或移除事件處理程序。
使用 PropertyInfo 發現以下信息:屬性的名稱、數據類型、聲明類型、反射類型和只讀或可寫狀態等;並獲取或設置屬性值。
使用 ParameterInfo 發現以下信息:參數的名稱、數據類型、參數是輸入參數還是輸出參數,以及參數在方法簽名中的位置等。

非託管代碼參數的封送與調用

CLR類似java虛擬機提供運行時環境,它負責資源管理(內存分配和垃圾收集),在應用程序和底層操作系統之間完成必要的分離。CLR進數據指針值傳入非託管代碼中行數據參數封送有兩種方式:多數情況下數據值執行復制,特定情況下將數據指針值傳入非託管代碼中

非託管的動態鏈接庫

API聲明爲外部:

  1. extern修飾符聲明調用外部API
  2. 使用DllImport 指定庫文件和方法的參數格式
  3. 使用static關鍵詞聲明方法爲靜態

[ DllImport( “xxxxx.dll", EntryPoint=“yyy" )]
public static extern int MessageBox(int h, string m, string c, int type);

DllImport屬性

[ DllImport( “kernel32.dll”,EntryPoint=“GetVersionEx” )]
“DllImport”屬性用來從不可控代碼中調用一個方法,它指定了DLL的相對/絕對地址;
EntryPoint指示要調用的 DLL 入口點的名稱或序號—DLL中的函數指針
CharSet控制調用函數的名稱版本
CallingConvention指示向非託管實現傳遞方法參數

class DllTest
    {
        [DllImport(@"../../../Release/dll_cpp.dll", EntryPoint ="testAdd", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = false, CallingConvention = CallingConvention.StdCall)]
        public static extern int testAdd(int a, int b);

        [DllImport(@"../../../Release/dll_cpp.dll", EntryPoint = "testMulti", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = false, CallingConvention = CallingConvention.StdCall)]
        public static extern int testMulti(int a, int b);
    }

DllImport函數wrapper

調用非託管的動態鏈接庫需要使用 Interop 服務
extern 修飾符用於聲明在外部實現的方法,與 DllImport 屬性一起使用,且將方法聲明爲 static
[DllImport(“User32.dll”)]
public static extern int MessageBox(int h, string m, string c, int type);
函數返回值及函數參數類型與創建動態鏈接庫中的函數參數保持一致

  • 數值型直接用對應的就可(DWORD -> int , WORD -> Int16)
  • API中字符串指針類型-> .net中string
  • API中句柄 (dWord) --> .net中IntPtr
  • API中結構struct --> .net中結構或者類。注意這種情況下,要先用StructLayout特性限定聲明結構或類,雖然比較複雜,在用到時查示例就好,不需死記

COM和DLL區別

COM英文爲Component Object Model(組件對象模型),是微軟生產軟件組件的標準。
它是構造二進制兼容軟件組件的規範,不管組件應用何種語言編寫只要遵循com規範就可以相互直接通信。提出com規範主要是爲了滿足:
1.程序的快速開發,可以將一個大型的工程分成若干個com組件同時開發。
2.可以動態的插入或卸載 com組件。
3.可以 隱藏或封裝com 組件內部的實現細節。
COM組件可以由不同的語言進行編寫,但COM組件之間的通信是通過組件的接口來實現的,COM組件接口的實現是統一的,它採用的是虛擬函數表(VTBL)形式。虛擬函數表中包含了組件函數的一組指針,我們可以通過這組指針來獲取我們想要通信的組件函數的內存地址。dll(動態鏈接庫)是包含函數和數據的模塊的集合。它可以導出數據也可以導出函數以供其它的dll調用。dll的加載可以通過靜態鏈接和動態鏈接兩種方式。

1.靜態鏈接時將所要鏈接的dll模塊以二進制的形式編譯進其他模塊。
2.動態鏈接指調用模塊在運行時加載DLL,使用LoadLibrary函數或LoadLibraryEx函數將dll加載到進程的地址空間,並調用GetProcAddress函數以獲取導出的 DLL函數的地址。

DLL與COM的關係:COM是一種規範,按照是COM規範實現的DLL可以被視爲COM組件,

例如我們用mfc建立的Active X控件工程其中的接口封裝是靠idl描述的所以可以視爲com組件。而且從上面關於com和dll的說明可以看出com組件的接口是一組具有特定規範的函數,所以com組件可被視爲dll但dll不一定是com組件

COM和DLL 最大的區別 就是: dll是以函數集合的方式來調用的是編程語言相關的,像VC必須加上extern “C”…而COM是以interface的方式提供給用戶使用的是一種二進制的調用規範,是與編程語言無關的,它使用idl接口定義語言來描述自己使用類繼承來實現自己的功能和方法.DLL只有DLL一種形式,裏面可任意定義函數無限制,只能運行在本機上。而COM有DLL和EXE兩種存在形勢: COM所在的DLL中必須導出四個函數:dllgetobjectclass,dllregisterserver, dllunregisterserver,dllunloadnow這四個函數各有作用,有些是提供給COM管理器用的,通過CLSID和IID來使用,有些是提供給註冊機用的.COM結合MTS,就是COM+, 是DCOM的高級版本,提供了更爲強大和安全的分佈式COM服務,DCOM運行在不同的機器上 用proxy和stub來實現遠程接口的本地映射 二者從執行速度來說 二者相差無幾 但是啓動速度DLL要比COM快!

com組件可以由不同的語言進行編寫,但com組件之間的通信是通過組件的接口來實現的,com組件接口的實現是統一的,它採用的是虛擬函數表VTBL形式。虛擬函數表中包含了組件函數的一組指針,我們可以通過這組指針來獲取我們想要通信的組件函數的內存地址。dll(動態鏈接庫)是包含函數和數據的模塊的集合。它可以導出數據也可以導出函數以供其它的dll調用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章