-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Implement unmanaged-to-managed direction for vtable stub generator #77130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jkoritzinsky
merged 24 commits into
dotnet:main
from
jkoritzinsky:vtable-unmanged-to-managed
Dec 8, 2022
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
ad0172f
Hook up initial infrastructure for unmanaged-to-managed vtable stubs.
jkoritzinsky f571cd0
Make the signatures for the UnmanagedCallersOnly wrapper have the cor…
jkoritzinsky c2f2fdf
Fix signature and add runtime tests for unmanaged->managed
jkoritzinsky 32f856c
Update incremental generation liveness test to be a theory and add an…
jkoritzinsky 9a38bee
Change IMarshallingGenerator.AsNativeType to return a ManagedTypeInfo…
jkoritzinsky 71fa346
Use MarshalDirection to provide a nice abstraction for determining wh…
jkoritzinsky 8646adf
Implement unmanaged->managed exception handling spec and get compilat…
jkoritzinsky baf8a36
Initial PR feedback.
jkoritzinsky 6bf9f2c
Merge branch 'main' of github.com:dotnet/runtime into vtable-unmanged…
jkoritzinsky 6819628
Fix function pointer signature
jkoritzinsky 972fd18
Emit a PopulateUnmanagedVirtualMethodTable wrapper function to fill t…
jkoritzinsky ddaa876
Add methods for providing virtual method table length.
jkoritzinsky ec57196
Merge branch 'main' of github.com:dotnet/runtime into vtable-unmanged…
jkoritzinsky 7f05abb
PR feedback
jkoritzinsky 4132fd9
Fix bad merge that lost the native-to-managed-this marshaller factory.
jkoritzinsky 96ac635
Enhance docs for the error case
jkoritzinsky 76ce3f5
Internalize the "fallback-to-fowarder" logic to BoundGenerators and h…
jkoritzinsky 8d89495
Pass in the fallback generator to BoundGenerators
jkoritzinsky 7c05d70
Change the BoundGenerators constructor to a factory method.
jkoritzinsky d4e71c4
Add lots of comments and docs
jkoritzinsky 0a45772
Fix a few missing init properties
jkoritzinsky 9879853
PR feedback and finish wiring up the exception marshallers to actuall…
jkoritzinsky b7d1ab3
Add diagnostics for ExceptionMarshalling and other misc PR feedback.
jkoritzinsky 8e9e5ca
Allow setting ExceptionMarshallingCustomType without setting Exceptio…
jkoritzinsky File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
59 changes: 59 additions & 0 deletions
59
docs/design/libraries/ComInterfaceGenerator/UnmanagedToManagedEH.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # Unmanaged to Managed Exception Handling | ||
|
|
||
| As part of building out the "vtable method stub" generator as the first steps on our path to the COM source generator, we need to determine how to handle exceptions at the managed code boundary in the unmanaged-calling-managed direction. When implementing a COM Callable Wrapper (or equivalent concept), the source generator will need to generate an `[UnmanagedCallersOnly]` wrapper of a method. We do not support propagating exceptions across an `[UnmanagedCallersOnly]` method on all platforms, so we need to determine how to handle exceptions. | ||
|
|
||
| We determined that there were three options: | ||
|
|
||
| 1. Do nothing | ||
| 2. Match the existing behavior in the runtime for `[PreserveSig]` on a method on a `[ComImport]` interface. | ||
| 3. Allow the user to provide their own "exception -> return value" translation. | ||
|
|
||
| We chose option 3 for a few reasons: | ||
|
|
||
| 1. Ecosystems like Swift don't use COM HResults as result types, so they may want different behavior for simple types like `int`. | ||
| 2. Not providing a customization option for exception handling would prevent dropping a single method down from the COM generator to the VTable generator to replicate the old `[PreserveSig]` behavior, and there's a desire to not support the `[PreserveSig]` attribute as it exists today. | ||
|
|
||
| As a result, we decided to provide their own rules while providing an easy opt-in to the COM-style rules. | ||
|
|
||
| ## Option 1: Do Nothing | ||
|
|
||
| This option seems the simplest option. The source generator will not emit a `try-catch` block and will just let the exception propogate when in a wrapper for a `VirtualMethodTableIndex` stub. There's a big problem with this path though. One goal of our COM source generator design is to let developers drop down a particular method from the "HRESULT-swapped, all nice COM defaults" mechanism to a `VirtualMethodTableIndex`-attributed method for particular methods when they do not match the expected default behaviors. This feature becomes untenable if we do not wrap the stub in a `try-catch`, as suddenly there is no mechanism to emulate the exception handling of exceptions thrown by marshallers. For cases where all marshallers are known to not throw exceptions except in cases where the process is unrecoverable (for example, `OutOfMemoryException`), this version may provide a slight performance improvement as there will be no exception handling in the generated stub. | ||
|
|
||
| ## Option 2: Match the existing behavior in the runtime for `[PreserveSig]` on a method on a `[ComImport]` interface | ||
|
|
||
| The runtime has some existing built-in behavior for generating a return value from an `Exception` in an unmanaged-to-managed `COM` stub. The behavior depends on the return type, and matches the following table: | ||
|
|
||
| | Type | Return Value in Exceptional Scenario | | ||
| |-----------------|--------------------------------------| | ||
| | `void` | Swallow the exception | | ||
| | `int` | `exception.HResult` | | ||
| | `uint` | `(uint)exception.HResult` | | ||
| | `float` | `float.NaN` | | ||
| | `double` | `double.NaN` | | ||
| | All other types | `default` | | ||
|
|
||
| We could match this behavior for all `VirtualMethodTableIndex`-attributed method stubs, but that would be forcibly encoding the rules of HResults and COM for our return types, and since COM is a Windows-centric technology, we don't want to lock users into this model in case they have different error code models that they want to integrate with. However, it does provide an exception handling boundary | ||
|
|
||
| ## Option 3: Allow the user to provide their own "exception -> return value" translation | ||
|
|
||
| Another option would be to give the user control of how to handle the exception marshalling, with an option for some nice defaults to replicate the other options with an easy opt-in. We would provide the following enumeration and new members on `VirtualMethodTableIndexAttribute`: | ||
|
|
||
| ```diff | ||
| namespace System.Runtime.InteropServices.Marshalling; | ||
|
|
||
| public class VirtualMethodTableIndexAttribute | ||
| { | ||
| + public ExceptionMarshalling ExceptionMarshalling { get; } | ||
| + public Type? ExceptionMarshallingType { get; } // When set to null or not set, equivalent to option 1 | ||
| } | ||
|
|
||
| + public enum ExceptionMarshalling | ||
| + { | ||
| + Custom, | ||
| + Com, // Match the COM-focused model described in Option 2 | ||
| + } | ||
| ``` | ||
|
|
||
| When the user sets `ExceptionMarshalling = ExceptionMarshalling.Custom`, they must specify a marshaller type in `ExceptionMarshallingType` that unmarshals a `System.Exception` to the same unmanaged return type as the marshaller of the return type in the method's signature. | ||
|
|
||
| To implement the `ExceptionMarshalling.Com` option, we will provide some marshallers to implement the different rules above, and select the correct rule given the unmanaged return type from the return type's marshaller. By basing the decision on the unmanaged type instead of the managed type, this mechanism will be able to kick in correctly for projects that use their own custom `HResult` struct that wraps an `int` or `uint`, as the `HResult` will be marshalled to an `int` or `uint` if it is well-defined. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.