|
| 1 | +--- |
| 2 | +title: ComWrappers source generation |
| 3 | +description: Learn about compile-time source generation for ComWrappers in .NET. |
| 4 | +ms.date: 08/25/2023 |
| 5 | +--- |
| 6 | + |
| 7 | +# Source generation for ComWrappers |
| 8 | + |
| 9 | +.NET 8 introduces a source generator that creates an implementation of the [ComWrappers](./com-wrappers.md) API for you. The generator recognizes the <xref:System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute>. |
| 10 | + |
| 11 | +The .NET runtime's built-in (not source-generated), Windows-only, COM interop system generates an IL stub—a stream of IL instructions that's JIT-ed—at run time to facilitate the transition from managed code to COM, and vice-versa. Since this IL stub is generated at run time, it's incompatible with [NativeAOT](../../core/deploying/native-aot/index.md) and [IL trimming](../../core/deploying/trimming/trim-self-contained.md). Stub generation at run time can also make diagnosing marshalling issues difficult. |
| 12 | + |
| 13 | +Built-in interop uses attributes such as `ComImport` or `DllImport`, which rely on code generation at run time. The following code shows an example of this: |
| 14 | + |
| 15 | +```csharp |
| 16 | +[ComImport] |
| 17 | +interface IFoo |
| 18 | +{ |
| 19 | + void Method(int i); |
| 20 | +} |
| 21 | + |
| 22 | +[DllImport("MyComObjectProvider.dll")] |
| 23 | +static nint GetPointerToComInterface(); |
| 24 | + |
| 25 | +[DllImport("MyComObjectProvider.dll")] |
| 26 | +static void GivePointerToComInterface(nint comObject); |
| 27 | + |
| 28 | +// Use the system to create a Runtime Callable Wrapper to use in managed code |
| 29 | +nint ptr = GetPointerToComInterface(); |
| 30 | +IFoo foo = (IFoo)Marshal.GetObjectForIUnknown(ptr); |
| 31 | +foo.Method(0); |
| 32 | +... |
| 33 | +// Use the system to create a COM Callable Wrapper to pass to unmanaged code |
| 34 | +IFoo foo = GetManagedIFoo(); |
| 35 | +nint ptr = Marshal.GetIUnknownForObject(foo); |
| 36 | +GivePointerToComInterface(ptr); |
| 37 | +``` |
| 38 | + |
| 39 | +The `ComWrappers` API enables interacting with COM in C# without using the built-in COM system, but [requires substantial boilerplate and hand-written unsafe code](./tutorial-comwrappers.md). The COM interface generator automates this process and makes `ComWrappers` as easy as built-in COM, but delivers it in a trimmable and AOT-friendly manner. |
| 40 | + |
| 41 | +## Basic usage |
| 42 | + |
| 43 | +To use the COM interface generator, add the <xref:System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute> and <xref:System.Runtime.InteropServices.GuidAttribute> attributes on the interface definition that you want to import from or expose to COM. The type must be marked `partial` and have `internal` or `public` visibility for the generated code to be able to access it. |
| 44 | + |
| 45 | +```csharp |
| 46 | +[GeneratedComInterface] |
| 47 | +[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")] |
| 48 | +internal partial IFoo |
| 49 | +{ |
| 50 | + void Method(int i); |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +Then, to expose a class that implements an interface to COM, add the <xref:System.Runtime.InteropServices.Marshalling.GeneratedComClassAttribute> to the implementing class. This class must also be `partial` and either `internal` or `public`. |
| 55 | + |
| 56 | +```csharp |
| 57 | +[GeneratedComClass] |
| 58 | +internal partial class Foo : IFoo |
| 59 | +{ |
| 60 | + public void Method(int i) |
| 61 | + { |
| 62 | + // Do things |
| 63 | + } |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +At compile time, the generator creates an implementation of the ComWrappers API, and you can use the <xref:System.Runtime.InteropServices.Marshalling.StrategyBasedComWrappers> type or a custom derived type to consume or expose the COM interface. |
| 68 | + |
| 69 | +```csharp |
| 70 | +[LibraryImport("MyComObjectProvider.dll")] |
| 71 | +static nint GetPointerToComInterface(); |
| 72 | + |
| 73 | +[LibraryImport("MyComObjectProvider.dll")] |
| 74 | +static void GivePointerToComInterface(nint comObject); |
| 75 | + |
| 76 | +// Use the ComWrappers API to create a Runtime Callable Wrapper to use in managed code |
| 77 | +ComWrappers cw = new StrategyBasedComWrappers(); |
| 78 | +nint ptr = GetPointerToComInterface(); |
| 79 | +IFoo foo = (IFoo)cw.GetOrCreateObjectForComInterface(ptr); |
| 80 | +foo.Method(0); |
| 81 | +... |
| 82 | +// Use the system to create a COM Callable Wrapper to pass to unmanaged code |
| 83 | +ComWrappers cw = new StrategyBasedComWrappers(); |
| 84 | +Foo foo = new(); |
| 85 | +nint ptr = cw.GetOrCreateComInterfaceForObject(foo); |
| 86 | +GivePointerToComInterface(ptr); |
| 87 | +``` |
| 88 | + |
| 89 | +## Customize marshalling |
| 90 | + |
| 91 | +The COM interface generator respects the <xref:System.Runtime.InteropServices.Marshalling.MarshalUsingAttribute> attribute and some usages of the <xref:System.Runtime.InteropServices.MarshalAsAttribute> attribute to customize marshalling of parameters. For more information, see how to [customize source-generated marshalling with the `MarshalUsing` attribute](./custom-marshalling-source-generation.md) and [customize parameter marshalling with the `MarshalAs` attribute](./customize-parameter-marshalling.md). The <xref:System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute.StringMarshalling?displayProperty=nameWithType> and <xref:System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute.StringMarshallingCustomType?displayProperty=nameWithType> properties apply to all parameters and return types of type `string` in the interface if they don't have other marshalling attributes. |
| 92 | + |
| 93 | +## Implicit HRESULTs and PreserveSig |
| 94 | + |
| 95 | +COM methods in C# have a different signature than the native methods. Standard COM has a return type of `HRESULT`, a 4 byte integer type representing error and success states. This `HRESULT` return value is hidden by default in the C# signature and converted to an exception when an error value is returned. The last "out" parameter of the native COM signature may optionally be converted into the return in the C# signature. |
| 96 | + |
| 97 | +For example, the following snippets show C# method signatures and the corresponding native signature the generator infers. |
| 98 | + |
| 99 | +```csharp |
| 100 | +void Method1(int i); |
| 101 | + |
| 102 | +int Method2(float i); |
| 103 | +``` |
| 104 | + |
| 105 | +```c |
| 106 | +HRESULT Method1(int i); |
| 107 | + |
| 108 | +HRESULT Method2(float i, _Out_ int* returnValue); |
| 109 | +``` |
| 110 | +
|
| 111 | +If you want to handle the `HRESULT` yourself, you can use the <xref:System.Runtime.InteropServices.PreserveSigAttribute> on the method to indicate the generator should not do this transformation. The following snippets demonstrate what native signature the generator expects when `[PreserveSig]` is applied. COM methods must return `HRESULT`, so the return value of any method with `PreserveSig` should be `int`. |
| 112 | +
|
| 113 | +```csharp |
| 114 | +[PreserveSig] |
| 115 | +int Method1(int i, out int j); |
| 116 | +
|
| 117 | +[PreserveSig] |
| 118 | +int Method2(float i); |
| 119 | +``` |
| 120 | + |
| 121 | +```c |
| 122 | +HRESULT Method1(int i, int* j); |
| 123 | + |
| 124 | +HRESULT Method2(float i); |
| 125 | +``` |
| 126 | +
|
| 127 | +For more information, see [Implicit method signature translations in .NET interop](./preserve-sig.md) |
| 128 | +
|
| 129 | +## Incompatibilities and differences to built-in COM |
| 130 | +
|
| 131 | +### `IUnknown` only |
| 132 | +
|
| 133 | +The only supported interface base is [`IUnknown`](/windows/win32/api/unknwn/nn-unknwn-iunknown). Interfaces with an <xref:System.Runtime.InteropServices.InterfaceTypeAttribute> that has a value other than <xref:System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown> are not supported in source-generated COM. Any interfaces without an `InterfaceTypeAttribute` are assumed to derive from `IUnknown`. This differs from built-in COM where the default is <xref:System.Runtime.InteropServices.ComInterfaceType.InterfaceIsDual>. |
| 134 | +
|
| 135 | +## Marshalling defaults and support |
| 136 | +
|
| 137 | +Source-generated COM has some different default marshalling behaviors from built-in COM. |
| 138 | +
|
| 139 | +- In the built-in COM system, all types have an implicit `[In]` attribute except for arrays of blittable elements, which have implicit `[In, Out]` attributes. In source-generated COM, all types, including arrays of blittable elements, have `[In]` semantics. |
| 140 | +
|
| 141 | +- `[In]` and `[Out]` attributes are only allowed on arrays. If `[Out]` or `[In, Out]` behavior is required on other types, use the `in` and `out` parameter modifiers. |
| 142 | +
|
| 143 | +### Derived interfaces |
| 144 | +
|
| 145 | +In the built-in COM system, if you have interfaces that derive from other COM interfaces, you must declare a shadowing method for each base method on the base interfaces with the `new` keyword. For more information, see [COM interface inheritance and .NET](./qualify-net-types-for-interoperation.md#com-interface-inheritance-and-net). |
| 146 | +
|
| 147 | +```csharp |
| 148 | +[ComImport] |
| 149 | +[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")] |
| 150 | +interface IBase |
| 151 | +{ |
| 152 | + void Method1(int i); |
| 153 | + void Method2(float i); |
| 154 | +} |
| 155 | +
|
| 156 | +[ComImport] |
| 157 | +[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")] |
| 158 | +interface IDerived : IBase |
| 159 | +{ |
| 160 | + new void Method1(int i); |
| 161 | + new void Method2(float f); |
| 162 | + void Method3(long l); |
| 163 | + void Method4(double d); |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +The COM interface generator does not expect any shadowing of base methods. To create a method that inherits from another, simply indicate the base interface as a C# base interface and add the derived interface's methods. For more information, see [the design doc](https://github.com/dotnet/runtime/blob/main/docs/design/libraries/ComInterfaceGenerator/DerivedComInterfaces.md). |
| 168 | + |
| 169 | +```csharp |
| 170 | +[GeneratedComInterface] |
| 171 | +[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")] |
| 172 | +interface IBase |
| 173 | +{ |
| 174 | + void Method1(int i); |
| 175 | + void Method2(float i); |
| 176 | +} |
| 177 | + |
| 178 | +[GeneratedComInterface] |
| 179 | +[Guid("3faca0d2-e7f1-4e9c-82a6-404fd6e0aab8")] |
| 180 | +interface IDerived : IBase |
| 181 | +{ |
| 182 | + void Method3(long l); |
| 183 | + void Method4(double d); |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +Note that an interface with the `GeneratedComInterface` attribute can only inherit from one base interface that has the `GeneratedComInterface` attribute. |
| 188 | + |
| 189 | +### Marshal APIs |
| 190 | + |
| 191 | +Some APIs in <xref:System.Runtime.InteropServices.Marshal> are not compatible with source-generated COM. Replace these methods with their corresponding methods on a `ComWrappers` implementation. |
| 192 | + |
| 193 | +## See also |
| 194 | + |
| 195 | +- [ComWrappers](./com-wrappers.md) |
| 196 | +- [Customizing Parameter Marshalling](./customize-parameter-marshalling.md) |
| 197 | +- [Runtime Callable Wrapper](runtime-callable-wrapper.md) |
| 198 | +- [COM Callable Wrapper](com-callable-wrapper.md) |
0 commit comments