Skip to content

Commit 51614b6

Browse files
authored
Handle struct returns for COM interface methods across all marshalling modes (#1536)
1 parent 89950cf commit 51614b6

File tree

17 files changed

+691
-121
lines changed

17 files changed

+691
-121
lines changed

src/Microsoft.Windows.CsWin32/Generator.Com.cs

Lines changed: 118 additions & 20 deletions
Large diffs are not rendered by default.

src/Microsoft.Windows.CsWin32/Generator.CustomMarshaler.cs renamed to src/Microsoft.Windows.CsWin32/Generator.CustomMarshaller.cs

Lines changed: 75 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Microsoft.Windows.CsWin32;
55

66
public partial class Generator
77
{
8-
internal string RequestCustomEnumMarshaler(string qualifiedEnumTypeName, UnmanagedType unmanagedType)
8+
internal string RequestCustomEnumMarshaller(string qualifiedEnumTypeName, UnmanagedType unmanagedType)
99
{
1010
// Create type syntax for the unmanaged type (uint for U4)
1111
TypeSyntax unmanagedTypeSyntax = unmanagedType switch
@@ -22,12 +22,12 @@ internal string RequestCustomEnumMarshaler(string qualifiedEnumTypeName, Unmanag
2222
throw new InvalidOperationException($"This generator doesn't share a prefix with this enum {qualifiedEnumTypeName}");
2323
}
2424

25-
// Custom marshalers should go in a InteropServices sub-namespace.
25+
// Custom marshallers should go in a InteropServices sub-namespace.
2626
shortNamespace += ".InteropServices";
2727

28-
string customTypeMarshalerName = $"{enumTypeName}To{unmanagedType}Marshaler";
28+
string customTypeMarshallerName = $"{enumTypeName}To{unmanagedType}Marshaller";
2929

30-
return this.volatileCode.GenerateCustomTypeMarshaler(customTypeMarshalerName, delegate
30+
return this.volatileCode.GenerateCustomTypeMarshaller(customTypeMarshallerName, delegate
3131
{
3232
// Create type syntax for the enum type
3333
TypeSyntax enumTypeSyntax = IdentifierName(enumTypeName);
@@ -57,7 +57,7 @@ internal string RequestCustomEnumMarshaler(string qualifiedEnumTypeName, Unmanag
5757
SyntaxKind.SimpleMemberAccessExpression,
5858
ParseName("global::System.Runtime.InteropServices.Marshalling.MarshalMode"),
5959
IdentifierName(mode))),
60-
AttributeArgument(TypeOfExpression(IdentifierName(customTypeMarshalerName)))))
60+
AttributeArgument(TypeOfExpression(IdentifierName(customTypeMarshallerName)))))
6161
.ToArray();
6262

6363
// Create ConvertToManaged method
@@ -92,24 +92,78 @@ internal string RequestCustomEnumMarshaler(string qualifiedEnumTypeName, Unmanag
9292
.WithBody(Block()); // Empty body
9393

9494
// Create the class declaration
95-
ClassDeclarationSyntax marshalerClass = ClassDeclaration(Identifier(customTypeMarshalerName))
95+
ClassDeclarationSyntax marshallerClass = ClassDeclaration(Identifier(customTypeMarshallerName))
9696
.AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.StaticKeyword))
9797
.AddAttributeLists(customMarshallerAttributes.Select(attr => AttributeList().AddAttributes(attr)).ToArray())
9898
.AddMembers(convertToManagedMethod, convertToUnmanagedMethod, freeMethod);
9999

100-
marshalerClass = marshalerClass.WithAdditionalAnnotations(new SyntaxAnnotation(NamespaceContainerAnnotation, shortNamespace));
100+
marshallerClass = marshallerClass.WithAdditionalAnnotations(new SyntaxAnnotation(NamespaceContainerAnnotation, shortNamespace));
101101

102-
string qualifiedName = $"global::{this.Namespace}.{shortNamespace}.{customTypeMarshalerName}";
102+
string qualifiedName = $"global::{this.Namespace}.{shortNamespace}.{customTypeMarshallerName}";
103103

104-
CustomMarshalerTypeRecord typeRecord = new(marshalerClass, qualifiedName);
104+
CustomMarshallerTypeRecord typeRecord = new(marshallerClass, qualifiedName);
105105

106-
this.volatileCode.AddCustomTypeMarshaler(customTypeMarshalerName, typeRecord);
106+
this.volatileCode.AddCustomTypeMarshaller(customTypeMarshallerName, typeRecord);
107107

