教學課程:使用 ComWrappers
API
在本教學課程中,您會了解如何正確為 ComWrappers
型別設定子類別,以提供最佳化且 AOT 友善的 COM Interop 解決方案。 開始本教學課程之前,您應該先熟悉 COM、其架構,以及現有的 COM Interop 解決方案。
在本教學課程中,您將會實作下列介面定義。 這些介面及其實作將會示範:
- 跨 COM/.NET 界限封送和解除封送型別。
- 在 .NET 中使用原生 COM 物件的兩種不同方法。
- 在 .NET 5 和更新版本中啟用自訂 COM Interop 的建議模式。
本教學課程中使用的所有原始程式碼都可在 dotnet/範例存放庫中取得。
注意
在 .NET 8 SDK 和更新版本中,會提供來源產生器來自動為您產生 ComWrappers
API 實作。 如需詳細資訊,請參閱 ComWrappers
程式碼產生。
C# 定義
interface IDemoGetType
{
string? GetString();
}
interface IDemoStoreType
{
void StoreString(int len, string? str);
}
Win32 C++ 定義
MIDL_INTERFACE("92BAA992-DB5A-4ADD-977B-B22838EE91FD")
IDemoGetType : public IUnknown
{
HRESULT STDMETHODCALLTYPE GetString(_Outptr_ wchar_t** str) = 0;
};
MIDL_INTERFACE("30619FEA-E995-41EA-8C8B-9A610D32ADCB")
IDemoStoreType : public IUnknown
{
HRESULT STDMETHODCALLTYPE StoreString(int len, _In_z_ const wchar_t* str) = 0;
};
ComWrappers
設計概觀
ComWrappers
API 的設計目的是要提供透過 .NET 5+ 執行階段完成 COM Interop 所需的最少互動。 這表示內建 COM Interop 系統存在的許多好處都不存在,而且必須從基本建置組塊建置。 API 的兩個主要責任如下:
- 有效率的物件識別 (例如,在
IUnknown*
執行個體與受控物件之間對應)。 - 記憶體回收 (GC) 整合。
這些效率是要求包裝函式建立和取得才能通過 ComWrappers
API,藉以完成。
因為 ComWrappers
API 有這麼少的責任,所以代表大部分 Interop 工作都應該由取用者處理 - 確實如此。 不過,額外的工作主要是機械式工作,而且可由來源產生解決方案執行。 例如,C#/WinRT 工具鏈是一種來源產生解決方案,建置在 ComWrappers
基礎上,提供 WinRT Interop 支援。
實作 ComWrappers
子類別
提供 ComWrappers
子類別表示提供足夠的資訊給 .NET 執行階段,以建立和記錄要投影至 COM 的受控物件以及要投影至 .NET 之 COM 物件的包裝函式。 在查看子類別的大綱之前,我們應該定義一些詞彙。
受控物件包裝函式 - 受控 .NET 物件需要包裝函式才能從非 .NET 環境使用。 這些包裝函式過去稱為 COM 可呼叫包裝函式 (CCW)。
原生物件包裝函式 - 在非 .NET 語言中實作的 COM 物件需要包裝函式才能從 .NET 使用。 這些包裝函式過去稱為執行階段可呼叫包裝函式 (RCW)。
步驟 1 - 定義實作的方法並了解其意圖
若要擴充 ComWrappers
型別,您必須實作下列三種方法。 這些方法都代表使用者參與建立或刪除包裝函式型別。 ComputeVtables()
和 CreateObject()
方法會分別建立受控物件包裝函式和原生物件包裝函式。 ReleaseObjects()
方法是由執行階段用來提出,從基礎原生物件「釋放」提供的包裝函式集合的要求。 在大部分情況下,ReleaseObjects()
方法的主體可以直接擲回 NotImplementedException,因為其只會在涉及參考追蹤器架構的進階案例中呼叫。
// See referenced sample for implementation.
class DemoComWrappers : ComWrappers
{
protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) =>
throw new NotImplementedException();
protected override object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags) =>
throw new NotImplementedException();
protected override void ReleaseObjects(IEnumerable objects) =>
throw new NotImplementedException();
}
若要實作 ComputeVtables()
方法,請決定您想要支援的受控型別。 在本教學課程中,我們將會支援先前定義的兩個介面 (IDemoGetType
和 IDemoStoreType
),以及實作兩個介面的受控型別 (DemoImpl
)。
class DemoImpl : IDemoGetType, IDemoStoreType
{
string? _string;
public string? GetString() => _string;
public void StoreString(int _, string? str) => _string = str;
}
針對 CreateObject()
方法,您也必須決定您想要支援的內容。 不過,在此情況下,我們只知道我們感興趣的 COM 介面,不知道 COM 類別。 從 COM 端取用的介面,與我們從 .NET 端投影的介面 (也就是 IDemoGetType
和 IDemoStoreType
) 相同。
我們不會在此教學課程中實作 ReleaseObjects()
。
步驟 2 - 實作 ComputeVtables()
讓我們從受控物件包裝函式開始,這些包裝函式會比較容易。 您將會為每個介面建置虛擬方法資料表或 vtable,以便將其投影到 COM 環境。 在本教學課程中,您會將 vtable 定義為一連串的指標,其中每個指標都代表介面上函式的實作 - 順序在這裡非常重要。 在 COM 中,每個介面都會繼承自 IUnknown
。 IUnknown
型別具有以下列順序定義的三個方法:QueryInterface()
、AddRef()
和 Release()
。 在 IUnknown
方法到達特定的介面方法之後。 例如,請考量 IDemoGetType
和 IDemoStoreType
。 概念上而言,型別的 vtable 看起來會像下面這樣:
IDemoGetType | IDemoStoreType
==================================
QueryInterface | QueryInterface
AddRef | AddRef
Release | Release
GetString | StoreString
查看 DemoImpl
,我們已經有 GetString()
和 StoreString()
的實作,但是函式 IUnknown
呢? 如何實作 IUnknown
執行個體超出本教學課程的範圍,但可以在 ComWrappers
中手動完成。 不過,在本教學課程中,您會讓執行階段處理該部分。 您可以使用 ComWrappers.GetIUnknownImpl()
方法來取得 IUnknown
實作。
您可能已實作所有方法,但不幸的是,只有 IUnknown
函式可在 COM vtable 中取用。 由於 COM 在執行階段之外,因此您必須建立 DemoImpl
實作的原生函式指標。 這可以使用 C# 函式指標和 UnmanagedCallersOnlyAttribute
來完成。 您可以建立會模擬 COM 函式簽章的 static
函式,以建立要插入 vtable 的函式。 以下是 IDemoGetType.GetString()
的 COM 簽章範例 - 從 COM ABI 重新叫用,第一個引數是執行個體本身。
[UnmanagedCallersOnly]
public static int GetString(IntPtr _this, IntPtr* str);
IDemoGetType.GetString()
的包裝函式實作應該包含封送邏輯,然後分派至要包裝的受控物件。 分派的所有狀態都包含在提供的 _this
引數內。 _this
引數實際上會是 ComInterfaceDispatch*
型別。 此型別代表具有單一欄位的低階結構 Vtable
,稍後會進行討論。 此型別的進一步詳細資料及其配置是執行階段的實作詳細資料,不應相依。 若要從 ComInterfaceDispatch*
執行個體擷取受控執行個體,請使用下列程式碼:
IDemoGetType inst = ComInterfaceDispatch.GetInstance<IDemoGetType>((ComInterfaceDispatch*)_this);
既然您已擁有可插入 vtable 的 C# 方法,您可以建構 vtable。 請注意,以可與無法載入組件搭配使用的方式,使用 RuntimeHelpers.AllocateTypeAssociatedMemory()
來配置記憶體。
GetIUnknownImpl(
out IntPtr fpQueryInterface,
out IntPtr fpAddRef,
out IntPtr fpRelease);
// Local variables with increment act as a guard against incorrect construction of
// the native vtable. It also enables a quick validation of final size.
int tableCount = 4;
int idx = 0;
var vtable = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(
typeof(DemoComWrappers),
IntPtr.Size * tableCount);
vtable[idx++] = fpQueryInterface;
vtable[idx++] = fpAddRef;
vtable[idx++] = fpRelease;
vtable[idx++] = (IntPtr)(delegate* unmanaged<IntPtr, IntPtr*, int>)&ABI.IDemoGetTypeManagedWrapper.GetString;
Debug.Assert(tableCount == idx);
s_IDemoGetTypeVTable = (IntPtr)vtable;
vtable 的配置是實作 ComputeVtables()
的第一個部分。 您也應該針對您打算支援的型別建構完整的 COM 定義 - 思考 DemoImpl
以及其哪些部分應該可從 COM 使用。 使用建構的 vtable,您現在可以建立一系列 ComInterfaceEntry
執行個體,代表 COM 中受控物件的完整檢視。
s_DemoImplDefinitionLen = 2;
int idx = 0;
var entries = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(
typeof(DemoComWrappers),
sizeof(ComInterfaceEntry) * s_DemoImplDefinitionLen);
entries[idx].IID = IDemoGetType.IID_IDemoGetType;
entries[idx++].Vtable = s_IDemoGetTypeVTable;
entries[idx].IID = IDemoStoreType.IID_IDemoStoreType;
entries[idx++].Vtable = s_IDemoStoreVTable;
Debug.Assert(s_DemoImplDefinitionLen == idx);
s_DemoImplDefinition = entries;
受控物件包裝函式的 vtable 和項目配置可以預先完成,因為資料可以用於型別的所有執行個體。 這裡的工作可以在 static
建構函式或模組初始設定式中執行,但應該事先完成,讓 ComputeVtables()
方法盡可能簡單且快速。
protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags,
out int count)
{
if (obj is DemoImpl)
{
count = s_DemoImplDefinitionLen;
return s_DemoImplDefinition;
}
// Unknown type
count = 0;
return null;
}
實作 ComputeVtables()
方法之後,ComWrappers
子類別就能夠針對 DemoImpl
的執行個體產生受控物件包裝函式。 請注意,從對於 GetOrCreateComInterfaceForObject()
的呼叫傳回的受控物件包裝函式屬於 IUnknown*
型別。 如果傳遞至包裝函式的原生 API 需要不同的介面,則必須執行該介面的 Marshal.QueryInterface()
。
var cw = new DemoComWrappers();
var demo = new DemoImpl();
IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);
步驟 3 - 實作 CreateObject()
建構具有更多實作選項的原生物件包裝函式,而且比建構受控物件包裝函式更細微。 要解決的第一個問題是 ComWrappers
子類別在支援 COM 型別方面有多寬鬆。 若要支援所有 COM 型別,這是辦得到的,您必須撰寫大量程式碼,或採用一些聰明的 Reflection.Emit
用法。 在本教學課程中,您只會支援同時實作 IDemoGetType
和 IDemoStoreType
的 COM 執行個體。 由於您知道集合有限,並且有任何提供的 COM 執行個體都必須實作這兩個介面的限制,因此您可以提供單一靜態定義的包裝函式;不過,動態案例在 COM 中很常見,我們將會探索這兩個選項。
靜態原生物件包裝函式
讓我們先看看靜態實作。 靜態原生物件包裝函式牽涉到定義實作 .NET 介面的受控型別,而且可以將受控型別上的呼叫轉送至 COM 執行個體。 靜態包裝函式的粗略大綱如下。
// See referenced sample for implementation.
class DemoNativeStaticWrapper
: IDemoGetType
, IDemoStoreType
{
public string? GetString() =>
throw new NotImplementedException();
public void StoreString(int len, string? str) =>
throw new NotImplementedException();
}
若要建構這個類別的執行個體,並將其提供為包裝函式,您必須定義一些原則。 如果此型別是當作包裝函式使用,因為要實作兩個介面,基礎 COM 執行個體也應該同時實作兩個介面。 假設您採用此原則,您必須透過在 COM 執行個體上對 Marshal.QueryInterface()
的呼叫來確認此原則。
int hr = Marshal.QueryInterface(ptr, ref IDemoGetType.IID_IDemoGetType, out IntPtr IDemoGetTypeInst);
if (hr != 0)
{
return null;
}
hr = Marshal.QueryInterface(ptr, ref IDemoStoreType.IID_IDemoStoreType, out IntPtr IDemoStoreTypeInst);
if (hr != 0)
{
Marshal.Release(IDemoGetTypeInst);
return null;
}
return new DemoNativeStaticWrapper()
{
IDemoGetTypeInst = IDemoGetTypeInst,
IDemoStoreTypeInst = IDemoStoreTypeInst
};
動態原生物件包裝函式
動態包裝函式更有彈性,因為其提供在執行階段查詢型別的方式,而不是以靜態方式查詢。 為了提供這項支援,您將會利用 IDynamicInterfaceCastable
- 您可以在這裡找到進一步的詳細資料。 觀察 DemoNativeDynamicWrapper
只會實作這個介面。 介面提供的功能是判斷執行階段支援何種型別的機會。 本教學課程的來源會在建立期間執行靜態檢查,但這只是用於程式碼共用,因為檢查可能會延遲到呼叫 DemoNativeDynamicWrapper.IsInterfaceImplemented()
為止。
// See referenced sample for implementation.
internal class DemoNativeDynamicWrapper
: IDynamicInterfaceCastable
{
public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType) =>
throw new NotImplementedException();
public bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented) =>
throw new NotImplementedException();
}
讓我們看看其中一個 DemoNativeDynamicWrapper
將會動態支援的介面。 下列程式碼提供使用預設介面方法功能的 IDemoStoreType
實作。
[DynamicInterfaceCastableImplementation]
unsafe interface IDemoStoreTypeNativeWrapper : IDemoStoreType
{
public static void StoreString(IntPtr inst, int len, string? str);
void IDemoStoreType.StoreString(int len, string? str)
{
var inst = ((DemoNativeDynamicWrapper)this).IDemoStoreTypeInst;
StoreString(inst, len, str);
}
}
在此範例中,有兩個重要事項需要注意:
DynamicInterfaceCastableImplementationAttribute
屬性。 從IDynamicInterfaceCastable
方法傳回的任何型別上都需要這個屬性。 其附加優點是讓 IL 修剪更容易,這表示 AOT 案例更可靠。- 轉換成
DemoNativeDynamicWrapper
。 這是IDynamicInterfaceCastable
動態本質的一部分。 從IDynamicInterfaceCastable.GetInterfaceImplementation()
傳回的型別會用於「隱藏」實作IDynamicInterfaceCastable
的型別。 此處的 Gist 是this
指標,而不是其假裝的指標,因為我們允許從DemoNativeDynamicWrapper
到IDemoStoreTypeNativeWrapper
的案例。
將呼叫轉送至 COM 執行個體
不論使用哪一個原生物件包裝函式,您都需要能夠在 COM 執行個體上叫用函式。 IDemoStoreTypeNativeWrapper.StoreString()
的實作可作為採用 unmanaged
C# 函式指標的範例。
public static void StoreString(IntPtr inst, int len, string? str)
{
IntPtr strLocal = Marshal.StringToCoTaskMemUni(str);
int hr = ((delegate* unmanaged<IntPtr, int, IntPtr, int>)(*(*(void***)inst + 3 /* IDemoStoreType.StoreString slot */)))(inst, len, strLocal);
if (hr != 0)
{
Marshal.FreeCoTaskMem(strLocal);
Marshal.ThrowExceptionForHR(hr);
}
}
讓我們檢查 COM 執行個體的取值,以存取其 vtable 實作。 COM ABI 會定義物件的第一個指標是型別的 vtable,而且可從該處存取所需的位置。 假設 COM 物件的位址為 0x10000
。 第一個指標大小值應該是 vtable 的位址 - 在此範例 0x20000
中。 當您位於 vtable 時,您會尋找第四個位置 (以零起始編製索引中的索引 3),以存取 StoreString()
實作。
COM instance
0x10000 0x20000
VTable for IDemoStoreType
0x20000 <Address of QueryInterface>
0x20008 <Address of AddRef>
0x20010 <Address of Release>
0x20018 <Address of StoreString>
接著,擁有函式指標可讓您藉由將物件執行個體當作第一個參數傳遞,分派到該物件上的該成員函式。 根據受控物件包裝函式實作的函式定義,此模式看起來應該相當熟悉。
實作 CreateObject()
方法之後,ComWrappers
子類別就能夠針對同時實作 IDemoGetType
和 IDemoStoreType
的 COM 執行個體產生原生物件包裝函式。
IntPtr iunk = ...; // Get a COM instance from native code.
object rcw = cw.GetOrCreateObjectForComInstance(iunk, CreateObjectFlags.UniqueInstance);
步驟 4 - 處理原生物件包裝函式存留期詳細資料
ComputeVtables()
和 CreateObject()
實作涵蓋一些包裝函式存留期詳細資料,但有進一步的考量。 雖然這可以是簡短步驟,但也可以大幅增加 ComWrappers
設計的複雜度。
不同於受控物件包裝函式,這是由呼叫其 AddRef()
和 Release()
方法所控制,而原生物件包裝函式的存留期是由 GC 非確定性地處理。 這裡的問題在於,原生物件包裝函式何時在代表 COM 執行個體的 IntPtr
上呼叫 Release()
? 有兩個一般貯體:
原生物件包裝函式的完成項負責呼叫 COM 執行個體的
Release()
方法。 這是安全呼叫此方法的唯一時間。 此時,GC 已正確判斷 .NET 執行階段中沒有原生物件包裝函式的其他參考。 如果您已正確支援 COM Apartment,則這裡可能會有複雜度;如需詳細資訊,請參閱其他考量一節。原生物件包裝函式會在
Dispose()
中實作IDisposable
和呼叫Release()
。
注意
只有在 CreateObject()
呼叫期間傳入 CreateObjectFlags.UniqueInstance
旗標時,IDisposable
才應該支援模式。 如果未遵循此需求,則已處置的原生物件包裝函式可以在處置之後重複使用。
使用 ComWrappers
子類別
您現在有可測試的 ComWrappers
子類別。 若要避免建立原生程式庫,其會傳回實作 IDemoGetType
和 IDemoStoreType
的 COM 執行個體,您要使用受控物件包裝函式並將其視為 COM 執行個體,這必須可行,才能傳遞 COM。
讓我們先建立受控物件包裝函式。 具現化 DemoImpl
執行個體並顯示其目前的字串狀態。
var demo = new DemoImpl();
string? value = demo.GetString();
Console.WriteLine($"Initial string: {value ?? "<null>"}");
現在您可以建立 DemoComWrappers
的執行個體和受控物件包裝函式,然後您可以傳遞至 COM 環境。
var cw = new DemoComWrappers();
IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);
不要將受控物件包裝函式傳遞至 COM 環境,而是假設您剛收到此 COM 執行個體,因此您將改為為其建立原生物件包裝函式。
var rcw = cw.GetOrCreateObjectForComInstance(ccw, CreateObjectFlags.UniqueInstance);
使用原生物件包裝函式時,您應該能夠將其轉換成其中一個所需的介面,並將其當作一般受控物件使用。 您可以檢查 DemoImpl
執行個體,並觀察原生物件包裝函式上作業的影響,該包裝函式會包裝受控物件包裝函式,後者接著包裝受控執行個體。
var getter = (IDemoGetType)rcw;
var store = (IDemoStoreType)rcw;
string msg = "hello world!";
store.StoreString(msg.Length, msg);
Console.WriteLine($"Setting string through wrapper: {msg}");
value = demo.GetString();
Console.WriteLine($"Get string through managed object: {value}");
msg = msg.ToUpper();
demo.StoreString(msg.Length, msg.ToUpper());
Console.WriteLine($"Setting string through managed object: {msg}");
value = getter.GetString();
Console.WriteLine($"Get string through wrapper: {value}");
因為您的 ComWrapper
子類別是設計來支援 CreateObjectFlags.UniqueInstance
,所以您可以立即清除原生物件包裝函式,而不是等待 GC 發生。
(rcw as IDisposable)?.Dispose();
使用 ComWrappers
啟用 COM
建立 COM 物件通常是透過 COM 啟用來執行 - 本文件範圍以外的複雜案例。 為了提供要遵循的概念模式,我們會介紹用於 COM 啟用的 CoCreateInstance()
API,並說明其如何搭配 ComWrappers
使用。
假設您的應用程式中有下列 C# 程式碼。 下列範例會使用 CoCreateInstance()
來啟動 COM 類別和內建 COM Interop 系統,將 COM 執行個體封送至適當的介面。 請注意,typeof(I).GUID
的使用僅限於判斷提示,而且是使用反映的情況,如果程式碼為 AOT 友善,可能會有影響。
public static I ActivateClass<I>(Guid clsid, Guid iid)
{
Debug.Assert(iid == typeof(I).GUID);
int hr = CoCreateInstance(ref clsid, IntPtr.Zero, /*CLSCTX_INPROC_SERVER*/ 1, ref iid, out object obj);
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
return (I)obj;
}
[DllImport("Ole32")]
private static extern int CoCreateInstance(
ref Guid rclsid,
IntPtr pUnkOuter,
int dwClsContext,
ref Guid riid,
[MarshalAs(UnmanagedType.Interface)] out object ppObj);
將上述項目轉換為使用 ComWrappers
,牽涉到從 CoCreateInstance()
P/Invoke 移除 MarshalAs(UnmanagedType.Interface)
,並手動執行封送。
static ComWrappers s_ComWrappers = ...;
public static I ActivateClass<I>(Guid clsid, Guid iid)
{
Debug.Assert(iid == typeof(I).GUID);
int hr = CoCreateInstance(ref clsid, IntPtr.Zero, /*CLSCTX_INPROC_SERVER*/ 1, ref iid, out IntPtr obj);
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
return (I)s_ComWrappers.GetOrCreateObjectForComInstance(obj, CreateObjectFlags.None);
}
[DllImport("Ole32")]
private static extern int CoCreateInstance(
ref Guid rclsid,
IntPtr pUnkOuter,
int dwClsContext,
ref Guid riid,
out IntPtr ppObj);
您也可以將處理站樣式函式抽象化,例如 ActivateClass<I>
,方法是在原生物件包裝函式的類別建構函式中包含啟用邏輯。 建構函式可以使用 ComWrappers.GetOrRegisterObjectForComInstance()
API,將新建構的受控物件與啟動的 COM 執行個體產生關聯。
其他考量
原生 AOT - 預先 (AOT) 編譯可提供改善的啟動成本,因為已避免 JIT 編譯。 在某些平台上,通常也需要移除 JIT 編譯的需求。 支援 AOT 是 ComWrappers
API 的目標,但任何包裝函式實作都必須小心不要意外導入 AOT 細分的情況,例如使用反映。 Type.GUID
屬性是使用反映的範例,但以不明顯的方式使用。 Type.GUID
屬性會使用反映來檢查型別的屬性,然後可能會檢查型別的名稱,並包含組件以產生其值。
來源產生 - 大部分 COM Interop 和 ComWrappers
實作所需的程式碼,可以由某些工具自動產生。 這兩種包裝函式型別的來源都可以產生適當的 COM 定義,例如型別程式庫 (TLB)、IDL 或主要 Interop 組件 (PIA)。
全域註冊 - 由於 ComWrappers
API 是設計為 COM Interop 的新階段,因此需要有一些方法可以部分與現有系統整合。 對 ComWrappers
API 有全域影響靜態方法,允許針對各種支援註冊全域執行個體。 這些方法是針對預期在所有情況下提供完整 COM Interop 支援的 ComWrappers
執行個體所設計,類似於內建 COM Interop 系統。
參考追蹤器支援 - 此支援主要用於 WinRT 案例,並代表進階案例。 對於大部分 ComWrapper
實作,CreateComInterfaceFlags.TrackerSupport
或 CreateObjectFlags.TrackerObject
旗標應該擲回 NotSupportedException。 如果您想要啟用此支援,可能是在 Windows 或甚至非 Windows 平台上,強烈建議您參考 C#/WinRT 工具鏈。
除了先前討論的存留期、型別系統和功能之外,ComWrappers
的 COM 相容實作還需要額外的考量。 對於將在 Windows 平台上使用的任何實作,有下列考量:
Apartments - COM 執行緒的組織結構稱為 "Apartments",且具有必須遵循的嚴格規則,才能進行穩定作業。 本教學課程不會實作 Apartment 感知原生物件包裝函式,但任何生產就緒實作都應該是 Apartment 感知。 若要達成此目的,建議您使用 Windows 8 中引進的
RoGetAgileReference
API。 對於 Windows 8 之前的版本,請考慮全域介面資料表。安全性 - COM 為類別啟用和代理權限提供豐富的安全性模型。