共用方式為


ComWrappers 的來源產生

.NET 8 引進來源產生器,為您建立 ComWrappers API 的實作。 產生器可辨識 GeneratedComInterfaceAttribute

.NET 執行階段的內建 (非來源產生的)、僅限 Windows 的 COM Interop 系統會產生 IL 虛設常式,也就是在執行階段成為 JIT 的 IL 指令資料流,以利從受控程式碼轉換至 COM,反之亦然。 由於此 IL 虛設常式是在執行階段產生,所以它與 NativeAOTIL 修剪不相容。 執行階段的虛設常式產生也可以讓診斷封送處理問題變得困難。

內建 Interop 會使用 ComImportDllImport 等屬性,這些屬性依賴於執行階段的程式碼產生。 下列程式碼將示範這項作業:

[ComImport]
interface IFoo
{
    void Method(int i);
}

[DllImport("MyComObjectProvider")]
static nint GetPointerToComInterface(); // C definition - IUnknown* GetPointerToComInterface();

[DllImport("MyComObjectProvider")]
static void GivePointerToComInterface(nint comObject); // C definition - void GivePointerToComInterface(IUnknown* pUnk);

// Use the system to create a Runtime Callable Wrapper to use in managed code
nint ptr = GetPointerToComInterface();
IFoo foo = (IFoo)Marshal.GetObjectForIUnknown(ptr);
foo.Method(0);
...
// Use the system to create a COM Callable Wrapper to pass to unmanaged code
IFoo foo = GetManagedIFoo();
nint ptr = Marshal.GetIUnknownForObject(foo);
GivePointerToComInterface(ptr);

ComWrappers API 可讓您在 C# 中與 COM 互動,而不使用內建 COM 系統,但需要大量的重複使用和手動撰寫的不安全的程式碼。 COM 介面產生器會將此程序自動化,並讓 ComWrappers 像內建 COM 一樣簡單,但會以可修剪且易於使用 AOT 的方式提供它。

基本使用方式

若要使用 COM 介面產生器,請在您要從 COM 匯入或公開的介面定義上新增 GeneratedComInterfaceAttributeGuidAttribute 屬性。 型別必須標示為 partial 且具有 internalpublic 可見度,產生的程式碼才能存取它。

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
internal partial interface IFoo
{
    void Method(int i);
}

然後,若要將實作介面的類別公開至 COM,請將 GeneratedComClassAttribute 新增至實作類別。 這個類別也必須是 partialinternalpublic

[GeneratedComClass]
internal partial class Foo : IFoo
{
    public void Method(int i)
    {
        // Do things
    }
}

在編譯時間,產生器會建立 ComWrappers API 的實作,且您可以使用 StrategyBasedComWrappers 型別或自訂衍生型別來取用或公開 COM 介面。

[LibraryImport("MyComObjectProvider")]
private static partial nint GetPointerToComInterface(); // C definition - IUnknown* GetPointerToComInterface();

[LibraryImport("MyComObjectProvider")]
private static partial void GivePointerToComInterface(nint comObject); // C definition - void GivePointerToComInterface(IUnknown* pUnk);

// Use the ComWrappers API to create a Runtime Callable Wrapper to use in managed code
ComWrappers cw = new StrategyBasedComWrappers();
nint ptr = GetPointerToComInterface();
IFoo foo = (IFoo)cw.GetOrCreateObjectForComInstance(ptr, CreateObjectFlags.None);
foo.Method(0);
...
// Use the system to create a COM Callable Wrapper to pass to unmanaged code
ComWrappers cw = new StrategyBasedComWrappers();
Foo foo = new();
nint ptr = cw.GetOrCreateComInterfaceForObject(foo, CreateComInterfaceFlags.None);
GivePointerToComInterface(ptr);

自訂封送處理

COM 介面產生器會遵守 MarshalUsingAttribute 屬性和 MarshalAsAttribute 屬性的一些用法,以自訂參數的封送處理。 如需詳細資訊,請參閱如何使用 MarshalUsing 屬性自訂來源產生的封送處理,以及使用 MarshalAs 屬性自訂參數封送處理。 如果 GeneratedComInterfaceAttribute.StringMarshallingGeneratedComInterfaceAttribute.StringMarshallingCustomType 屬性沒有其他封送處理屬性,則會套用至介面中的所有參數和傳回型別 string