108108
return typeRecord;
109109
});
110110
}
111111

112-
internal string RequestCustomWinRTMarshaler(string qualifiedWinRTTypeName)
112+
internal string RequestCustomTypeDefMarshaller(string fullyQualifiedTypeName, TypeSyntax unmanagedTypeSyntax)
113+
{
114+
if (!TrySplitPossiblyQualifiedName(fullyQualifiedTypeName, out string? @namespace, out string typeDefName) ||
115+
!this.TryStripCommonNamespace(@namespace, out string? shortNamespace))
116+
{
117+
throw new InvalidOperationException($"This generator doesn't share a prefix with this enum {fullyQualifiedTypeName}");
118+
}
119+
120+
// Custom marshallers should go in a InteropServices sub-namespace.
121+
shortNamespace += ".InteropServices";
122+
123+
string customTypeMarshallerName = $"{typeDefName}Marshaller";
124+
125+
return this.volatileCode.GenerateCustomTypeMarshaller(customTypeMarshallerName, delegate
126+
{
127+
// Type syntax for the typedef type (unqualified within its namespace container).
128+
TypeSyntax typedefTypeSyntax = ParseName(fullyQualifiedTypeName);
129+
130+
// Single CustomMarshaller attribute for MarshalMode.Default.
131+
AttributeSyntax attribute = Attribute(ParseName("global::System.Runtime.InteropServices.Marshalling.CustomMarshaller"))
132+
.AddArgumentListArguments(
133+
AttributeArgument(TypeOfExpression(typedefTypeSyntax)),
134+
AttributeArgument(MemberAccessExpression(
135+
SyntaxKind.SimpleMemberAccessExpression,
136+
ParseName("global::System.Runtime.InteropServices.Marshalling.MarshalMode"),
137+
IdentifierName("Default"))),
138+
AttributeArgument(TypeOfExpression(IdentifierName(customTypeMarshallerName))));
139+
140+
// public static unsafe void* ConvertToUnmanaged(HWND managed) => managed.Value;
141+
MethodDeclarationSyntax toUnmanaged = MethodDeclaration(unmanagedTypeSyntax, Identifier("ConvertToUnmanaged"))
142+
.AddModifiers(TokenWithSpace(SyntaxKind.PublicKeyword), TokenWithSpace(SyntaxKind.StaticKeyword), TokenWithSpace(SyntaxKind.UnsafeKeyword))
143+
.AddParameterListParameters(Parameter(Identifier("managed")).WithType(typedefTypeSyntax.WithTrailingTrivia(Space)))
144+
.WithBody(Block(ReturnStatement(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName("managed"), IdentifierName("Value")))));
145+
146+
// public static unsafe HWND ConvertToManaged(void* unmanaged) => new(unmanaged);
147+
MethodDeclarationSyntax toManaged = MethodDeclaration(typedefTypeSyntax, Identifier("ConvertToManaged"))
148+
.AddModifiers(TokenWithSpace(SyntaxKind.PublicKeyword), TokenWithSpace(SyntaxKind.StaticKeyword), TokenWithSpace(SyntaxKind.UnsafeKeyword))
149+
.AddParameterListParameters(Parameter(Identifier("unmanaged")).WithType(unmanagedTypeSyntax.WithTrailingTrivia(Space)))
150+
.WithBody(Block(ReturnStatement(ObjectCreationExpression(typedefTypeSyntax)
151+
.WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(IdentifierName("unmanaged"))))))));
152+
153+
ClassDeclarationSyntax marshallerClass = ClassDeclaration(Identifier(customTypeMarshallerName))
154+
.AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.StaticKeyword))
155+
.AddAttributeLists(AttributeList().AddAttributes(attribute))
156+
.AddMembers(toUnmanaged, toManaged)
157+
.WithAdditionalAnnotations(new SyntaxAnnotation(NamespaceContainerAnnotation, shortNamespace));
158+
159+
string qualifiedName = $"global::{this.Namespace}.{shortNamespace}.{customTypeMarshallerName}";
160+
CustomMarshallerTypeRecord typeRecord = new(marshallerClass, qualifiedName);
161+
this.volatileCode.AddCustomTypeMarshaller(customTypeMarshallerName, typeRecord);
162+
return typeRecord;
163+
});
164+
}
165+
166+
internal string RequestCustomWinRTMarshaller(string qualifiedWinRTTypeName)
113167
{
114168
if (!TrySplitPossiblyQualifiedName(qualifiedWinRTTypeName, out string? @namespace, out string winrtTypeName))
115169
{
@@ -118,11 +172,11 @@ internal string RequestCustomWinRTMarshaler(string qualifiedWinRTTypeName)
118172
@namespace = string.Empty;
119173
}
120174

121-
string customTypeMarshalerName = $"WinRTMarshaler{winrtTypeName}";
175+
string customTypeMarshallerName = $"WinRTMarshaller{winrtTypeName}";
122176

123-
string marshalerNamespace = "CsWin32.InteropServices";
177+
string marshallerNamespace = "CsWin32.InteropServices";
124178

125-
return this.volatileCode.GenerateCustomTypeMarshaler(customTypeMarshalerName, delegate
179+
return this.volatileCode.GenerateCustomTypeMarshaller(customTypeMarshallerName, delegate
126180
{
127181
// Create type syntax for the WinRT type (using the qualified name)
128182
TypeSyntax winrtTypeSyntax = string.IsNullOrEmpty(@namespace)
@@ -151,7 +205,7 @@ internal string RequestCustomWinRTMarshaler(string qualifiedWinRTTypeName)
151205
SyntaxKind.SimpleMemberAccessExpression,
152206
ParseName("global::System.Runtime.InteropServices.Marshalling.MarshalMode"),
153207
IdentifierName(mode))),
154-
AttributeArgument(TypeOfExpression(IdentifierName(customTypeMarshalerName)))))
208+
AttributeArgument(TypeOfExpression(IdentifierName(customTypeMarshallerName)))))
155209
.ToArray();
156210

