Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
88a62cc
ComGenerator Docs v1
jtschuster Aug 24, 2023
9f4f10f
PR feedback
jtschuster Aug 25, 2023
ab8381b
.Net => .NET
jtschuster Aug 25, 2023
67bf999
Remove tutorial todo and add references to generator docs
jtschuster Aug 30, 2023
9745307
Add attribute info and example of new interface inheritance
jtschuster Sep 20, 2023
83af844
Fix lint error
jtschuster Sep 20, 2023
b36741c
A couple tweaks
jtschuster Sep 21, 2023
ec0b041
Merge branch 'main' of https://github.com/dotnet/docs into ComGenerat…
jtschuster Sep 21, 2023
b324d46
Fix link
jtschuster Sep 21, 2023
29ee5de
Remove newline at top of file
jtschuster Sep 21, 2023
0c30086
LibraryImport has no preserveSig
jtschuster Sep 23, 2023
7ec8e40
Update docs/standard/native-interop/preserve-sig.md
AaronRobinsonMSFT Sep 23, 2023
77172ea
Update docs/standard/native-interop/preserve-sig.md
AaronRobinsonMSFT Sep 23, 2023
49da91b
Apply suggestions from code review
jtschuster Sep 25, 2023
144cb94
Reword translation to indicate the translation is from C# to native
jtschuster Sep 25, 2023
d5307bb
Apply suggestions from code review
jtschuster Sep 25, 2023
05257ac
PR feedback - update marshalling customization docs
jtschuster Sep 25, 2023
535c043
Merge branch 'ComGeneratorDocs' of https://github.com/jtschuster/docs…
jtschuster Sep 25, 2023
dff5739
Add to TOC and fix link
jtschuster Sep 25, 2023
2683981
Reword a few things and replace int with HRESULT in native sig
jtschuster Sep 26, 2023
a99abff
Add section for UnmanagedType.Error
jtschuster Sep 26, 2023
4a37cc9
Update docs/standard/native-interop/preserve-sig.md
jtschuster Sep 27, 2023
b690c08
Update docs/standard/native-interop/preserve-sig.md
jtschuster Sep 27, 2023
ebb4bdb
Update docs/standard/native-interop/preserve-sig.md
jtschuster Sep 27, 2023
8e020d9
Apply suggestions from code review
jtschuster Sep 27, 2023
b62a05d
Apply suggestions from code review
jtschuster Sep 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/navigate/advanced-programming/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -364,10 +364,14 @@ items:
href: ../../standard/native-interop/com-callable-wrapper.md
- name: "Tutorial: Use the ComWrappers API"
href: ../../standard/native-interop/tutorial-comwrappers.md
- name: Source generation
href: ../../standard/native-interop/comwrappers-source-generation.md
- name: Qualify .NET types for COM interop
href: ../../standard/native-interop/qualify-net-types-for-interoperation.md
- name: Apply interop attributes
href: ../../standard/native-interop/apply-interop-attributes.md
- name: HRESULT and PreserveSig
href: ../../standard/native-interop/preserve-sig.md
- name: Exceptions
href: ../../standard/native-interop/exceptions-interoperability.md
- name: Memory management
Expand Down
31 changes: 16 additions & 15 deletions docs/standard/native-interop/com-wrappers.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: "COM Wrappers"
description: COM clients and .NET objects interact by using a COM callable wrapper and a runtime callable wrapper. The CLR creates wrappers automatically.
ms.date: "03/30/2017"
helpviewer_keywords:
helpviewer_keywords:
- "wrapper classes"
- "COM interop, COM wrappers"
- "COM wrappers"
Expand All @@ -13,22 +13,23 @@ ms.assetid: e56c485b-6b67-4345-8e66-fd21835a6092
---
# COM Wrappers