隱含 HRESULT 和 PreserveSig

C# 中的 COM 方法具有與原生方法不同的簽章。 標準 COM 的傳回型別為 HRESULT,這是代表錯誤和成功狀態的 4 個位元組整數型別。 這個 HRESULT 傳回值預設會在 C# 簽章中隱藏,並在傳回錯誤值時轉換成例外狀況。 原生 COM 簽章的最後一個「out」參數可以選擇性地轉換成 C# 簽章中的傳回。

例如,下列程式碼片段會顯示 C# 方法簽章,以及產生器推斷的對應原生簽章。

void Method1(int i);

int Method2(float i);
HRESULT Method1(int i);

HRESULT Method2(float i, _Out_ int* returnValue);

如果您想要自行處理 HRESULT,則可以使用方法上的 PreserveSigAttribute 來指出產生器不應該執行此轉換。 下列程式碼片段示範產生器在套用 [PreserveSig] 時所預期的原生簽章。 COM 方法必須傳回 HRESULT,因此使用 PreserveSig 之任何方法的傳回值應該是 int

[PreserveSig]
int Method1(int i, out int j);

[PreserveSig]
int Method2(float i);
HRESULT Method1(int i, int* j);

HRESULT Method2(float i);

如需詳細資訊,請參閱 .NET Interop 中的隱含方法簽章轉譯

內建 COM 的不相容和差異

僅限 IUnknown

唯一支援的介面基底是 IUnknown。 來源產生的 COM 不支援具有 InterfaceIsIUnknown 以外 InterfaceTypeAttribute 值的介面。 任何沒有 InterfaceTypeAttribute 的介面都會假設衍生自 IUnknown。 這與預設為 InterfaceIsDual 的內建 COM 不同。

封送處理預設值和支援

來源產生的 COM 有一些不同於內建 COM 的預設封送處理行為。

  • 在內建 COM 系統中,除了具有隱含 [In, Out] 屬性的 blittable 元素陣列之外,所有型別都有隱含 [In] 屬性。 在來源產生的 COM 中,所有型別,包括 blittable 元素的陣列,都具有 [In] 語意。

  • [In][Out] 屬性只能在陣列上使用。 如果其他型別需要 [Out][In, Out] 行為,請使用 inout 參數修飾元。

衍生介面

在內建 COM 系統中,如果您有衍生自其他 COM 介面的介面,則必須使用 new 關鍵字,針對基底介面上的每個基底方法宣告遮蔽方法。 如需詳細資訊,請參閱 COM 介面繼承和 .NET

[ComImport]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IBase
{
    void Method1(int i);
    void Method2(float i);
}

[ComImport]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IDerived : IBase
{
    new void Method1(int i);
    new void Method2(float f);
    void Method3(long l);
    void Method4(double d);
}

COM 介面產生器不需要任何基底方法的遮蔽。 若要建立繼承自另一個的方法,只要將基底介面指示為 C# 基底介面,並新增衍生介面的方法。 如需詳細資訊,請參閱設計文件

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IBase
{
    void Method1(int i);
    void Method2(float i);
}

[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
interface IDerived : IBase
{
    void Method3(long l);
    void Method4(double d);
}

請注意,具有 GeneratedComInterface 屬性的介面只能繼承自具有 GeneratedComInterface 屬性的基底介面。

跨元件界限的衍生介面

在 .NET 8 中,不支援使用衍生自另一個GeneratedComInterface元件中定義之 -attributed 介面的屬性來定義介面GeneratedComInterfaceAttribute

在 .NET 9 和更新版本中,此案例受到下列限制的支援:

  • 基底介面類型必須以與衍生型別相同的目標架構為目標進行編譯。
  • 如果基底介面類型有基底介面,則基底介面類型不得遮蔽其任何成員。

此外,在重新建置專案之前,在另一個元件中定義的基底介面鏈結中,任何產生的虛擬方法位移所做的任何變更都不會計入衍生介面中。

注意

在 .NET 9 和更新版本中,在跨元件界限繼承產生的 COM 介面時,會發出警告,以通知您使用這項功能的限制和陷阱。 您可以停用此警告來確認限制,並跨元件界限繼承。

封送處理 API

Marshal 中的某些 API 與來源產生的 COM 不相容。 將這些方法取代為其 ComWrappers 實作上的對應方法。

另請參閱