157211
// Create ConvertToManaged method
@@ -206,24 +260,24 @@ internal string RequestCustomWinRTMarshaler(string qualifiedWinRTTypeName)
206260
ArgumentList().AddArguments(Argument(IdentifierName("unmanaged")))))));
207261

208262
// Create the class declaration
209-
ClassDeclarationSyntax marshalerClass = ClassDeclaration(Identifier(customTypeMarshalerName))
263+
ClassDeclarationSyntax marshallerClass = ClassDeclaration(Identifier(customTypeMarshallerName))
210264
.AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.StaticKeyword))
211265
.AddAttributeLists(customMarshallerAttributes.Select(attr => AttributeList().AddAttributes(attr)).ToArray())
212266
.AddMembers(convertToManagedMethod, convertToUnmanagedMethod, freeMethod);
213267

214-
marshalerClass = marshalerClass.WithAdditionalAnnotations(new SyntaxAnnotation(NamespaceContainerAnnotation, marshalerNamespace));
268+
marshallerClass = marshallerClass.WithAdditionalAnnotations(new SyntaxAnnotation(NamespaceContainerAnnotation, marshallerNamespace));
215269

216-
string qualifiedName = $"global::{this.Namespace}.{marshalerNamespace}.{customTypeMarshalerName}";
270+
string qualifiedName = $"global::{this.Namespace}.{marshallerNamespace}.{customTypeMarshallerName}";
217271

218-
CustomMarshalerTypeRecord typeRecord = new(marshalerClass, qualifiedName);
272+
CustomMarshallerTypeRecord typeRecord = new(marshallerClass, qualifiedName);
219273

220274
// For WinRT types, we generally don't need a specific namespace container annotation
221275
// since they're typically in the global namespace context
222-
this.volatileCode.AddCustomTypeMarshaler(customTypeMarshalerName, typeRecord);
276+
this.volatileCode.AddCustomTypeMarshaller(customTypeMarshallerName, typeRecord);
223277

224278
return typeRecord;
225279
});
226280
}
227281

228-
private record CustomMarshalerTypeRecord(ClassDeclarationSyntax ClassDeclaration, string QualifiedName);
282+
private record CustomMarshallerTypeRecord(ClassDeclarationSyntax ClassDeclaration, string QualifiedName);
229283
}

src/Microsoft.Windows.CsWin32/Generator.Features.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public partial class Generator
2121
private readonly bool overloadResolutionPriorityAttributePredefined;
2222
private readonly bool unscopedRefAttributePredefined;
2323
private readonly bool canUseComVariant;
24+
private readonly bool canUseMemberFunctionCallingConvention;
2425
private readonly INamedTypeSymbol? runtimeFeatureClass;
2526
private readonly bool generateSupportedOSPlatformAttributes;
2627
private readonly bool generateSupportedOSPlatformAttributesOnInterfaces; // only supported on net6.0 (https://github.com/dotnet/runtime/pull/48838)

0 commit comments

Comments
 (0)