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 diff --git a/src/WinRT.Interop.Generator/Extensions/WindowsRuntimeExtensions.cs b/src/WinRT.Interop.Generator/Extensions/WindowsRuntimeExtensions.cs index 1ade639d2..3a7805e0e 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: true, 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) diff --git a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.ManagedParameter.cs index e4e1003d0..f4644c925 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). @@ -73,49 +76,26 @@ 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)) { - 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); - - // 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 + // 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, 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 = GetValueTypeMarshallerType(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)])); + // 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 body.Instructions.ReferenceReplaceRange(marker, [ CilInstruction.CreateLdarg(parameterIndex), - new CilInstruction(Call, marshallerMethod.Import(module))]); + new CilInstruction(Call, marshallerType.ConvertToManaged().Import(module))]); } } else if (parameterType.IsTypeOfString()) @@ -125,29 +105,15 @@ 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 - ITypeDefOrRef marshallerType = GetReferenceTypeMarshallerType(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()])); + // 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, 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 71acbfd64..29ac2bb6a 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,17 +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); - - // 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, @@ -119,7 +110,7 @@ public static void RewriteMethod( loadMarker: loadMarker, finallyMarker: finallyMarker, parameterIndex: parameterIndex, - marshallerMethod: marshallerMethod, + marshallerMethod: marshallerType.BoxToUnmanaged(), disposeMethod: null, interopReferences: interopReferences, module: module); @@ -127,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 = GetValueTypeMarshallerType(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, @@ -150,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); } @@ -189,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 = GetReferenceTypeMarshallerType(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, @@ -205,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 6188ebdc9..f7fea568c 100644 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs +++ b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.RetVal.cs @@ -5,10 +5,10 @@ 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; +using WindowsRuntime.InteropGenerator.Resolvers; using static AsmResolver.PE.DotNet.Cil.CilOpCodes; #pragma warning disable CS1573, IDE0072 @@ -71,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)) { @@ -103,42 +85,24 @@ 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); - - // 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 = GetValueTypeMarshallerType(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())]); } } @@ -163,33 +127,16 @@ 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 - ITypeDefOrRef marshallerType = GetReferenceTypeMarshallerType(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 d0e1e6785..1e40be5ca 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,21 +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); - - // 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( @@ -112,7 +99,7 @@ public static void RewriteMethod( body: body, marker: marker, source: source, - marshallerMethod: marshallerMethod, + marshallerMethod: marshallerType.UnboxToManaged(), releaseOrDisposeMethod: interopReferences.WindowsRuntimeUnknownMarshallerFree, module: module); } @@ -121,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 = GetValueTypeMarshallerType(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( @@ -143,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 = GetValueTypeMarshallerType(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)]); } } @@ -199,31 +165,10 @@ 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 - ITypeDefOrRef marshallerType = GetReferenceTypeMarshallerType(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( @@ -231,7 +176,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/Factories/InteropMethodRewriteFactory.cs b/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.cs deleted file mode 100644 index 39db3a7a5..000000000 --- a/src/WinRT.Interop.Generator/Factories/InteropMethodRewriteFactory.cs +++ /dev/null @@ -1,105 +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'. - if (type.IsFundamentalWindowsRuntimeType(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}", - 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.IsTypeOfType(interopReferences) || - type.IsTypeOfException(interopReferences) || - type.IsCustomMappedWindowsRuntimeNonGenericInterfaceType(interopReferences) || - type.IsCustomMappedWindowsRuntimeNonGenericDelegateType(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/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 new file mode 100644 index 000000000..09f607161 --- /dev/null +++ b/src/WinRT.Interop.Generator/Resolvers/InteropMarshallerTypeResolver.cs @@ -0,0 +1,79 @@ +// 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 +{ + /// + /// 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 InteropMarshallerType 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'. + 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 new(type, interopReferences, 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)) + { + 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"); + + return new(type, interopReferences, marshallerType); + } + } +} 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; + } } ///