COM differs from the .NET runtime object model in several important ways:
- Clients of COM objects must manage the lifetime of those objects; the common language runtime manages the lifetime of objects in its environment.
- Clients of COM objects discover whether a service is available by requesting an interface that provides that service and getting back an interface pointer, or not. Clients of .NET objects can obtain a description of an object's functionality using reflection.
- NET objects reside in memory managed by the .NET runtime execution environment. The execution environment can move objects around in memory for performance reasons and update all references to the objects it moves. Unmanaged clients, having obtained a pointer to an object, rely on the object to remain at the same location. These clients have no mechanism for dealing with an object whose location is not fixed.
To overcome these differences, the runtime provides wrapper classes to make both managed and unmanaged clients think they are calling objects within their respective environment. Whenever your managed client calls a method on a COM object, the runtime creates a [runtime callable wrapper](runtime-callable-wrapper.md) (RCW). RCWs abstract the differences between managed and unmanaged reference mechanisms, among other things. The runtime also creates a [COM callable wrapper](com-callable-wrapper.md) (CCW) to reverse the process, enabling a COM client to seamlessly call a method on a .NET object. As the following illustration shows, the perspective of the calling code determines which wrapper class the runtime creates.
![COM wrapper overview](./media/com-wrappers/bidirectional-com-overview.gif)
In most cases, the standard RCW or CCW generated by the runtime provides adequate marshalling for calls that cross the boundary between COM and the .NET runtime. Using custom attributes, you can optionally adjust the way the runtime represents managed and unmanaged code.
COM differs from the .NET runtime object model in several important ways:

- Clients of COM objects must manage the lifetime of those objects; the common language runtime manages the lifetime of objects in its environment.

- Clients of COM objects discover whether a service is available by requesting an interface that provides that service and getting back an interface pointer, or not. Clients of .NET objects can obtain a description of an object's functionality using reflection.

- NET objects reside in memory managed by the .NET runtime execution environment. The execution environment can move objects around in memory for performance reasons and update all references to the objects it moves. Unmanaged clients, having obtained a pointer to an object, rely on the object to remain at the same location. These clients have no mechanism for dealing with an object whose location is not fixed.

To overcome these differences, the runtime provides wrapper classes to make both managed and unmanaged clients think they are calling objects within their respective environment. Whenever your managed client calls a method on a COM object, the runtime creates a [runtime callable wrapper](runtime-callable-wrapper.md) (RCW). RCWs abstract the differences between managed and unmanaged reference mechanisms, among other things. The runtime also creates a [COM callable wrapper](com-callable-wrapper.md) (CCW) to reverse the process, enabling a COM client to seamlessly call a method on a .NET object. As the following illustration shows, the perspective of the calling code determines which wrapper class the runtime creates.

![COM wrapper overview](./media/com-wrappers/bidirectional-com-overview.gif)

In most cases, the standard RCW or CCW generated by the runtime provides adequate marshalling for calls that cross the boundary between COM and the .NET runtime. Using custom attributes, you can optionally adjust the way the runtime represents managed and unmanaged code.

## See also

- [ComWrappers Source Generation](./comwrappers-source-generation.md)
- [Advanced COM Interoperability in .NET Framework](/previous-versions/dotnet/netframework-4.0/bd9cdfyx(v=vs.100))
- [Runtime Callable Wrapper](runtime-callable-wrapper.md)
- [COM Callable Wrapper](com-callable-wrapper.md)
Expand Down
4 changes: 4 additions & 0 deletions docs/standard/native-interop/cominterop.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ ms.date: 07/11/2019

The Component Object Model (COM) lets an object expose its functionality to other components and to host applications on Windows platforms. To help enable users to interoperate with their existing code bases, .NET Framework has always provided strong support for interoperating with COM libraries. In .NET Core 3.0, a large portion of this support has been added to .NET Core on Windows. The documentation here explains how the common COM interop technologies work and how you can utilize them to interoperate with your existing COM libraries.

## Built-in and source-generated COM interop

