COM 元件
Source Code 的作用CBSE — Component Based System Engineering
interface)Run Time 時與元件的相互連結而完成系統功能COM 是一套規範
Win32 動態連結程式庫( DLL )及以 .EXE 形式存在的可執行檔location transparent﹚:用戶端程式可以將遠端機器上的元件當成本地端機器上的元件一樣使用COM 規定所有的介面都必須繼承 IUnknown介面,因此每個COM介面都一定包含三個函式
QueryInterfaceAddRefRelease
COM 運作情境
COM Object 都是屬於特定類別(class) 的個體(instance)。COM 程式庫得知道物件的類別,才能開始執行該物件的真正個體CLSID 作為鍵值在 Windows 的 Registry 登錄資料庫中記錄元件所在的DLL檔案名稱
CoCreateInstance 利用 CLSID 作機碼查得檔案的名稱,Registry 中新增紀錄或讀取資料。COM 的用戶端程式就是利用 Registry 找出所要使用的元件Client 端在建立物件透過 CoCreateInstance 取得該物件第一個介面指標後,就可以呼叫 QueryInterface,並傳入該介面的 IID,利用IUnknown::QueryInterface,向物件要求取得其他方法所在介面的指標。如果成功的話,就可以使用傳回的指標,便得知物件所提供某個特定介面。
COM 物件的實作部分都位於伺服器(Server)中。Server 中含有真正用來實作物件方法的程式碼,並且負責維護物件的資料。COM 有三種類型:
In-process Server:物件的實作係位於動態連結函式庫中,執行時與 client 端為於同一 process 中。Local Server:物件的實作位於與客戶端相同的機器中,但是在分開的行程內。Remote Server:物件的實作位於與客戶端不同的機器內所執行的 DLL 或獨立行程中。跨機器的分散式系統必須借助Distributed COM (DCOM) 的支援才得以完成。DCOM 是一個高階的網路協定,建立於 COM 之上的規格和服務,讓位於不同電腦上的行程內之 COM 元件可以互相運作
目前在元件的開發方面發展,有三大陣營
DCOM (Distributed Component Object Model)
Microsoft 的 DCOM 就是根據其原本非分散式物件模組 COM 所進化而來的COM+又叫 DCOM 它是 COM 的加強版,主要可遠端執行。DCOM 其實是二次元層次 (Binary Level) 的分散式物件介面標準。NT 及 Windows 98,Internet 之策略,Active X 就是其中的表表者。CORBA (Common Object Request Broker Architecture)Java RMI (Remote Method Invocation)IUnknown 介面介紹
// IUnknown 介面定義
interface IUnknown
{
virtual HRESULT __stdcall QueryInterface(const IID& iid, void **ppv) = 0;
virtual ULONG __stdcall AddRef() = 0;
virtual ULONG __stdcall Release() = 0;
};
// 自定義介面範例
{
object ,
uuid( E7CD0D00-1827-11CF-9946-4445535542231 )
}
interface Imyclass : IUnknown
{
import "unknwn.idl" ;
HRESULT myFuntion1();
HRESULT myFunction2();
}
CoCreateInstance 的定義
STDAPI CoCreateInstance(
REFCLSID rclsid , /// Class identifier of the object
LPUNKNOWN pUnkOut , /// Pointer to whether object is or isn't part of an aggregate
DWORD dwClsContext , /// Context for running executable code
REFIID riid , /// Reference to the identifier of the interface
LPVOID *ppv /// Indirect pointer to the request interface
)
// 其中,dwClsContext的值是列舉CLSCTX所定義:
typedef enum tagCLSCTX
{
CLSCTX_INPROC_SERVER = 1 ,
CLSCTX_INPROC_HANDLER = 2 ,
CLSCTX_LOCAL_SERVER = 4 ,
CLSCTX_REMOTE_SERVER = 16
} CLSCTX;
COM 元件製作
VB6/C/C++ 開發的 unmanaged 元件,又稱 ActiveX DLL。使用 regsvr32.exe 註冊。.NET 開發的類別庫專案,又稱 Managed DLL。使用 regasm.exe 註冊。在 .NET 中的 COM 使用教學
.NET 專案中引用 COM 元件
COM 元件的型別轉成 .NET 型別
COM 元件和 .NET 元件在使用上有所不同,COM 元件必須先登錄(registered)後才可使用。Regsvr32.exe 可以註冊 COM 元件regsvr32 person.dllCOM 元件中的型別必須轉換成 CLR 認得的型別,這樣子才可以在 .NET 專案中引用 COM 元件。TlbImp.exe (型別程式庫匯入工具) ,就可以將 COM 型別程式庫中的型別定義轉換為 CLR 組件中的等效定義。tlbimp ComLib.dll /out: NetLib.dll.NET 專案中加入 COM 元件參考
COM 元件,另一個簡單的方法就是直接在 Visual Studio 開發工具裡頭,直接加入該 COM 元件的參考。 因為加入參考時,Visual Studio 就會自動將 COM 元件中的型別定義轉換為 CLR 組件中的等效定義,同時登錄 COM 元件。COM 元件加入參考後,它會出現在[COM 頁籤]中的清單。.Net 程式中使用 COM 元件
COM 元件的型別程式庫之後,使用程式庫中的物件,就跟直接使用 .NET 建立的物件一樣。 例如 MS Speech Object Library 是一套可用來將文字轉語音的 COM 元件,當我們匯入這個 COM 元件之後,就可以輕輕鬆鬆利用這個元件來叫電腦說話。如下程式碼範例: // 加入參考 Microsoft Speech Object 這個 COM 元件
using SpeechLib;
namespace CSharpComUsingDemo
{
class Program
{
static void Main(string[] args)
{
SpVoice voiceDemo = new SpVoice();
voiceDemo.Voice = voiceDemo.GetVoices(string.Empty, string.Empty).Item(0);
voiceDemo.Speak("各位好! Nice to Meet you!,我會英文也會中文。", SpeechVoiceSpeakFlags.SVSFDefault);
}
}
}
COM Interop 的錯誤處理
System.Exception 是所有 Exception 物件的最上層類別,但是在 .NET 2.0 之前,它是無法攔截 COM 元件所引發的錯誤的,因為 System.Exception 只能補捉 CLS 相容的錯誤 (CLS - compliant exception, Common Language Specification)。 而 COM 元件所引發的錯誤是 non-CLS-compliant exceptions 的。.NET 2.0 開始, System.Runtime.CompilerServices 命名空間增加了 RuntimeWrappedException 類別,這個類別繼承自 System.Exception ,可用來包裝不是衍生自 System.Exception 類別的例外狀況。 RuntimeWrappedException 類別中的 WrappedException 屬性就是用來包裝產生例外的物件。RuntimeCompatibilityAttribute 可用來指定是否使用 RuntimeWrappedException 物件來包裝不是衍生自 Exception 類別的例外狀況。COM Interop 限制
Static/shared members:COM 物件不支援 static/shared 成員。Parameterized constructors:COM 物件不允許傳參數給 constructor。Inheritance:無法辨識基底類別中的遮蔽成員 (shadow members),繼承能力上有所限制。Portability:COM 物件必須被註冊後才可使用,非 Windows 的其他系統沒有登入資料庫 (registry),所以無法使用在其他平台。.Net Core 建立 COM 元件依據在 Github 上 Net Sample
core\extensions\COMServerDemoCOMClient
COM 引用範例dotnet new console COMClientCOMClient.manifest <?xml version="1.0" encoding="utf-8"?>
<!-- https://docs.microsoft.com/windows/desktop/sbscs/assembly-manifests -->
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity
type="win32"
name="COMClient"
version="1.0.0.0" />
<dependency>
<dependentAssembly>
<!-- RegFree COM matching the registration of the generated managed COM server -->
<assemblyIdentity
type="win32"
name="COMServer.X"
version="1.0.0.0"/>
</dependentAssembly>
</dependency>
</assembly>
COMClient 專案設定檔
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<!-- The application manifest is only needed for RegFree COM scenarios -->
<ApplicationManifest Condition="'$(RegFree)' == 'True'">COMClient.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<!-- Used in lieu of a Primary Interop Assembly (PIA) -->
<Compile Include="../COMContract/*.cs" />
</ItemGroup>
</Project>
COM 介面
namespace COMClient
{
class Program
{
//......... static void Main(string[] args) { .... }
}
namespace Activation
{
/// <summary>
/// Managed definition of CoClass
/// </summary>
[ComImport]
[CoClass(typeof(ServerClass))]
[Guid(ContractGuids.ServerInterface)] // By TlbImp convention, set this to the GUID of the parent interface
internal interface Server : IServer
{
}
/// <summary>
/// Managed activation for CoClass
/// </summary>
[ComImport]
[Guid(ContractGuids.ServerClass)]
internal class ServerClass
{
}
}
}
using System;
using System.Runtime.InteropServices;
namespace COMClient
{
class Program
{
static void Main(string[] args)
{
var server = new Activation.Server();
var pi = server.ComputePi();
Console.WriteLine($"\u03C0 = {pi}");
Console.Read();
}
}
}
dotnet.exe build /p:RegFree=Truedotnet.exe cleanCOMClient\bin\Debug\netcoreapp3.0\COMClient.exe 檔案COMContract
COM 元件的 GUID 及 方法介面
ContractGuids
internal sealed class ContractGuids
{
// 定義 COM 元件類別及介面的 GUID
public const string ServerClass = "DB1797F5-7198-4411-8563-D05F4E904956";
public const string ServerInterface = "BA9AC84B-C7FC-41CF-8B2F-1764EB773D4B";
}
IServer
using System;
using System.Runtime.InteropServices;
[ComVisible(true)]
[Guid(ContractGuids.ServerInterface)] //定義該類別使用的 GUID 碼
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IServer
{
/// <summary>
/// 定義 COM 元件方法介面
/// Compute the value of the constant Pi.
/// </summary>
double ComputePi();
}
COMServer
COM 元件內部實作
using System;
using System.Runtime.InteropServices;
namespace COMServer
{
[ComVisible(true)]
[Guid(ContractGuids.ServerClass)]
public class Server : IServer
{
// 實作 元件 IServer 介面方法
double IServer.ComputePi()
{
double sum = 0.0;
int sign = 1;
for (int i = 0; i < 1024; ++i)
{
sum += sign / (2.0 * i + 1.0);
sign *= -1;
}
return 4.0 * sum;
}
}
}
dotnet new classlib COMServerCOMServer 的專案設定檔
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!--
COM servers must define a framework to use in situations where a CLR instance is not already present in the process.
Note that since the COM server may be activated in a process where the CLR must be activated, both the projects
*.runtimeconfig.json and *.deps.json files must be bundled with the server itself.
In the following example, the following files are needed to deploy the COM server:
COMServer.comhost.dll
COMServer.dll
COMServer.deps.json
COMServer.runtimeconfig.json
In a RegFree COM scenario, the following file must also be deployed:
COMServer.X.manifest
-->
<TargetFramework>netcoreapp3.0</TargetFramework>
<!-- Indicate the assembly is providing a COM server -->
<EnableComHosting>True</EnableComHosting>
<!-- Generate a RegFree COM manifest -->
<EnableRegFreeCom Condition="'$(RegFree)' == 'True'">True</EnableRegFreeCom>
</PropertyGroup>
<ItemGroup>
<!-- Used in lieu of a Primary Interop Assembly (PIA) -->
<Compile Include="../COMContract/*.cs" />
</ItemGroup>
<Target Name="ServerUsage"
Condition="'$(RegFree)' != 'True'"
AfterTargets="Build">
<Message Importance="High" Text="%0a************************************%0a*** $(MSBuildProjectName) usage instructions ***%0a************************************" />
<Message Importance="High" Text="The server must be COM registered in order to activate.%0aThe following commands must be executed from an elevated command prompt." />
<Message Importance="High" Text="Register:%0a regsvr32.exe "$(ProjectDir)$(OutputPath)$(ComHostFileName)"" />
<Message Importance="High" Text="Unregister:%0a regsvr32.exe /u "$(ProjectDir)$(OutputPath)$(ComHostFileName)"" />
</Target>
<Target Name="ServerUsage_RegFree"
Condition="'$(RegFree)' == 'True'"
AfterTargets="Build">
<Message Importance="High" Text="%0a************************************%0a*** $(MSBuildProjectName) usage instructions ***%0a************************************" />
<Message Importance="High" Text="A RegFree COM manifest has been created for the server.%0aThe manifest '@(RegFreeComManifest->'%(Filename)%(Extension)')' must be included during server deployment.%0aThe COMServer project will copy all required outputs to the COMClient output directory." />
<ItemGroup>
<ServerOutput Include="$(OutputPath)*.dll" />
<ServerOutput Include="$(OutputPath)*.runtimeconfig.json" />
<ServerOutput Include="$(OutputPath)*.deps.json" />
<ServerOutput Include="$(OutputPath)*.manifest" />
</ItemGroup>
<!-- Deploy all required server outputs -->
<Copy SourceFiles="@(ServerOutput)"
DestinationFolder="../COMClient/$(OutputPath)" />
</Target>
<Target Name="Clean_RegFree"
AfterTargets="Clean">
<ItemGroup>
<ServerOutputToDelete Include="../COMClient/$(OutputPath)COMServer.*" />
</ItemGroup>
<!-- Cleanup deployed server outputs -->
<Delete Files="@(ServerOutputToDelete)" />
</Target>
</Project>
參考來源