ComWrappers 的來源產生
.NET 8 引進來源產生器,為您建立 ComWrappers API 的實作。 產生器可辨識 GeneratedComInterfaceAttribute。
.NET 執行階段的內建 (非來源產生的)、僅限 Windows 的 COM Interop 系統會產生 IL 虛設常式,也就是在執行階段成為 JIT 的 IL 指令資料流,以利從受控程式碼轉換至 COM,反之亦然。 由於此 IL 虛設常式是在執行階段產生,所以它與 NativeAOT 和 IL 修剪不相容。 執行階段的虛設常式產生也可以讓診斷封送處理問題變得困難。
內建 Interop 會使用 ComImport
或 DllImport
等屬性,這些屬性依賴於執行階段的程式碼產生。 下列程式碼將示範這項作業:
[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 匯入或公開的介面定義上新增 GeneratedComInterfaceAttribute 和 GuidAttribute 屬性。 型別必須標示為 partial
且具有 internal
或 public
可見度,產生的程式碼才能存取它。
[GeneratedComInterface]
[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")]
internal partial interface IFoo
{
void Method(int i);
}
然後,若要將實作介面的類別公開至 COM,請將 GeneratedComClassAttribute 新增至實作類別。 這個類別也必須是 partial
和 internal
或 public
。
[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.StringMarshalling 和 GeneratedComInterfaceAttribute.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]
行為,請使用in
和out
參數修飾元。
衍生介面
在內建 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
實作上的對應方法。