COM interop functionality can be achieved through a built-in system in the .NET runtime (introduced in .NET Core 3.0) or through implementing the [ComWrappers API](./tutorial-comwrappers.md) (introduced in .NET 6). In .NET 8, the [COM source generator](./comwrappers-source-generation.md) can be used to automatically implement the ComWrappers API for `IUnknown`-based interfaces.

- [COM Wrappers](./com-wrappers.md)
- [COM Callable Wrappers](./com-callable-wrapper.md)
- [Runtime Callable Wrappers](./runtime-callable-wrapper.md)
Expand Down
198 changes: 198 additions & 0 deletions docs/standard/native-interop/comwrappers-source-generation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
---
title: ComWrappers source generation
description: Learn about compile-time source generation for ComWrappers in .NET.
ms.date: 08/25/2023
---

# Source Generation for ComWrappers

.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>.

The .NET runtime's built-in (not source-generated), Windows-only, COM interop system generates an IL stub&mdash;a stream of IL instructions that is JIT-ed&mdash;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 isn't compatible with [NativeAOT](../../core/deploying/native-aot/index.md), or [IL trimming](../../core/deploying/trimming/trim-self-contained.md). Stub generation at run-time can also make diagnosing marshalling issues difficult.

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:

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

[DllImport("MyComObjectProvider.dll")]
static nint GetPointerToComInterface();

[DllImport("MyComObjectProvider.dll")]
static void GivePointerToComInterface(nint comObject);

// 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);
```

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.

## Basic usage

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.

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

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 will also need to be `partial` and either `internal` or `public`.

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

At compile time, the generator will create 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.

```csharp
[LibraryImport("MyComObjectProvider.dll")]
static nint GetPointerToComInterface();

[LibraryImport("MyComObjectProvider.dll")]
static void GivePointerToComInterface(nint comObject);

// 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.GetOrCreateObjectForComInterface(ptr);
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);
GivePointerToComInterface(ptr);
```

## Customizing marshalling

The COM interface generator respects the <xref:System.Runtime.InteropServices.Marshalling.MarshalUsingAttribute> and some usages of <xref:System.Runtime.InteropServices.MarshalAsAttribute> attributes 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). If an interface has `string` parameters or return types, the <xref:System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute.StringMarshalling?displayProperty=nameWithType> and <xref:System.Runtime.InteropServices.Marshalling.GeneratedComInterfaceAttribute.StringMarshallingCustomType?displayProperty=nameWithType> properties will apply to all parameters and return types of `string` in the interface if they don't have other marshalling attributes.

## Implicit HRESULTs and PreserveSig

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.

For example, the following snippets show C# method signatures and the corresponding native signature the generator infers.

```csharp
void Method1(int i);

int Method2(float i);
```

```c
HRESULT Method1(int i);

HRESULT Method2(float i, _Out_ int* returnValue);
```
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. Below are snippets that 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`.
```csharp
[PreserveSig]
int Method1(int i, out int j);
[PreserveSig]
int Method2(float i);
```

