分享方式:


教學課程:使用 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() 方法,請決定您想要支援的受控型別。 在本教學課程中,我們將會支援先前定義的兩個介面 (IDemoGetTypeIDemoStoreType),以及實作兩個介面的受控型別 (DemoImpl)。

class DemoImpl : IDemoGetType, IDemoStoreType
{
    string? _string;
    public string? GetString() => _string;
    public void StoreString(int _, string? str) => _string = str;
}

針對 CreateObject() 方法,您也必須決定您想要支援的內容。 不過,在此情況下,我們只知道我們感興趣的 COM 介面,不知道 COM 類別。 從 COM 端取用的介面,與我們從 .NET 端投影的介面 (也就是 IDemoGetTypeIDemoStoreType) 相同。

我們不會在此教學課程中實作 ReleaseObjects()

步驟 2 - 實作 ComputeVtables()

讓我們從受控物件包裝函式開始,這些包裝函式會比較容易。 您將會為每個介面建置虛擬方法資料表vtable,以便將其投影到 COM 環境。 在本教學課程中,您會將 vtable 定義為一連串的指標,其中每個指標都代表介面上函式的實作 - 順序在這裡非常重要。 在 COM 中,每個介面都會繼承自 IUnknownIUnknown 型別具有以下列順序定義的三個方法:QueryInterface()AddRef()Release()。 在 IUnknown 方法到達特定的介面方法之後。 例如,請考量 IDemoGetTypeIDemoStoreType。 概念上而言,型別的 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 用法。 在本教學課程中,您只會支援同時實作 IDemoGetTypeIDemoStoreType 的 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);
    }
}

在此範例中,有兩個重要事項需要注意:

  1. DynamicInterfaceCastableImplementationAttribute 屬性。 從 IDynamicInterfaceCastable 方法傳回的任何型別上都需要這個屬性。 其附加優點是讓 IL 修剪更容易,這表示 AOT 案例更可靠。
  2. 轉換成 DemoNativeDynamicWrapper。 這是 IDynamicInterfaceCastable 動態本質的一部分。 從 IDynamicInterfaceCastable.GetInterfaceImplementation() 傳回的型別會用於「隱藏」實作 IDynamicInterfaceCastable 的型別。 此處的 Gist 是 this 指標,而不是其假裝的指標,因為我們允許從 DemoNativeDynamicWrapperIDemoStoreTypeNativeWrapper 的案例。

將呼叫轉送至 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 子類別就能夠針對同時實作 IDemoGetTypeIDemoStoreType 的 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()? 有兩個一般貯體:

  1. 原生物件包裝函式的完成項負責呼叫 COM 執行個體的 Release() 方法。 這是安全呼叫此方法的唯一時間。 此時,GC 已正確判斷 .NET 執行階段中沒有原生物件包裝函式的其他參考。 如果您已正確支援 COM Apartment,則這裡可能會有複雜度;如需詳細資訊,請參閱其他考量一節。

  2. 原生物件包裝函式會在 Dispose() 中實作 IDisposable 和呼叫 Release()

注意

只有在 CreateObject() 呼叫期間傳入 CreateObjectFlags.UniqueInstance 旗標時,IDisposable 才應該支援模式。 如果未遵循此需求,則已處置的原生物件包裝函式可以在處置之後重複使用。

使用 ComWrappers 子類別

您現在有可測試的 ComWrappers 子類別。 若要避免建立原生程式庫,其會傳回實作 IDemoGetTypeIDemoStoreType 的 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.TrackerSupportCreateObjectFlags.TrackerObject 旗標應該擲回 NotSupportedException。 如果您想要啟用此支援,可能是在 Windows 或甚至非 Windows 平台上,強烈建議您參考 C#/WinRT 工具鏈

除了先前討論的存留期、型別系統和功能之外,ComWrappers 的 COM 相容實作還需要額外的考量。 對於將在 Windows 平台上使用的任何實作,有下列考量:

  • Apartments - COM 執行緒的組織結構稱為 "Apartments",且具有必須遵循的嚴格規則,才能進行穩定作業。 本教學課程不會實作 Apartment 感知原生物件包裝函式,但任何生產就緒實作都應該是 Apartment 感知。 若要達成此目的,建議您使用 Windows 8 中引進的 RoGetAgileReference API。 對於 Windows 8 之前的版本,請考慮全域介面資料表

  • 安全性 - COM 為類別啟用和代理權限提供豐富的安全性模型。