Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6128fd8
Add IID for IMapChangedEventArgs1 interface
Sergio0694 Dec 18, 2025
b1a205f
Refactor delegate invoke method body construction
Sergio0694 Dec 18, 2025
f646f95
Refactor delegate exception handling labels
Sergio0694 Dec 18, 2025
8cd7a16
Inline ldloca_0_invoke instruction in delegate builder
Sergio0694 Dec 18, 2025
34d21e3
Add NativeParameter marshalling logic to Interop factory
Sergio0694 Dec 18, 2025
b03e88a
Handle Exception type marshalling in native parameter rewrite
Sergio0694 Dec 19, 2025
8d68617
Add IsTypeOfVoidPointer method to WindowsRuntimeExtensions
Sergio0694 Dec 19, 2025
291aed7
Implement marshalling logic for native parameters
Sergio0694 Dec 19, 2025
7fc9a94
Add NativeParameter class for method rewrite info
Sergio0694 Dec 19, 2025
4f39553
Add support for native parameter method rewrite
Sergio0694 Dec 19, 2025
e062412
Add tracking for native parameter method rewrites
Sergio0694 Dec 19, 2025
f513211
Add emitState tracking to NativeDelegateType method
Sergio0694 Dec 19, 2025
dfc88a9
Add IListExtensions with ReferenceIndexOf method
Sergio0694 Dec 19, 2025
0f1762a
Add ReferenceContains method to IListExtensions
Sergio0694 Dec 19, 2025
5f04a0f
Use reference-based collection methods for instruction checks
Sergio0694 Dec 19, 2025
f515f47
Add ReferenceRemove and ReferenceRemoveRange extensions
Sergio0694 Dec 19, 2025
858c805
Rename ReplaceRange to ReferenceReplaceRange in instruction helpers
Sergio0694 Dec 19, 2025
ab81c4b
Refactor MethodRewriteInfo comparison logic
Sergio0694 Dec 19, 2025
3d785b5
Refactor vtable sharing checks to use ABI type void*
Sergio0694 Dec 19, 2025
2db1e12
Add HasReferenceAbiType method to WindowsRuntimeExtensions
Sergio0694 Dec 19, 2025
bbcd2df
Refactor ABI type checks to use HasReferenceAbiType
Sergio0694 Dec 19, 2025
05c43bd
Refactor delegate vtable creation for custom types
Sergio0694 Dec 19, 2025
9d58b6e
Rename sharedReadOnlyDictionaryType to sharedDictionaryType
Sergio0694 Dec 19, 2025
368395c
Add shared vtable type support for delegate interop
Sergio0694 Dec 19, 2025
90aff58
Pass vftblType to Delegate.ImplType method
Sergio0694 Dec 19, 2025
1e39832
Clarify XML doc references to AsmResolver.DotNet.TypeReference
Sergio0694 Dec 20, 2025
fca3f01
Handle Type parameters in interop method rewriting
Sergio0694 Dec 20, 2025
354f8ac
Add references for Nullable<int> and HString marshalling
Sergio0694 Dec 20, 2025
7299097
Handle string parameters in interop method rewriting
Sergio0694 Dec 20, 2025
5715fdd
Fix incorrect value type for dictionaries
Sergio0694 Dec 20, 2025
138ef25
Update XML docs for ABI type methods
Sergio0694 Dec 20, 2025
da42a7b
Update marshalling and pragma warning handling
Sergio0694 Jan 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ public static void Vftbl(
TypeSignature keyType = dictionaryType.TypeArguments[0];
TypeSignature valueType = dictionaryType.TypeArguments[1];

bool isKeyReferenceType = !keyType.IsValueType || keyType.IsConstructedKeyValuePairType(interopReferences);
bool isValueReferenceType = !valueType.IsValueType || valueType.IsConstructedKeyValuePairType(interopReferences);
bool isKeyReferenceType = keyType.HasReferenceAbiType(interopReferences);
bool isValueReferenceType = valueType.HasReferenceAbiType(interopReferences);

// We can share the vtable type for 'void*' when both key and value types are reference types
if (isKeyReferenceType && isValueReferenceType)
Expand Down Expand Up @@ -135,14 +135,14 @@ static void GetOrCreateVftbl(
}

// Create a dummy signature just to generate the mangled name for the vtable type
TypeSignature sharedReadOnlyDictionaryType = interopReferences.IDictionary2.MakeGenericReferenceType(
TypeSignature sharedDictionaryType = interopReferences.IDictionary2.MakeGenericReferenceType(
displayKeyType,
displayValueType);

// Construct a new specialized vtable type
TypeDefinition newVftblType = WellKnownTypeDefinitionFactory.IDictionary2Vftbl(
ns: InteropUtf8NameFactory.TypeNamespace(sharedReadOnlyDictionaryType),
name: InteropUtf8NameFactory.TypeName(sharedReadOnlyDictionaryType, "Vftbl"),
ns: InteropUtf8NameFactory.TypeNamespace(sharedDictionaryType),
name: InteropUtf8NameFactory.TypeName(sharedDictionaryType, "Vftbl"),
keyType: keyType,
valueType: valueType,
interopReferences: interopReferences,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,8 @@ public static void Vftbl(
{
TypeSignature elementType = listType.TypeArguments[0];

// All reference types can share the same vtable type (as it just uses 'void*' for the ABI type).
// We can also share vtables for 'KeyValuePair<,>' types, as their ABI type is an interface.
if (!elementType.IsValueType || elementType.IsConstructedKeyValuePairType(interopReferences))
// For types which use 'void*' as their ABI types, we can share the same vtable type definition
if (elementType.HasReferenceAbiType(interopReferences))
{
vftblType = interopDefinitions.IList1Vftbl;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public static void Vftbl(
// All reference types can share the same vtable type (as it just uses 'void*' for the ABI type).
// The 'IMapView<K, V>' interface doesn't use 'V' as a by-value parameter anywhere in the vtable,
// so we can aggressively share vtable types for all cases where 'K' is a reference type.
if (!keyType.IsValueType || keyType.IsConstructedKeyValuePairType(interopReferences))
if (keyType.HasReferenceAbiType(interopReferences))
{
vftblType = interopDefinitions.IReadOnlyDictionary2Vftbl;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static void Vftbl(
TypeSignature elementType = readOnlyListType.TypeArguments[0];

// Same logic as with 'IList1.Vftbl' (i.e. share for all reference types)
if (!elementType.IsValueType || elementType.IsConstructedKeyValuePairType(interopReferences))
if (elementType.HasReferenceAbiType(interopReferences))
{
vftblType = interopDefinitions.IReadOnlyList1Vftbl;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,34 @@ internal static class CilInstructionCollectionExtensions
extension(CilInstructionCollection instructions)
{
/// <summary>
/// Replaces a target instruction with a collection of new instructions.
/// Removes a set of CIL instructions from the collection.
/// </summary>
/// <param name="target">The instruction to replace.</param>
/// <param name="values">The new instructions to emit.</param>
public void ReplaceRange(CilInstruction target, params IEnumerable<CilInstruction> values)
/// <param name="items">The instructions to remove.</param>
public void ReferenceRemoveRange(params IEnumerable<CilInstruction> items)
{
int index;

// Find the index of the target instruction in the collection.
// We can't use 'IndexOf', as we only want to match by reference.
for (index = 0; index < instructions.Count; index++)
foreach (CilInstruction item in items)
{
if (instructions[index] == target)
{
break;
}
_ = instructions.ReferenceRemove(item);
}
}

/// <summary>
/// Replaces a target instruction with a collection of new instructions.
/// </summary>
/// <param name="target">The instruction to replace.</param>
/// <param name="items">The new instructions to emit.</param>
public void ReferenceReplaceRange(CilInstruction target, params IEnumerable<CilInstruction> items)
{
int index = instructions.ReferenceIndexOf(target);

// Ensure we did find the target instruction
if (index >= instructions.Count)
if (index == -1)
{
throw new ArgumentException("The target instruction was not found in the collection.", nameof(target));
}

instructions.RemoveAt(index);
instructions.InsertRange(index, values);
instructions.InsertRange(index, items);
}
}
}
60 changes: 60 additions & 0 deletions src/WinRT.Interop.Generator/Extensions/IListExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;

namespace WindowsRuntime.InteropGenerator;

/// <summary>
/// Extensions for the <see cref="IList{T}"/> type.
/// </summary>
internal static class IListExtensions
{
extension<T>(IList<T> list)
where T : class
{
/// <inheritdoc cref="List{T}.Contains(T)"/>
/// <remarks>
/// This method only ever compares values by reference equality.
/// </remarks>
public bool ReferenceContains(T value)
{
return list.Count != 0 && list.ReferenceIndexOf(value) >= 0;
}

/// <inheritdoc cref="List{T}.Remove(T)"/>
/// <remarks>
/// This method only ever compares values by reference equality.
/// </remarks>
public bool ReferenceRemove(T value)
{
int index = list.ReferenceIndexOf(value);

if (index >= 0)
{
list.RemoveAt(index);

return true;
}

return false;
}

/// <inheritdoc cref="IList{T}.IndexOf(T)"/>
/// <remarks>
/// This method only ever compares values by reference equality.
/// </remarks>
public int ReferenceIndexOf(T value)
{
for (int i = 0; i < list.Count; i++)
{
if (ReferenceEquals(list[i], value))
{
return i;
}
}

return -1;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ public bool IsTypeOfObject()
return type is CorLibTypeSignature { ElementType: ElementType.Object };
}

/// <summary>
/// Checks whether an <see cref="ITypeDescriptor"/> is a <see cref="void"/> pointer type.
/// </summary>
/// <returns>Whether the type is a <see cref="void"/> pointer type.</returns>
public bool IsTypeOfVoidPointer()
{
return type is PointerTypeSignature { BaseType: CorLibTypeSignature { ElementType: ElementType.Void } };
}

/// <summary>
/// Checks whether an <see cref="ITypeDescriptor"/> represents a fundamental Windows Runtime type.
/// </summary>
Expand Down Expand Up @@ -477,11 +486,46 @@ public bool IsTrackerSupportRequired(InteropReferences interopReferences)
return false;
}

/// <summary>
/// Gets whether a given type has an ABI type that is a reference type.
/// </summary>
/// <param name="interopReferences">The <see cref="InteropReferences"/> instance to use.</param>
/// <returns>Whether the input type has an ABI type that is a reference type.</returns>
public bool HasReferenceAbiType(InteropReferences interopReferences)
{
// All constructed generics will use 'void*' for the ABI type
if (type is GenericInstanceTypeSignature)
{
return true;
}

// All other value types will never have a reference type as the ABI type
if (type.IsValueType)
{
return false;
}

// 'Type' is a class, but is custom-mapped to the 'TypeName' struct type
if (SignatureComparer.IgnoreVersion.Equals(type, interopReferences.Type))
{
return false;
}

// 'Exception' is also a class, but is custom-mapped to the 'HResult' struct type
if (SignatureComparer.IgnoreVersion.Equals(type, interopReferences.Exception))
{
return false;
}

// For all other cases (e.g. interfaces, classes, delegates, etc.), the ABI type is always a pointer
return true;
}

/// <summary>
/// Gets the ABI type for a given type.
/// </summary>
/// <param name="interopReferences">The <see cref="InteropReferences"/> instance to use.</param>
/// <returns>The ABi type for the input type.</returns>
/// <returns>The ABI type for the input type.</returns>
public TypeSignature GetAbiType(InteropReferences interopReferences)
{
// All constructed generics will use 'void*' for the ABI type. This applies to both reference
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,12 @@ public static void RewriteMethod(
}

// If we didn't find the marker, it means the target method is either invalid
if (!body.Instructions.Contains(marker))
if (!body.Instructions.ReferenceContains(marker))
{
throw WellKnownInteropExceptions.MethodRewriteMarkerInstructionNotFoundError(marker, method);
}

// If we didn't find the marker, it means the target method is either invalid, or the
// supplied marker was incorrect (or the caller forgot to add it to the method body).
// Validate that the target parameter index is in range
if ((uint)parameterIndex >= method.Parameters.Count)
{
throw WellKnownInteropExceptions.MethodRewriteParameterIndexNotValidError(parameterIndex, method);
Expand All @@ -74,12 +73,12 @@ public static void RewriteMethod(
// If the return type is blittable, we can just load it directly (simplest case)
if (parameterType.IsBlittable(interopReferences))
{
body.Instructions.ReplaceRange(marker, CilInstruction.CreateLdarg(parameterIndex));
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.ReplaceRange(marker, [
body.Instructions.ReferenceReplaceRange(marker, [
CilInstruction.CreateLdarg(parameterIndex),
new CilInstruction(Call, emitState.LookupTypeDefinition(parameterType, "Marshaller").GetMethod("ConvertToManaged"))]);
}
Expand All @@ -98,7 +97,7 @@ public static void RewriteMethod(
parameterTypes: [module.CorLibTypeFactory.Void.MakePointerType()]));

// Emit code similar to 'KeyValuePair<,>' above, to marshal the resulting 'Nullable<T>' value
body.Instructions.ReplaceRange(marker, [
body.Instructions.ReferenceReplaceRange(marker, [
CilInstruction.CreateLdarg(parameterIndex),
new CilInstruction(Call, marshallerMethod.Import(module))]);
}
Expand All @@ -116,29 +115,29 @@ public static void RewriteMethod(
parameterTypes: [parameterType.GetAbiType(interopReferences)]));

// We can directly call the marshaller and return it, no 'try/finally' complexity is needed
body.Instructions.ReplaceRange(marker, [
body.Instructions.ReferenceReplaceRange(marker, [
CilInstruction.CreateLdarg(parameterIndex),
new CilInstruction(Call, marshallerMethod.Import(module))]);
}
}
else if (parameterType.IsTypeOfString())
{
// When marshalling 'string' values, we must use 'HStringMarshaller' (the ABI type is not actually a COM object)
body.Instructions.ReplaceRange(marker, [
body.Instructions.ReferenceReplaceRange(marker, [
CilInstruction.CreateLdarg(parameterIndex),
new CilInstruction(Call, interopReferences.HStringMarshallerConvertToManaged.Import(module))]);
}
else if (parameterType.IsTypeOfType(interopReferences))
{
// When marshalling 'Type' values, we must use 'TypeMarshaller' (the ABI type is a value type)
body.Instructions.ReplaceRange(marker, [
body.Instructions.ReferenceReplaceRange(marker, [
CilInstruction.CreateLdarg(parameterIndex),
new CilInstruction(Call, interopReferences.TypeMarshallerConvertToManaged.Import(module))]);
}
else if (parameterType is GenericInstanceTypeSignature)
{
// This case (constructed interfaces or delegates) is effectively identical to marshalling 'KeyValuePair<,>' values
body.Instructions.ReplaceRange(marker, [
body.Instructions.ReferenceReplaceRange(marker, [
CilInstruction.CreateLdarg(parameterIndex),
new CilInstruction(Call, emitState.LookupTypeDefinition(parameterType, "Marshaller").GetMethod("ConvertToManaged"))]);
}
Expand All @@ -155,7 +154,7 @@ public static void RewriteMethod(
parameterTypes: [module.CorLibTypeFactory.Void.MakePointerType()]));

// Marshal the value and release the original interface pointer
body.Instructions.ReplaceRange(marker, [
body.Instructions.ReferenceReplaceRange(marker, [
CilInstruction.CreateLdarg(parameterIndex),
new CilInstruction(Call, marshallerMethod.Import(module))]);
}
Expand Down
Loading