From 925d8d510ed24e0136e0096d6eecbba66b2c11b3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 20 Dec 2025 11:20:02 -0800 Subject: [PATCH 1/8] Refactor marshaller type resolution for custom-mapped types Consolidates logic for resolving marshaller types for custom-mapped Windows Runtime structs and classes, removing special cases for TimeSpan and DateTimeOffset. Updates checks to use IsCustomMappedWindowsRuntimeNonGenericStructOrClassType for consistency and maintainability. --- .../Factories/InteropMethodRewriteFactory.cs | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.cs index 39db3a7a5..92f8f376f 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.cs @@ -34,25 +34,15 @@ private static ITypeDefOrRef GetValueTypeMarshallerType( // For primitive types, the marshaller type is in 'WinRT.Runtime.dll'. // In this case we can also rely on all types being under 'System'. - if (type.IsFundamentalWindowsRuntimeType(interopReferences)) + // The same applies to custom-mapped struct types. + if (type.IsFundamentalWindowsRuntimeType(interopReferences) || + type.IsCustomMappedWindowsRuntimeNonGenericStructOrClassType(interopReferences)) { return interopReferences.WindowsRuntimeModule.CreateTypeReference( ns: "ABI.System"u8, name: $"{type.Name}Marshaller"); } - // 'TimeSpan' is custom-mapped and not blittable - if (SignatureComparer.IgnoreVersion.Equals(type, interopReferences.TimeSpan)) - { - return interopReferences.TimeSpanMarshaller; - } - - // 'DateTimeOffset' also is custom-mapped and not blittable - if (SignatureComparer.IgnoreVersion.Equals(type, interopReferences.DateTimeOffset)) - { - return interopReferences.DateTimeOffsetMarshaller; - } - // In all other cases, the marshaller type will be in the declared assembly return type.Resolve()!.DeclaringModule!.CreateTypeReference( ns: $"ABI.{type.Namespace}", @@ -84,10 +74,9 @@ private static ITypeDefOrRef GetReferenceTypeMarshallerType( } // For custom-mapped types, get the marshaller type from 'WinRT.Runtime.dll' - if (type.IsTypeOfType(interopReferences) || - type.IsTypeOfException(interopReferences) || - type.IsCustomMappedWindowsRuntimeNonGenericInterfaceType(interopReferences) || - type.IsCustomMappedWindowsRuntimeNonGenericDelegateType(interopReferences)) + if (type.IsCustomMappedWindowsRuntimeNonGenericInterfaceType(interopReferences) || + type.IsCustomMappedWindowsRuntimeNonGenericDelegateType(interopReferences) || + type.IsCustomMappedWindowsRuntimeNonGenericStructOrClassType(interopReferences)) { return interopReferences.WindowsRuntimeModule.CreateTypeReference( ns: $"ABI.{type.Namespace}", From ae740013382dde71cf0e2d50b73753310a99de6d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 20 Dec 2025 11:28:03 -0800 Subject: [PATCH 2/8] Add TryGetNullableUnderlyingType extension method Introduces a new TryGetNullableUnderlyingType method to extract the underlying type from a constructed Nullable type. Also updates XML documentation to reference types without the System. prefix for clarity. --- .../Extensions/WindowsRuntimeExtensions.cs | 51 +++++++++++++++---- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/src/WinRT.Interop.Generator/Extensions/WindowsRuntimeExtensions.cs b/src/WinRT.Interop.Generator/Extensions/WindowsRuntimeExtensions.cs index 1ade639d2..69d8ccdf3 100644 --- a/src/WinRT.Interop.Generator/Extensions/WindowsRuntimeExtensions.cs +++ b/src/WinRT.Interop.Generator/Extensions/WindowsRuntimeExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AsmResolver; using AsmResolver.DotNet; @@ -49,27 +50,27 @@ public bool TryGetGuidAttribute(InteropReferences interopReferences, out Guid ii extension(ITypeDescriptor type) { /// - /// Checks whether an is some type. + /// Checks whether an is some type. /// - /// Whether the type is some type. + /// Whether the type is some type. public bool IsTypeOfGuid(InteropReferences interopReferences) { return SignatureComparer.IgnoreVersion.Equals(type, interopReferences.Guid); } /// - /// Checks whether an is some type. + /// Checks whether an is some type. /// - /// Whether the type is some type. + /// Whether the type is some type. public bool IsTypeOfType(InteropReferences interopReferences) { return SignatureComparer.IgnoreVersion.Equals(type, interopReferences.Type); } /// - /// Checks whether an is some type. + /// Checks whether an is some type. /// - /// Whether the type is some type. + /// Whether the type is some type. public bool IsTypeOfException(InteropReferences interopReferences) { return SignatureComparer.IgnoreVersion.Equals(type, interopReferences.Exception); @@ -675,20 +676,50 @@ public bool IsConstructedKeyValuePairType(InteropReferences interopReferences) } /// - /// Checks whether a is some type. + /// Checks whether a is some type. /// /// The instance to use. - /// Whether the type is some type. + /// Whether the type is some type. public bool IsConstructedNullableValueType(InteropReferences interopReferences) { return SignatureComparer.IgnoreVersion.Equals((signature as GenericInstanceTypeSignature)?.GenericType, interopReferences.Nullable1); } /// - /// Checks whether a is some or type. + /// Tries to extract the underlying type from a constructed type. /// /// The instance to use. - /// Whether the type is some or type. + /// The underlying nullable type, if the input type is a constructed type. + /// Whether was successfully retrieved. + public bool TryGetNullableUnderlyingType(InteropReferences interopReferences, [NotNullWhen(true)] out TypeSignature? underlyingType) + { + // First check that we have some constructed generic value type. + // We also check that we have a single type argument to narrow down. + if (signature is not GenericInstanceTypeSignature { IsValueType: false, TypeArguments: [TypeSignature typeArgument] } genericSignature) + { + underlyingType = null; + + return false; + } + + // Check that we actually have a constructed 'Nullable' type + if (!SignatureComparer.IgnoreVersion.Equals(genericSignature.GenericType, interopReferences.Nullable1)) + { + underlyingType = null; + + return false; + } + + underlyingType = typeArgument; + + return true; + } + + /// + /// Checks whether a is some or type. + /// + /// The instance to use. + /// Whether the type is some or type. public bool IsConstructedSpanOrReadOnlySpanType(InteropReferences interopReferences) { if (signature is not GenericInstanceTypeSignature genericSignature) From 06fd219f9c02990bda098f2696d6bc9527e4364d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 20 Dec 2025 11:45:05 -0800 Subject: [PATCH 3/8] Refactor marshaller type resolution into dedicated resolver Moved marshaller type resolution logic from InteropMethodRewriteFactory to a new InteropMarshallerTypeResolver class. Updated all usages to use the new resolver, improving code organization and maintainability. Also fixed a bug in WindowsRuntimeExtensions to correctly check for value types in generic instance signatures. --- .../Extensions/WindowsRuntimeExtensions.cs | 2 +- ...opMethodRewriteFactory.ManagedParameter.cs | 16 ++-- ...ropMethodRewriteFactory.NativeParameter.cs | 10 +- .../InteropMethodRewriteFactory.RetVal.cs | 11 +-- ...InteropMethodRewriteFactory.ReturnValue.cs | 16 +--- .../Factories/InteropMethodRewriteFactory.cs | 94 ------------------- .../InteropMarshallerTypeResolver.cs | 71 ++++++++++++++ 7 files changed, 93 insertions(+), 127 deletions(-) delete mode 100644 src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.cs create mode 100644 src/WinRT.Interop.Generator/Resolvers/InteropMarshallerTypeResolver.cs diff --git a/src/WinRT.Interop.Generator/Extensions/WindowsRuntimeExtensions.cs b/src/WinRT.Interop.Generator/Extensions/WindowsRuntimeExtensions.cs index 69d8ccdf3..3a7805e0e 100644 --- a/src/WinRT.Interop.Generator/Extensions/WindowsRuntimeExtensions.cs +++ b/src/WinRT.Interop.Generator/Extensions/WindowsRuntimeExtensions.cs @@ -695,7 +695,7 @@ public bool TryGetNullableUnderlyingType(InteropReferences interopReferences, [N { // First check that we have some constructed generic value type. // We also check that we have a single type argument to narrow down. - if (signature is not GenericInstanceTypeSignature { IsValueType: false, TypeArguments: [TypeSignature typeArgument] } genericSignature) + if (signature is not GenericInstanceTypeSignature { IsValueType: true, TypeArguments: [TypeSignature typeArgument] } genericSignature) { underlyingType = null; diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs index e4e1003d0..e2764923f 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs @@ -9,12 +9,15 @@ using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Generation; using WindowsRuntime.InteropGenerator.References; +using WindowsRuntime.InteropGenerator.Resolvers; using static AsmResolver.PE.DotNet.Cil.CilOpCodes; namespace WindowsRuntime.InteropGenerator.Factories; -/// -internal partial class InteropMethodRewriteFactory +/// +/// A factory to rewrite interop method definitons, and add marshalling code as needed. +/// +internal static partial class InteropMethodRewriteFactory { /// /// Contains the logic for marshalling managed parameters (i.e. parameters that are passed to managed methods). @@ -82,10 +85,7 @@ public static void RewriteMethod( } else if (parameterType.IsConstructedNullableValueType(interopReferences)) { - TypeSignature underlyingType = ((GenericInstanceTypeSignature)parameterType).TypeArguments[0]; - - // For 'Nullable' return types, we need the marshaller for the instantiated 'T' type (same as for return values) - ITypeDefOrRef marshallerType = GetValueTypeMarshallerType(underlyingType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); // Get the right reference to the unboxing marshalling method to call IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( @@ -103,7 +103,7 @@ public static void RewriteMethod( { // The last case handles all other value types. It doesn't matter if they possibly hold some unmanaged // resources, as they're only being used as parameters. That means the caller is responsible for disposal. - ITypeDefOrRef marshallerType = GetValueTypeMarshallerType(parameterType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); // Get the reference to 'ConvertToManaged' to produce the resulting value to return IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( @@ -135,7 +135,7 @@ public static void RewriteMethod( else { // Get the marshaller type for all other reference types - ITypeDefOrRef marshallerType = GetReferenceTypeMarshallerType(parameterType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); // Get the marshalling method, with the parameter type always just being 'void*' here too IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.NativeParameter.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.NativeParameter.cs index 71acbfd64..5ef966ed1 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.NativeParameter.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.NativeParameter.cs @@ -10,6 +10,7 @@ using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Generation; using WindowsRuntime.InteropGenerator.References; +using WindowsRuntime.InteropGenerator.Resolvers; using static AsmResolver.PE.DotNet.Cil.CilOpCodes; #pragma warning disable CS1573 @@ -100,10 +101,7 @@ public static void RewriteMethod( } else if (parameterType.IsConstructedNullableValueType(interopReferences)) { - TypeSignature underlyingType = ((GenericInstanceTypeSignature)parameterType).TypeArguments[0]; - - // For 'Nullable' return types, we need the marshaller for the instantiated 'T' type (same as for return values) - ITypeDefOrRef marshallerType = GetValueTypeMarshallerType(underlyingType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); // Get the right reference to the unboxing marshalling method to call IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( @@ -127,7 +125,7 @@ public static void RewriteMethod( else { // The last case handles all other value types, which need explicit disposal for their ABI values - ITypeDefOrRef marshallerType = GetValueTypeMarshallerType(parameterType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); // Get the reference to 'ConvertToUnmanaged' to produce the resulting value to pass as argument IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( @@ -189,7 +187,7 @@ public static void RewriteMethod( else { // Get the marshaller for all other types (doesn't matter if constructed generics or not) - ITypeDefOrRef marshallerType = GetReferenceTypeMarshallerType(parameterType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); // Get the reference to 'ConvertToUnmanaged' to produce the resulting value to pass as argument IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs index 6188ebdc9..1d36d6e70 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs @@ -9,6 +9,7 @@ using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Generation; using WindowsRuntime.InteropGenerator.References; +using WindowsRuntime.InteropGenerator.Resolvers; using static AsmResolver.PE.DotNet.Cil.CilOpCodes; #pragma warning disable CS1573, IDE0072 @@ -103,11 +104,7 @@ public static void RewriteMethod( } else if (retValType.IsConstructedNullableValueType(interopReferences)) { - TypeSignature underlyingType = ((GenericInstanceTypeSignature)retValType).TypeArguments[0]; - - // For 'Nullable' return types, we need the marshaller for the instantiated 'T' - // type, as that will contain the boxing methods. See more info in 'ReturnValue'. - ITypeDefOrRef marshallerType = GetValueTypeMarshallerType(underlyingType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(retValType, interopReferences, emitState); // Get the right reference to the boxing marshalling method to call IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( @@ -127,7 +124,7 @@ public static void RewriteMethod( else { // For all other struct types, we just always defer to their generated marshaller type - ITypeDefOrRef marshallerType = GetValueTypeMarshallerType(retValType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(retValType, interopReferences, emitState); // Get the reference to 'ConvertToUnmanaged' to produce the resulting value to return IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( @@ -176,7 +173,7 @@ public static void RewriteMethod( else { // Get the marshaller type for either generic reference types, or all other reference types - ITypeDefOrRef marshallerType = GetReferenceTypeMarshallerType(retValType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(retValType, interopReferences, emitState); // Get the marshalling method for this '[retval]' type IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ReturnValue.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ReturnValue.cs index d0e1e6785..5554cbbb8 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ReturnValue.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ReturnValue.cs @@ -8,6 +8,7 @@ using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Generation; using WindowsRuntime.InteropGenerator.References; +using WindowsRuntime.InteropGenerator.Resolvers; using static AsmResolver.PE.DotNet.Cil.CilOpCodes; #pragma warning disable CS1573 @@ -90,14 +91,7 @@ public static void RewriteMethod( } else if (returnType.IsConstructedNullableValueType(interopReferences)) { - TypeSignature underlyingType = ((GenericInstanceTypeSignature)returnType).TypeArguments[0]; - - // For 'Nullable' return types, we need the marshaller for the instantiated 'T' - // type, as that will contain the unboxing methods. The 'T' in this case can be - // a custom-mapped primitive type or a projected type. Technically speaking it can - // never be a 'KeyValuePair<,>' or 'Nullable', because both of those are interface - // types in the Windows Runtime type system, meaning they can't be boxed like value types. - ITypeDefOrRef marshallerType = GetValueTypeMarshallerType(underlyingType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(returnType, interopReferences, emitState); // Get the right reference to the unboxing marshalling method to call IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( @@ -121,7 +115,7 @@ public static void RewriteMethod( // Here we're marshalling a value type that is managed, meaning its ABI type will // hold some references to unmanaged resources. In this case we need to resolve the // marshaller type so we can both marshal the value and also clean resources after. - ITypeDefOrRef marshallerType = GetValueTypeMarshallerType(returnType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(returnType, interopReferences, emitState); // Get the reference to 'ConvertToManaged' to produce the resulting value to return IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( @@ -151,7 +145,7 @@ public static void RewriteMethod( { // The last case is a non-blittable, unmanaged value type. That is, we still have to call // the marshalling method to get the return value, but no resources cleanup is needed. - ITypeDefOrRef marshallerType = GetValueTypeMarshallerType(returnType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(returnType, interopReferences, emitState); // Get the reference to 'ConvertToManaged' to produce the resulting value to return IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( @@ -216,7 +210,7 @@ public static void RewriteMethod( else { // Get the marshaller type for either generic reference types, or all other reference types - ITypeDefOrRef marshallerType = GetReferenceTypeMarshallerType(returnType, interopReferences, emitState); + ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(returnType, interopReferences, emitState); // Get the marshalling method, with the parameter type always just being 'void*' here too IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.cs deleted file mode 100644 index 92f8f376f..000000000 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures; -using WindowsRuntime.InteropGenerator.Generation; -using WindowsRuntime.InteropGenerator.References; - -namespace WindowsRuntime.InteropGenerator.Factories; - -/// -/// A factory to rewrite interop method definitons, and add marshalling code as needed. -/// -internal static partial class InteropMethodRewriteFactory -{ - /// - /// Get the marshaller type for a specified value type. - /// - /// The value type to get the marshaller type for. - /// The instance to use. - /// The emit state for this invocation. - /// The marshaller type for . - private static ITypeDefOrRef GetValueTypeMarshallerType( - TypeSignature type, - InteropReferences interopReferences, - InteropGeneratorEmitState emitState) - { - // For generic instantiations (the only one possible is 'KeyValuePair<,>' - // here), the marshaller type will be in the same 'WinRT.Interop.dll'. - if (type is GenericInstanceTypeSignature) - { - return emitState.LookupTypeDefinition(type, "Marshaller"); - } - - // For primitive types, the marshaller type is in 'WinRT.Runtime.dll'. - // In this case we can also rely on all types being under 'System'. - // The same applies to custom-mapped struct types. - if (type.IsFundamentalWindowsRuntimeType(interopReferences) || - type.IsCustomMappedWindowsRuntimeNonGenericStructOrClassType(interopReferences)) - { - return interopReferences.WindowsRuntimeModule.CreateTypeReference( - ns: "ABI.System"u8, - name: $"{type.Name}Marshaller"); - } - - // In all other cases, the marshaller type will be in the declared assembly - return type.Resolve()!.DeclaringModule!.CreateTypeReference( - ns: $"ABI.{type.Namespace}", - name: $"{type.Name}Marshaller"); - } - - /// - /// Get the marshaller type for a specified reference type. - /// - /// The reference type to get the marshaller type for. - /// The instance to use. - /// The emit state for this invocation. - /// The marshaller type for . - private static ITypeDefOrRef GetReferenceTypeMarshallerType( - TypeSignature type, - InteropReferences interopReferences, - InteropGeneratorEmitState emitState) - { - // Just like for value types, generic types have marshaller types in 'WinRT.Interop.dll' - if (type is GenericInstanceTypeSignature) - { - return emitState.LookupTypeDefinition(type, "Marshaller"); - } - - // Special case 'object', we'll directly use 'WindowsRuntimeObjectMarshaller' for it - if (type.IsTypeOfObject()) - { - return interopReferences.WindowsRuntimeObjectMarshaller; - } - - // For custom-mapped types, get the marshaller type from 'WinRT.Runtime.dll' - if (type.IsCustomMappedWindowsRuntimeNonGenericInterfaceType(interopReferences) || - type.IsCustomMappedWindowsRuntimeNonGenericDelegateType(interopReferences) || - type.IsCustomMappedWindowsRuntimeNonGenericStructOrClassType(interopReferences)) - { - return interopReferences.WindowsRuntimeModule.CreateTypeReference( - ns: $"ABI.{type.Namespace}", - name: $"{type.Name}Marshaller"); - } - - // In all other cases, the marshaller type will be in the declared assembly. Note that this - // also includes special manually projected types, such as 'AsyncActionCompletedHandler'. - // Even though those types are in 'WinRT.Runtime.dll', the marshaller type will also be - // there, so trying to resolve it via the declaring module like for other types is fine. - return type.Resolve()!.DeclaringModule!.CreateTypeReference( - ns: $"ABI.{type.Namespace}", - name: $"{type.Name}Marshaller"); - } -} \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Resolvers/InteropMarshallerTypeResolver.cs b/src/WinRT.Interop.Generator/Resolvers/InteropMarshallerTypeResolver.cs new file mode 100644 index 000000000..d5e8ec7c4 --- /dev/null +++ b/src/WinRT.Interop.Generator/Resolvers/InteropMarshallerTypeResolver.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.InteropGenerator.Generation; +using WindowsRuntime.InteropGenerator.References; + +namespace WindowsRuntime.InteropGenerator.Resolvers; + +/// +/// A resolver for marshaller types for Windows Runtime types. +/// +internal static class InteropMarshallerTypeResolver +{ + /// + /// Get the marshaller type for a specified type. + /// + /// The type to get the marshaller type for. + /// The instance to use. + /// The emit state for this invocation. + /// The marshaller type for . + public static ITypeDefOrRef GetMarshallerType( + TypeSignature type, + InteropReferences interopReferences, + InteropGeneratorEmitState emitState) + { + // First handle constructed generic types (which can be either value types or reference types) + if (type is GenericInstanceTypeSignature) + { + // For 'Nullable' return types, we need the marshaller for the instantiated 'T' type, + // as that will contain the unboxing methods. The 'T' in this case can be a custom-mapped + // primitive type or a projected value type. Technically speaking it can never be a + // 'KeyValuePair<,>' or 'Nullable', because both of those are interface types in the + // Windows Runtime type system, meaning they can't be boxed like value types. + if (type.TryGetNullableUnderlyingType(interopReferences, out TypeSignature? underlyingType)) + { + return GetMarshallerType(underlyingType, interopReferences, emitState); + } + + // For all other generic instantiations (including 'KeyValuePair<,>'), we can just look the marshaller + // types up. All those marshaller types will always be generated in the same 'WinRT.Interop.dll'. + return emitState.LookupTypeDefinition(type, "Marshaller"); + } + + // Special case 'object', we'll directly use 'WindowsRuntimeObjectMarshaller' for it + if (type.IsTypeOfObject()) + { + return interopReferences.WindowsRuntimeObjectMarshaller; + } + + // For custom-mapped types, get the marshaller type from 'WinRT.Runtime.dll' + if (type.IsFundamentalWindowsRuntimeType(interopReferences) || + type.IsCustomMappedWindowsRuntimeNonGenericInterfaceType(interopReferences) || + type.IsCustomMappedWindowsRuntimeNonGenericDelegateType(interopReferences) || + type.IsCustomMappedWindowsRuntimeNonGenericStructOrClassType(interopReferences)) + { + return interopReferences.WindowsRuntimeModule.CreateTypeReference( + ns: $"ABI.{type.Namespace}", + name: $"{type.Name}Marshaller"); + } + + // In all other cases, the marshaller type will be in the declared assembly. Note that this + // also includes special manually projected types, such as 'AsyncActionCompletedHandler'. + // Even though those types are in 'WinRT.Runtime.dll', the marshaller type will also be + // there, so trying to resolve it via the declaring module like for other types is fine. + return type.Resolve()!.DeclaringModule!.CreateTypeReference( + ns: $"ABI.{type.Namespace}", + name: $"{type.Name}Marshaller"); + } +} From 3ddaaa8ba7966a4f714e9bb4652d0079eff5986a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 20 Dec 2025 12:59:49 -0800 Subject: [PATCH 4/8] Add boxing and unboxing for Exception marshaller Marked ExceptionMarshaller as unsafe and added BoxToUnmanaged and UnboxToManaged methods to support boxing and unboxing of System.Exception objects for interop scenarios. --- src/WinRT.Runtime2/ABI/System/Exception.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/WinRT.Runtime2/ABI/System/Exception.cs b/src/WinRT.Runtime2/ABI/System/Exception.cs index e15674e1a..37c20f295 100644 --- a/src/WinRT.Runtime2/ABI/System/Exception.cs +++ b/src/WinRT.Runtime2/ABI/System/Exception.cs @@ -51,7 +51,7 @@ public struct Exception DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] -public static class ExceptionMarshaller +public static unsafe class ExceptionMarshaller { /// /// Converts a managed to an unmanaged . @@ -72,6 +72,20 @@ public static Exception ConvertToUnmanaged(global::System.Exception? value) { return RestrictedErrorInfo.GetExceptionForHR(value.Value); } + + /// + public static WindowsRuntimeObjectReferenceValue BoxToUnmanaged(global::System.Exception? value) + { + return value is null ? default : new((void*)WindowsRuntimeComWrappers.Default.GetOrCreateComInterfaceForObject(value, CreateComInterfaceFlags.None, in WellKnownWindowsInterfaceIIDs.IID_IReferenceOfException)); + } + + /// + public static global::System.Exception? UnboxToManaged(void* value) + { + Exception? abi = WindowsRuntimeValueTypeMarshaller.UnboxToManaged(value); + + return abi.HasValue ? ConvertToManaged(abi.GetValueOrDefault()) : null; + } } /// From 949000f099ecec4883c9447f0c69f6f754bec69d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 20 Dec 2025 14:01:12 -0800 Subject: [PATCH 5/8] Refactor marshaller type resolution and usage Introduces InteropMarshallerType as a ref struct to encapsulate marshaller type and method resolution. Updates all usages to leverage strongly-typed marshaller method accessors, improving code clarity and reducing repeated logic. Refactors InteropMarshallerTypeResolver to return InteropMarshallerType instead of ITypeDefOrRef. --- ...opMethodRewriteFactory.ManagedParameter.cs | 33 +---- ...ropMethodRewriteFactory.NativeParameter.cs | 42 +----- .../InteropMethodRewriteFactory.RetVal.cs | 33 +---- ...InteropMethodRewriteFactory.ReturnValue.cs | 53 ++------ .../Resolvers/InteropMarshallerType.cs | 128 ++++++++++++++++++ .../InteropMarshallerTypeResolver.cs | 32 +++-- 6 files changed, 176 insertions(+), 145 deletions(-) create mode 100644 src/WinRT.Interop.Generator/Resolvers/InteropMarshallerType.cs diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs index e2764923f..afc86a49e 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs @@ -85,37 +85,23 @@ public static void RewriteMethod( } else if (parameterType.IsConstructedNullableValueType(interopReferences)) { - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); - - // Get the right reference to the unboxing marshalling method to call - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "UnboxToManaged"u8, - signature: MethodSignature.CreateStatic( - returnType: parameterType, - parameterTypes: [module.CorLibTypeFactory.Void.MakePointerType()])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); // Emit code similar to 'KeyValuePair<,>' above, to marshal the resulting 'Nullable' value body.Instructions.ReferenceReplaceRange(marker, [ CilInstruction.CreateLdarg(parameterIndex), - new CilInstruction(Call, marshallerMethod.Import(module))]); + new CilInstruction(Call, marshallerType.UnboxToManaged().Import(module))]); } else { // The last case handles all other value types. It doesn't matter if they possibly hold some unmanaged // resources, as they're only being used as parameters. That means the caller is responsible for disposal. - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); - - // Get the reference to 'ConvertToManaged' to produce the resulting value to return - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "ConvertToManaged"u8, - signature: MethodSignature.CreateStatic( - returnType: parameterType, - parameterTypes: [parameterType.GetAbiType(interopReferences)])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); // We can directly call the marshaller and return it, no 'try/finally' complexity is needed body.Instructions.ReferenceReplaceRange(marker, [ CilInstruction.CreateLdarg(parameterIndex), - new CilInstruction(Call, marshallerMethod.Import(module))]); + new CilInstruction(Call, marshallerType.ConvertToManaged().Import(module))]); } } else if (parameterType.IsTypeOfString()) @@ -135,19 +121,12 @@ public static void RewriteMethod( else { // Get the marshaller type for all other reference types - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); - - // Get the marshalling method, with the parameter type always just being 'void*' here too - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "ConvertToManaged"u8, - signature: MethodSignature.CreateStatic( - returnType: parameterType, - parameterTypes: [module.CorLibTypeFactory.Void.MakePointerType()])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); // Marshal the value and release the original interface pointer body.Instructions.ReferenceReplaceRange(marker, [ CilInstruction.CreateLdarg(parameterIndex), - new CilInstruction(Call, marshallerMethod.Import(module))]); + new CilInstruction(Call, marshallerType.ConvertToManaged().Import(module))]); } } } diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.NativeParameter.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.NativeParameter.cs index 5ef966ed1..29ac2bb6a 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.NativeParameter.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.NativeParameter.cs @@ -101,14 +101,7 @@ public static void RewriteMethod( } else if (parameterType.IsConstructedNullableValueType(interopReferences)) { - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); - - // Get the right reference to the unboxing marshalling method to call - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "BoxToUnmanaged"u8, - signature: MethodSignature.CreateStatic( - returnType: interopReferences.WindowsRuntimeObjectReferenceValue.ToValueTypeSignature(), - parameterTypes: [parameterType])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); RewriteBody( parameterType: parameterType, @@ -117,7 +110,7 @@ public static void RewriteMethod( loadMarker: loadMarker, finallyMarker: finallyMarker, parameterIndex: parameterIndex, - marshallerMethod: marshallerMethod, + marshallerMethod: marshallerType.BoxToUnmanaged(), disposeMethod: null, interopReferences: interopReferences, module: module); @@ -125,21 +118,7 @@ public static void RewriteMethod( else { // The last case handles all other value types, which need explicit disposal for their ABI values - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); - - // Get the reference to 'ConvertToUnmanaged' to produce the resulting value to pass as argument - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "ConvertToUnmanaged"u8, - signature: MethodSignature.CreateStatic( - returnType: parameterType.GetAbiType(interopReferences), - parameterTypes: [parameterType])); - - // Get the reference to 'Dispose' method to call on the ABI value - IMethodDefOrRef disposeMethod = marshallerType.GetMethodDefOrRef( - name: "Dispose"u8, - signature: MethodSignature.CreateStatic( - returnType: interopReferences.CorLibTypeFactory.Void, - parameterTypes: [parameterType.GetAbiType(interopReferences)])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); RewriteBody( parameterType: parameterType, @@ -148,8 +127,8 @@ public static void RewriteMethod( loadMarker: loadMarker, finallyMarker: finallyMarker, parameterIndex: parameterIndex, - marshallerMethod: marshallerMethod, - disposeMethod: disposeMethod, + marshallerMethod: marshallerType.ConvertToUnmanaged(), + disposeMethod: marshallerType.Dispose(), interopReferences: interopReferences, module: module); } @@ -187,14 +166,7 @@ public static void RewriteMethod( else { // Get the marshaller for all other types (doesn't matter if constructed generics or not) - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); - - // Get the reference to 'ConvertToUnmanaged' to produce the resulting value to pass as argument - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "ConvertToUnmanaged"u8, - signature: MethodSignature.CreateStatic( - returnType: interopReferences.WindowsRuntimeObjectReferenceValue.ToValueTypeSignature(), - parameterTypes: [parameterType])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); RewriteBody( parameterType: parameterType, @@ -203,7 +175,7 @@ public static void RewriteMethod( loadMarker: loadMarker, finallyMarker: finallyMarker, parameterIndex: parameterIndex, - marshallerMethod: marshallerMethod, + marshallerMethod: marshallerType.ConvertToUnmanaged(), disposeMethod: null, interopReferences: interopReferences, module: module); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs index 1d36d6e70..b9aad7c7d 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs @@ -104,38 +104,24 @@ public static void RewriteMethod( } else if (retValType.IsConstructedNullableValueType(interopReferences)) { - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(retValType, interopReferences, emitState); - - // Get the right reference to the boxing marshalling method to call - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "BoxToManaged"u8, - signature: MethodSignature.CreateStatic( - returnType: retValType, - parameterTypes: [module.CorLibTypeFactory.Void.MakePointerType()])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(retValType, interopReferences, emitState); // Emit code similar to 'KeyValuePair<,>' above, to marshal the resulting 'Nullable' value RewriteBody( body: body, marker: marker, - marshallerMethod: marshallerMethod, + marshallerMethod: marshallerType.BoxToUnmanaged(), interopReferences: interopReferences, module: module); } else { // For all other struct types, we just always defer to their generated marshaller type - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(retValType, interopReferences, emitState); - - // Get the reference to 'ConvertToUnmanaged' to produce the resulting value to return - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "ConvertToUnmanaged"u8, - signature: MethodSignature.CreateStatic( - returnType: retValType, - parameterTypes: [retValType.GetAbiType(interopReferences)])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(retValType, interopReferences, emitState); // Delegate to the marshaller to convert the managed value type on the evaluation stack body.Instructions.ReferenceReplaceRange(marker, [ - new CilInstruction(Call, marshallerMethod.Import(module)), + new CilInstruction(Call, marshallerType.ConvertToUnmanaged().Import(module)), new CilInstruction(Stobj, retValType.GetAbiType(interopReferences).Import(module).ToTypeDefOrRef())]); } } @@ -173,20 +159,13 @@ public static void RewriteMethod( else { // Get the marshaller type for either generic reference types, or all other reference types - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(retValType, interopReferences, emitState); - - // Get the marshalling method for this '[retval]' type - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "ConvertToUnmanaged"u8, - signature: MethodSignature.CreateStatic( - returnType: interopReferences.WindowsRuntimeObjectReferenceValue.ToValueTypeSignature(), - parameterTypes: [retValType])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(retValType, interopReferences, emitState); // Marshal the value and assign it to the target location RewriteBody( body: body, marker: marker, - marshallerMethod: marshallerMethod, + marshallerMethod: marshallerType.ConvertToUnmanaged(), interopReferences: interopReferences, module: module); } diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ReturnValue.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ReturnValue.cs index 5554cbbb8..9dbbe71d2 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ReturnValue.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ReturnValue.cs @@ -91,14 +91,7 @@ public static void RewriteMethod( } else if (returnType.IsConstructedNullableValueType(interopReferences)) { - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(returnType, interopReferences, emitState); - - // Get the right reference to the unboxing marshalling method to call - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "UnboxToManaged"u8, - signature: MethodSignature.CreateStatic( - returnType: returnType, - parameterTypes: [module.CorLibTypeFactory.Void.MakePointerType()])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(returnType, interopReferences, emitState); // Emit code similar to 'KeyValuePair<,>' above, to marshal the resulting 'Nullable' value RewriteBody( @@ -106,7 +99,7 @@ public static void RewriteMethod( body: body, marker: marker, source: source, - marshallerMethod: marshallerMethod, + marshallerMethod: marshallerType.UnboxToManaged(), releaseOrDisposeMethod: interopReferences.WindowsRuntimeUnknownMarshallerFree, module: module); } @@ -115,21 +108,7 @@ public static void RewriteMethod( // Here we're marshalling a value type that is managed, meaning its ABI type will // hold some references to unmanaged resources. In this case we need to resolve the // marshaller type so we can both marshal the value and also clean resources after. - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(returnType, interopReferences, emitState); - - // Get the reference to 'ConvertToManaged' to produce the resulting value to return - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "ConvertToManaged"u8, - signature: MethodSignature.CreateStatic( - returnType: returnType, - parameterTypes: [returnType.GetAbiType(interopReferences)])); - - // Get the reference to 'Dispose' too, as the ABI type has some unmanaged references - IMethodDefOrRef disposeMethod = marshallerType.GetMethodDefOrRef( - name: "Dispose"u8, - signature: MethodSignature.CreateStatic( - returnType: module.CorLibTypeFactory.Void, - parameterTypes: [returnType.GetAbiType(interopReferences)])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(returnType, interopReferences, emitState); // Emit code similar to the cases above, but calling 'Dispose' on the ABI type instead of releasing it RewriteBody( @@ -137,27 +116,20 @@ public static void RewriteMethod( body: body, marker: marker, source: source, - marshallerMethod: marshallerMethod, - releaseOrDisposeMethod: disposeMethod, + marshallerMethod: marshallerType.ConvertToManaged(), + releaseOrDisposeMethod: marshallerType.Dispose(), module: module); } else { // The last case is a non-blittable, unmanaged value type. That is, we still have to call // the marshalling method to get the return value, but no resources cleanup is needed. - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(returnType, interopReferences, emitState); - - // Get the reference to 'ConvertToManaged' to produce the resulting value to return - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "ConvertToManaged"u8, - signature: MethodSignature.CreateStatic( - returnType: returnType, - parameterTypes: [returnType.GetAbiType(interopReferences)])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(returnType, interopReferences, emitState); // We can directly call the marshaller and return it, no 'try/finally' complexity is needed body.Instructions.ReferenceReplaceRange(marker, [ CilInstruction.CreateLdloc(source, body), - new CilInstruction(Call, marshallerMethod.Import(module)), + new CilInstruction(Call, marshallerType.ConvertToManaged().Import(module)), new CilInstruction(Ret)]); } } @@ -210,14 +182,7 @@ public static void RewriteMethod( else { // Get the marshaller type for either generic reference types, or all other reference types - ITypeDefOrRef marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(returnType, interopReferences, emitState); - - // Get the marshalling method, with the parameter type always just being 'void*' here too - IMethodDefOrRef marshallerMethod = marshallerType.GetMethodDefOrRef( - name: "ConvertToManaged"u8, - signature: MethodSignature.CreateStatic( - returnType: returnType, - parameterTypes: [module.CorLibTypeFactory.Void.MakePointerType()])); + InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(returnType, interopReferences, emitState); // Marshal the value and release the original interface pointer RewriteBody( @@ -225,7 +190,7 @@ public static void RewriteMethod( body: body, marker: marker, source: source, - marshallerMethod: marshallerMethod, + marshallerMethod: marshallerType.ConvertToManaged(), releaseOrDisposeMethod: interopReferences.WindowsRuntimeUnknownMarshallerFree, module: module); } diff --git a/src/WinRT.Interop.Generator/Resolvers/InteropMarshallerType.cs b/src/WinRT.Interop.Generator/Resolvers/InteropMarshallerType.cs new file mode 100644 index 000000000..ed2874ee0 --- /dev/null +++ b/src/WinRT.Interop.Generator/Resolvers/InteropMarshallerType.cs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.InteropGenerator.References; + +namespace WindowsRuntime.InteropGenerator.Resolvers; + +/// +/// A resolver for marshaller types for Windows Runtime types. +/// +internal readonly ref struct InteropMarshallerType +{ + /// + /// The managed type being marshalled. + /// + private readonly TypeSignature _type; + + /// + /// The instance to use. + /// + private readonly InteropReferences _interopReferences; + + /// + /// The for the marshaller type for . + /// + private readonly ITypeDefOrRef _marshallerType; + + /// + /// Creates a new instance with the specified parameters. + /// + /// + /// + /// + public InteropMarshallerType( + TypeSignature type, + InteropReferences interopReferences, + ITypeDefOrRef marshallerType) + { + _type = type; + _interopReferences = interopReferences; + _marshallerType = marshallerType; + } + + /// + /// Gets the for the ConvertToManaged method for a specified type. + /// + /// The resulting value. + public IMethodDefOrRef ConvertToManaged() + { + return _marshallerType.GetMethodDefOrRef( + name: "ConvertToManaged"u8, + signature: MethodSignature.CreateStatic( + returnType: _type, + parameterTypes: [_type.GetAbiType(_interopReferences)])); + } + + /// + /// Gets the for the ConvertToUnmanaged method for a specified type. + /// + /// The resulting value. + public IMethodDefOrRef ConvertToUnmanaged() + { + TypeSignature abiType = _type.GetAbiType(_interopReferences); + + // If the ABI type is 'void*' and the type is not 'string', then we use 'WindowsRuntimeObjectReferenceValue'. + // This is because that's always used to return Windows Runtime native objects. Just 'HSTRING' is special. + TypeSignature returnType = abiType.IsTypeOfVoidPointer() && !_type.IsTypeOfString() + ? _interopReferences.WindowsRuntimeObjectReferenceValue.ToValueTypeSignature() + : abiType; + + return _marshallerType.GetMethodDefOrRef( + name: "ConvertToUnmanaged"u8, + signature: MethodSignature.CreateStatic( + returnType: returnType, + parameterTypes: [_type])); + } + + /// + /// Gets the for the BoxToUnmanaged method for a specified type. + /// + /// The resulting value. + public IMethodDefOrRef BoxToUnmanaged() + { + // When boxing, the parameter is either 'Nullable' for value types, or just the same type + TypeSignature parameterType = _type.IsValueType + ? _interopReferences.Nullable1.MakeGenericValueType(_type) + : _type; + + return _marshallerType.GetMethodDefOrRef( + name: "BoxToUnmanaged"u8, + signature: MethodSignature.CreateStatic( + returnType: _interopReferences.WindowsRuntimeObjectReferenceValue.ToValueTypeSignature(), + parameterTypes: [parameterType])); + } + + /// + /// Gets the for the UnboxToManaged method for a specified type. + /// + /// The resulting value. + public IMethodDefOrRef UnboxToManaged() + { + // When unboxing, the return type is either 'Nullable' for value types, or just the same type + TypeSignature returnType = _type.IsValueType + ? _interopReferences.Nullable1.MakeGenericValueType(_type) + : _type; + + return _marshallerType.GetMethodDefOrRef( + name: "UnboxToManaged"u8, + signature: MethodSignature.CreateStatic( + returnType: returnType, + parameterTypes: [_interopReferences.CorLibTypeFactory.Void.MakePointerType()])); + } + + /// + /// Gets the for the Dispose method for a specified type. + /// + /// The resulting value. + public IMethodDefOrRef Dispose() + { + return _marshallerType.GetMethodDefOrRef( + name: "Dispose"u8, + signature: MethodSignature.CreateStatic( + returnType: _interopReferences.CorLibTypeFactory.Void, + parameterTypes: [_type.GetAbiType(_interopReferences)])); + } +} diff --git a/src/WinRT.Interop.Generator/Resolvers/InteropMarshallerTypeResolver.cs b/src/WinRT.Interop.Generator/Resolvers/InteropMarshallerTypeResolver.cs index d5e8ec7c4..09f607161 100644 --- a/src/WinRT.Interop.Generator/Resolvers/InteropMarshallerTypeResolver.cs +++ b/src/WinRT.Interop.Generator/Resolvers/InteropMarshallerTypeResolver.cs @@ -14,13 +14,13 @@ namespace WindowsRuntime.InteropGenerator.Resolvers; internal static class InteropMarshallerTypeResolver { /// - /// Get the marshaller type for a specified type. + /// Gets the marshaller type for a specified type. /// /// The type to get the marshaller type for. /// The instance to use. /// The emit state for this invocation. /// The marshaller type for . - public static ITypeDefOrRef GetMarshallerType( + public static InteropMarshallerType GetMarshallerType( TypeSignature type, InteropReferences interopReferences, InteropGeneratorEmitState emitState) @@ -40,13 +40,15 @@ public static ITypeDefOrRef GetMarshallerType( // For all other generic instantiations (including 'KeyValuePair<,>'), we can just look the marshaller // types up. All those marshaller types will always be generated in the same 'WinRT.Interop.dll'. - return emitState.LookupTypeDefinition(type, "Marshaller"); + ITypeDefOrRef marshallerType = emitState.LookupTypeDefinition(type, "Marshaller"); + + return new(type, interopReferences, marshallerType); } // Special case 'object', we'll directly use 'WindowsRuntimeObjectMarshaller' for it if (type.IsTypeOfObject()) { - return interopReferences.WindowsRuntimeObjectMarshaller; + return new(type, interopReferences, interopReferences.WindowsRuntimeObjectMarshaller); } // For custom-mapped types, get the marshaller type from 'WinRT.Runtime.dll' @@ -55,17 +57,23 @@ public static ITypeDefOrRef GetMarshallerType( type.IsCustomMappedWindowsRuntimeNonGenericDelegateType(interopReferences) || type.IsCustomMappedWindowsRuntimeNonGenericStructOrClassType(interopReferences)) { - return interopReferences.WindowsRuntimeModule.CreateTypeReference( + ITypeDefOrRef marshallerType = interopReferences.WindowsRuntimeModule.CreateTypeReference( ns: $"ABI.{type.Namespace}", name: $"{type.Name}Marshaller"); + + return new(type, interopReferences, marshallerType); } + else + { + // In all other cases, the marshaller type will be in the declared assembly. Note that this + // also includes special manually projected types, such as 'AsyncActionCompletedHandler'. + // Even though those types are in 'WinRT.Runtime.dll', the marshaller type will also be + // there, so trying to resolve it via the declaring module like for other types is fine. + ITypeDefOrRef marshallerType = type.Resolve()!.DeclaringModule!.CreateTypeReference( + ns: $"ABI.{type.Namespace}", + name: $"{type.Name}Marshaller"); - // In all other cases, the marshaller type will be in the declared assembly. Note that this - // also includes special manually projected types, such as 'AsyncActionCompletedHandler'. - // Even though those types are in 'WinRT.Runtime.dll', the marshaller type will also be - // there, so trying to resolve it via the declaring module like for other types is fine. - return type.Resolve()!.DeclaringModule!.CreateTypeReference( - ns: $"ABI.{type.Namespace}", - name: $"{type.Name}Marshaller"); + return new(type, interopReferences, marshallerType); + } } } From c3d773ae16f082898ec087dce74e0833e2a92860 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 20 Dec 2025 14:13:45 -0800 Subject: [PATCH 6/8] Refactor generic type marshalling logic Removed special-case handling for generic instance types (such as KeyValuePair and constructed interfaces/delegates) in parameter and return value marshalling. Generic types are now handled by the standard marshaller resolution logic, simplifying the code and reducing duplication. --- ...opMethodRewriteFactory.ManagedParameter.cs | 21 ++++--------------- .../InteropMethodRewriteFactory.RetVal.cs | 10 --------- ...InteropMethodRewriteFactory.ReturnValue.cs | 14 ------------- 3 files changed, 4 insertions(+), 41 deletions(-) diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs index afc86a49e..f4644c925 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs @@ -76,18 +76,11 @@ public static void RewriteMethod( { body.Instructions.ReferenceReplaceRange(marker, CilInstruction.CreateLdarg(parameterIndex)); } - else if (parameterType.IsConstructedKeyValuePairType(interopReferences)) - { - // If the type is some constructed 'KeyValuePair<,>' type, we use the generated marshaller - body.Instructions.ReferenceReplaceRange(marker, [ - CilInstruction.CreateLdarg(parameterIndex), - new CilInstruction(Call, emitState.LookupTypeDefinition(parameterType, "Marshaller").GetMethod("ConvertToManaged"))]); - } else if (parameterType.IsConstructedNullableValueType(interopReferences)) { InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); - // Emit code similar to 'KeyValuePair<,>' above, to marshal the resulting 'Nullable' value + // For 'Nullable' parameters (i.e. we have an 'IReference' interface pointer), we unbox the underlying type body.Instructions.ReferenceReplaceRange(marker, [ CilInstruction.CreateLdarg(parameterIndex), new CilInstruction(Call, marshallerType.UnboxToManaged().Import(module))]); @@ -96,6 +89,7 @@ public static void RewriteMethod( { // The last case handles all other value types. It doesn't matter if they possibly hold some unmanaged // resources, as they're only being used as parameters. That means the caller is responsible for disposal. + // This case can also handle 'KeyValuePair<,>' instantiations, which are just marshalled normally too. InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); // We can directly call the marshaller and return it, no 'try/finally' complexity is needed @@ -111,19 +105,12 @@ public static void RewriteMethod( CilInstruction.CreateLdarg(parameterIndex), new CilInstruction(Call, interopReferences.HStringMarshallerConvertToManaged.Import(module))]); } - else if (parameterType is GenericInstanceTypeSignature) - { - // This case (constructed interfaces or delegates) is effectively identical to marshalling 'KeyValuePair<,>' values - body.Instructions.ReferenceReplaceRange(marker, [ - CilInstruction.CreateLdarg(parameterIndex), - new CilInstruction(Call, emitState.LookupTypeDefinition(parameterType, "Marshaller").GetMethod("ConvertToManaged"))]); - } else { - // Get the marshaller type for all other reference types + // Get the marshaller type for all other reference types (including generics) InteropMarshallerType marshallerType = InteropMarshallerTypeResolver.GetMarshallerType(parameterType, interopReferences, emitState); - // Marshal the value and release the original interface pointer + // Marshal the value normally (the caller will own the native resource) body.Instructions.ReferenceReplaceRange(marker, [ CilInstruction.CreateLdarg(parameterIndex), new CilInstruction(Call, marshallerType.ConvertToManaged().Import(module))]); diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs index b9aad7c7d..481be3719 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs @@ -146,16 +146,6 @@ public static void RewriteMethod( new CilInstruction(Call, interopReferences.ExceptionMarshallerConvertToUnmanaged.Import(module)), new CilInstruction(Stobj, interopReferences.AbiException.Import(module).ToTypeDefOrRef())]); } - else if (retValType is GenericInstanceTypeSignature) - { - // For all other generic type instantiations, we use the marshaller in 'WinRT.Interop.dll' - RewriteBody( - body: body, - marker: marker, - marshallerMethod: emitState.LookupTypeDefinition(retValType, "Marshaller").GetMethod("ConvertToUnmanaged"), - interopReferences: interopReferences, - module: module); - } else { // Get the marshaller type for either generic reference types, or all other reference types diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ReturnValue.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ReturnValue.cs index 9dbbe71d2..1e40be5ca 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ReturnValue.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ReturnValue.cs @@ -165,20 +165,6 @@ public static void RewriteMethod( new CilInstruction(Call, interopReferences.ExceptionMarshallerConvertToManaged.Import(module)), new CilInstruction(Ret)]); } - else if (returnType is GenericInstanceTypeSignature) - { - // This case (constructed interfaces or delegates) is effectively identical to marshalling - // 'KeyValuePair<,>' values: the marshalling code will always be in 'WinRT.Interop.dll', the - // ABI type will always just be 'void*', and we will always release the interface pointer. - RewriteBody( - returnType: returnType, - body: body, - marker: marker, - source: source, - marshallerMethod: emitState.LookupTypeDefinition(returnType, "Marshaller").GetMethod("ConvertToManaged"), - releaseOrDisposeMethod: interopReferences.WindowsRuntimeUnknownMarshallerFree, - module: module); - } else { // Get the marshaller type for either generic reference types, or all other reference types From 5de979cd7daf37034a8a319996e3645c275e0867 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 20 Dec 2025 14:19:26 -0800 Subject: [PATCH 7/8] Add CreateStind method for indirect store instructions Introduces the CreateStind extension method to generate the appropriate indirect store (stind) instruction based on the provided type signature. This enhances the CilInstructionExtensions utility for handling various value types and custom types when emitting IL. --- .../Extensions/CilInstructionExtensions.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/WinRT.Interop.Generator/Extensions/CilInstructionExtensions.cs b/src/WinRT.Interop.Generator/Extensions/CilInstructionExtensions.cs index 0f97ba309..140b96d6c 100644 --- a/src/WinRT.Interop.Generator/Extensions/CilInstructionExtensions.cs +++ b/src/WinRT.Interop.Generator/Extensions/CilInstructionExtensions.cs @@ -1,8 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Diagnostics.CodeAnalysis; +using AsmResolver.DotNet; using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables; using static AsmResolver.PE.DotNet.Cil.CilOpCodes; namespace WindowsRuntime.InteropGenerator; @@ -69,5 +73,33 @@ public static CilInstruction CreateLdloc(CilLocalVariable local, CilMethodBody m int i => new CilInstruction(Ldloc, i) }; } + + /// + /// Create a new instruction storing a value indirectly to a target location. + /// + /// The type of value to store. + /// The in use. + /// The instruction. + [SuppressMessage("Style", "IDE0072", Justification = "We use 'stobj' for all other possible types.")] + public static CilInstruction CreateStind(TypeSignature type, ModuleDefinition module) + { + return type.ElementType switch + { + ElementType.Boolean => new CilInstruction(Stind_I1), + ElementType.Char => new CilInstruction(Stind_I2), + ElementType.I1 => new CilInstruction(Stind_I1), + ElementType.U1 => new CilInstruction(Stind_I1), + ElementType.I2 => new CilInstruction(Stind_I2), + ElementType.U2 => new CilInstruction(Stind_I2), + ElementType.I4 => new CilInstruction(Stind_I4), + ElementType.U4 => new CilInstruction(Stind_I4), + ElementType.I8 => new CilInstruction(Stind_I8), + ElementType.U8 => new CilInstruction(Stind_I8), + ElementType.R4 => new CilInstruction(Stind_R4), + ElementType.R8 => new CilInstruction(Stind_R8), + ElementType.ValueType when type.Resolve() is { IsClass: true, IsEnum: true } => new CilInstruction(Stind_I4), + _ => new CilInstruction(Stobj, type.Import(module).ToTypeDefOrRef()), + }; + } } } \ No newline at end of file From cdb9665347bdb995f8ea733ac929f3b2433804b7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 20 Dec 2025 14:19:31 -0800 Subject: [PATCH 8/8] Refactor blittable type store instruction logic Replaces the switch statement for selecting the correct indirect store instruction for blittable types with a call to CilInstruction.CreateStind. This simplifies the code and centralizes the logic for determining the appropriate store instruction. --- .../InteropMethodRewriteFactory.RetVal.cs | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs index 481be3719..f7fea568c 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs @@ -5,7 +5,6 @@ using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Cil; -using AsmResolver.PE.DotNet.Metadata.Tables; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Generation; using WindowsRuntime.InteropGenerator.References; @@ -72,25 +71,7 @@ public static void RewriteMethod( // However, we must use the correct indirect store instruction for primitive types. if (retValType.IsBlittable(interopReferences)) { - CilInstruction storeInstruction = retValType.ElementType switch - { - ElementType.Boolean => new CilInstruction(Stind_I1), - ElementType.Char => new CilInstruction(Stind_I2), - ElementType.I1 => new CilInstruction(Stind_I1), - ElementType.U1 => new CilInstruction(Stind_I1), - ElementType.I2 => new CilInstruction(Stind_I2), - ElementType.U2 => new CilInstruction(Stind_I2), - ElementType.I4 => new CilInstruction(Stind_I4), - ElementType.U4 => new CilInstruction(Stind_I4), - ElementType.I8 => new CilInstruction(Stind_I8), - ElementType.U8 => new CilInstruction(Stind_I8), - ElementType.R4 => new CilInstruction(Stind_R4), - ElementType.R8 => new CilInstruction(Stind_R8), - ElementType.ValueType when retValType.Resolve() is { IsClass: true, IsEnum: true } => new CilInstruction(Stind_I4), - _ => new CilInstruction(Stobj, retValType.Import(module).ToTypeDefOrRef()), - }; - - body.Instructions.ReferenceReplaceRange(marker, [storeInstruction]); + body.Instructions.ReferenceReplaceRange(marker, [CilInstruction.CreateStind(retValType, module)]); } else if (retValType.IsConstructedKeyValuePairType(interopReferences)) {