COM
元件
Source Code
的作用CBSE
— Component Based System Engineering
interface
)Run Time
時與元件的相互連結而完成系統功能COM
是一套規範
Win32
動態連結程式庫( DLL
)及以 .EXE
形式存在的可執行檔location transparent
﹚:用戶端程式可以將遠端機器上的元件當成本地端機器上的元件一樣使用COM
規定所有的介面都必須繼承 IUnknown介面
,因此每個COM介面都一定包含三個函式
QueryInterface
AddRef
Release
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.dll
COM
元件中的型別必須轉換成 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\COMServerDemo
COMClient
COM
引用範例dotnet new console COMClient
COMClient.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=True
dotnet.exe clean
COMClient\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 COMServer
COMServer
的專案設定檔
<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>
參考來源