```c
HRESULT Method1(int i, int* j);

HRESULT Method2(float i);
```
For more information, see [Implicit method signature translations in .NET interop](./preserve-sig.md)
## Incompatibilities and differences to built-in COM
### IUnknown only
The only supported interface base is [IUnknown](/windows/win32/api/unknwn/nn-unknwn-iunknown). Interfaces with an <xref:System.Runtime.InteropServices.InterfaceTypeAttribute> with a value other values 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>.
## Marshalling defaults and support
Source-generated COM has some different default marshalling behaviors from built in COM.
- In the built-in COM system, all types had an implicit `[In]` attribute except for arrays of blittable elements, which had implicit `[In, Out]` attributes. In source-generated COM, all types, including arrays of blittable elements, have `[In]` semantics.
- `[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.
### Derived interfaces
In the built-in COM system, if you had interfaces which derived from other COM interfaces, you would need to declare a shadowing method for each base method on the base interfaces with the `new` keyword. See [COM interface inheritance and .NET](./qualify-net-types-for-interoperation.md#com-interface-inheritance-and-net) for more information.
```csharp
[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);
}
```

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. See [the design doc](https://github.com/dotnet/runtime/blob/main/docs/design/libraries/ComInterfaceGenerator/DerivedComInterfaces.md) for more information.

```csharp
[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);
}
```

Note that an interface with the `GeneratedComInterface` attribute can only inherit from one base interface that has the `GeneratedComInterface` attribute.

### Marshal APIs

Some APIs in <xref:System.Runtime.InteropServices.Marshal> are not compatible with source-generated COM. These methods should be replaced with their corresponding methods on a `ComWrappers` implementation.

## See also

- [ComWrappers](./com-wrappers.md)
- [Customizing Parameter Marshalling](./customize-parameter-marshalling.md)
- [Runtime Callable Wrapper](runtime-callable-wrapper.md)
- [COM Callable Wrapper](com-callable-wrapper.md)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ ms.topic: how-to

When the .NET runtime's default parameter marshalling behavior doesn't do what you want, use can use the <xref:System.Runtime.InteropServices.MarshalAsAttribute?displayProperty=nameWithType> attribute to customize how your parameters are marshalled. These customization features do not apply when [runtime marshalling is disabled](disabled-marshalling.md).

> [!NOTE]
> Source-generated interop for [P/Invokes](./pinvoke-source-generation.md) and [COM](./comwrappers-source-generation.md) only respects a small subset of <xref:System.Runtime.InteropServices.MarshalAsAttribute> on parameters. It is recommended to use <xref:System.Runtime.InteropServices.Marshalling.MarshalUsingAttribute> for source-generated interop instead. See [Custom marshalling for source generation](./custom-marshalling-source-generation.md) for more information.

## Customizing string parameters

.NET has a variety of formats for marshalling strings. These methods are split into distinct sections on C-style strings and Windows-centric string formats.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ ms.topic: how-to

Sometimes the default marshalling rules for structures aren't exactly what you need. The .NET runtimes provide a few extension points for you to customize your structure's layout and how fields are marshalled. Customizing structure layout is supported for all scenarios, but customizing field marshalling is only supported for scenarios where runtime marshalling is enabled. If [runtime marshalling is disabled](disabled-marshalling.md), then any field marshalling must be done manually.

> [!NOTE]
> This article does not cover customizing marshalling for source-generated interop. If using [source-generated interop for P/Invokes](./pinvoke-source-generation.md) or [COM](./comwrappers-source-generation.md), customizing marshalling is covered in [another article](./custom-marshalling-source-generation.md).

## Customizing structure layout

.NET provides the <xref:System.Runtime.InteropServices.StructLayoutAttribute?displayProperty=nameWithType> attribute and the <xref:System.Runtime.InteropServices.LayoutKind?displayProperty=nameWithType> enumeration to allow you to customize how fields are placed in memory. The following guidance will help you avoid common issues.
Expand Down
3 changes: 3 additions & 0 deletions docs/standard/native-interop/pinvoke.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ Both of the previous examples depend on parameters, and in both cases, the param

## More resources

- [Writing cross platform P/Invokes](./native-library-loading.md)
- [Source generated P/Invoke marshalling](./pinvoke-source-generation.md)
- [PInvoke.net wiki](https://www.pinvoke.net/) an excellent Wiki with information on common Windows APIs and how to call them.
- [C#/Win32 P/Invoke source generator](https://github.com/microsoft/CsWin32/) automatically generates definitions for Windows APIs.
- [P/Invoke in C++/CLI](/cpp/dotnet/native-and-dotnet-interoperability)
- [Mono documentation on P/Invoke](https://www.mono-project.com/docs/advanced/pinvoke/)
Loading