Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 2 additions & 5 deletions src/Microsoft.Windows.CsWin32/Generator.Com.cs
Original file line number Diff line number Diff line change
Expand Up @@ -714,11 +714,8 @@ static ExpressionSyntax ThisPointer(PointerTypeSyntax? typedPointer = null)
return typedPointer is not null ? CastExpression(typedPointer, invocation) : invocation;
}

// Add helper methods when appropriate.
if (hasIUnknownMembers && this.Options.FriendlyOverloads.Enabled)
{
members.AddRange(this.ExtractMembersFromTemplate("IUnknownHelperMethods"));
}
// The IID_PPV_ARGS pattern in DeclareFriendlyOverload now handles QueryInterface<T> generically,
// so the IUnknownHelperMethods template is no longer needed.

// We expose the vtbl struct to support CCWs.
IdentifierNameSyntax vtblStructName = IdentifierName("Vtbl");
Expand Down
147 changes: 137 additions & 10 deletions src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.Windows.CsWin32;
Expand Down Expand Up @@ -75,17 +75,14 @@ private IEnumerable<MethodDeclarationSyntax> DeclareFriendlyOverloads(MethodDefi
yield return (MethodDeclarationSyntax)templateFriendlyOverload;
}

if (externMethodDeclaration.Identifier.ValueText != "CoCreateInstance" || !this.options.ComInterop.UseIntPtrForComOutPointers)
if (this.options.AllowMarshaling && this.TryFetchTemplate("marshaling/" + externMethodDeclaration.Identifier.ValueText, out templateFriendlyOverload))
{
if (this.options.AllowMarshaling && this.TryFetchTemplate("marshaling/" + externMethodDeclaration.Identifier.ValueText, out templateFriendlyOverload))
{
yield return (MethodDeclarationSyntax)templateFriendlyOverload;
}
yield return (MethodDeclarationSyntax)templateFriendlyOverload;
}

if (!this.options.AllowMarshaling && this.TryFetchTemplate("no_marshaling/" + externMethodDeclaration.Identifier.ValueText, out templateFriendlyOverload))
{
yield return (MethodDeclarationSyntax)templateFriendlyOverload;
}
if (!this.options.AllowMarshaling && this.TryFetchTemplate("no_marshaling/" + externMethodDeclaration.Identifier.ValueText, out templateFriendlyOverload))
{
yield return (MethodDeclarationSyntax)templateFriendlyOverload;
}

bool improvePointersToSpansAndRefs = this.canUseSpan;
Expand Down Expand Up @@ -153,6 +150,51 @@ private IEnumerable<MethodDeclarationSyntax> DeclareFriendlyOverload(
SyntaxToken friendlyMethodName = externMethodDeclaration.Identifier;
bool emulateMemberFunctionCallConv = friendlyMethodName.ValueText.EndsWith(EmulateMemberFunctionCallConvSuffix);

// Pre-scan for IID_PPV_ARGS pattern: a Guid* [In] parameter immediately followed by a void** [ComOutPtr] parameter.
int iidPpvRiidOrigIndex = -1;
int iidPpvPpvOrigIndex = -1;
bool iidPpvMarshalingMode = false;

if (this.options.FriendlyOverloads.ComOutPtrGenericOverloads)
{
var metadataParamsForScan = new List<(Parameter Param, int OrigIndex)>();
foreach (ParameterHandle ph in methodDefinition.GetParameters())
{
Parameter p = this.Reader.GetParameter(ph);
if (p.SequenceNumber > 0 && p.SequenceNumber - 1 < originalSignature.ParameterTypes.Length)
{
metadataParamsForScan.Add((p, p.SequenceNumber - 1));
}
}

// Only match when the Guid* + void** [ComOutPtr] pair are the final two parameters (the canonical IID_PPV_ARGS position).
if (metadataParamsForScan.Count >= 2)
{
int i = metadataParamsForScan.Count - 2;
int riidOrig = metadataParamsForScan[i].OrigIndex;
int ppvOrig = metadataParamsForScan[i + 1].OrigIndex;

if (ppvOrig == riidOrig + 1
&& originalSignature.ParameterTypes[riidOrig] is PointerTypeHandleInfo { ElementType: HandleTypeHandleInfo guidInfo }
&& guidInfo.IsType("Guid")
&& this.FindInteropDecorativeAttribute(metadataParamsForScan[i + 1].Param.GetCustomAttributes(), "ComOutPtrAttribute") is not null
&& originalSignature.ParameterTypes[ppvOrig] is PointerTypeHandleInfo { ElementType: PointerTypeHandleInfo { ElementType: PrimitiveTypeHandleInfo { PrimitiveTypeCode: PrimitiveTypeCode.Void } } }
&& riidOrig < parameters.Count && ppvOrig < parameters.Count)
{
ParameterSyntax ppvExtern = externMethodDeclaration.ParameterList.Parameters[ppvOrig];

// Skip if ppv is typed as IntPtr (UseIntPtrForComOutPointers mode).
if (ppvExtern.Type is not IdentifierNameSyntax { Identifier.ValueText: nameof(IntPtr) })
{
iidPpvRiidOrigIndex = riidOrig;
iidPpvPpvOrigIndex = ppvOrig;
iidPpvMarshalingMode = ppvExtern.Modifiers.Any(SyntaxKind.OutKeyword)
&& ppvExtern.Type is PredefinedTypeSyntax { Keyword.RawKind: (int)SyntaxKind.ObjectKeyword };
}
}
}
}

foreach (ParameterHandle paramHandle in methodDefinition.GetParameters())
{
Parameter param = this.Reader.GetParameter(paramHandle);
Expand All @@ -175,6 +217,80 @@ private IEnumerable<MethodDeclarationSyntax> DeclareFriendlyOverload(
paramIndex++;
}

// Handle IID_PPV_ARGS pattern: riid parameter is removed, ppv parameter is genericized.
if (origParamIndex == iidPpvRiidOrigIndex)
{
signatureChanged = true;
ParameterSyntax riidExternParam = externMethodDeclaration.ParameterList.Parameters[origParamIndex];
ExpressionSyntax typeofTGuid = MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
TypeOfExpression(IdentifierName("T")),
IdentifierName("GUID"));

if (riidExternParam.Type is PointerTypeSyntax)
{
leadingStatements.Add(LocalDeclarationStatement(
VariableDeclaration(
ParseTypeName("global::System.Guid"),
[VariableDeclarator(Identifier("__riid"), EqualsValueClause(typeofTGuid))])));
arguments[paramIndex] = Argument(PrefixUnaryExpression(SyntaxKind.AddressOfExpression, IdentifierName("__riid")));
}
else
{
arguments[paramIndex] = Argument(typeofTGuid);
}

parametersToRemove.Add(paramIndex);
continue;
}

if (origParamIndex == iidPpvPpvOrigIndex)
{
signatureChanged = true;
IdentifierNameSyntax tName = IdentifierName("T");

if (iidPpvMarshalingMode)
{
parameters[paramIndex] = StripAttributes(externMethodDeclaration.ParameterList.Parameters[paramIndex])
.WithType(tName.WithTrailingTrivia(TriviaList(Space)))
.WithModifiers([TokenWithSpace(SyntaxKind.OutKeyword)]);

arguments[paramIndex] = Argument(DeclarationExpression(
PredefinedType(TokenWithSpace(SyntaxKind.ObjectKeyword)),
SingleVariableDesignation(Identifier("__ppv"))))
.WithRefKindKeyword(TokenWithSpace(SyntaxKind.OutKeyword));

IdentifierNameSyntax ppvName = IdentifierName(externMethodDeclaration.ParameterList.Parameters[paramIndex].Identifier.ValueText);
trailingStatements.Add(ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
ppvName,
CastExpression(tName, IdentifierName("__ppv")))));
}
else
{
parameters[paramIndex] = StripAttributes(externMethodDeclaration.ParameterList.Parameters[paramIndex])
.WithType(PointerType(tName).WithTrailingTrivia(TriviaList(Space)))
.WithModifiers([TokenWithSpace(SyntaxKind.OutKeyword)]);

leadingStatements.Add(LocalDeclarationStatement(
VariableDeclaration(
PointerType(PredefinedType(Token(SyntaxKind.VoidKeyword))),
[VariableDeclarator(Identifier("__ppv"))])));

arguments[paramIndex] = Argument(PrefixUnaryExpression(SyntaxKind.AddressOfExpression, IdentifierName("__ppv")));

IdentifierNameSyntax ppvName = IdentifierName(externMethodDeclaration.ParameterList.Parameters[paramIndex].Identifier.ValueText);
trailingStatements.Add(ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
ppvName,
CastExpression(PointerType(tName), IdentifierName("__ppv")))));
}

continue;
}

bool isOptional = (param.Attributes & ParameterAttributes.Optional) == ParameterAttributes.Optional;
CustomAttributeHandleCollection paramAttributes = param.GetCustomAttributes();
bool isReserved = this.FindInteropDecorativeAttribute(paramAttributes, "ReservedAttribute") is not null;
Expand Down Expand Up @@ -1344,6 +1460,17 @@ bool TryHandleCountParam(TypeSyntax elementType, bool nullableSource)
.WithBody(body)
.WithSemicolonToken(default);

// If the IID_PPV_ARGS pattern was detected, make this method generic.
if (iidPpvRiidOrigIndex >= 0)
{
TypeParameterConstraintClauseSyntax constraintClause = iidPpvMarshalingMode
? TypeParameterConstraintClause(IdentifierName("T"), [ClassOrStructConstraint(SyntaxKind.ClassConstraint)])
: TypeParameterConstraintClause(IdentifierName("T"), [TypeConstraint(IdentifierName("unmanaged"))]);
friendlyDeclaration = friendlyDeclaration
.AddTypeParameterListParameters(TypeParameter(Identifier("T")))
.AddConstraintClauses(constraintClause);
}

if (returnSafeHandleType is object)
{
friendlyDeclaration = friendlyDeclaration.WithReturnType(returnSafeHandleType.WithTrailingTrivia(TriviaList(Space)));
Expand Down
8 changes: 8 additions & 0 deletions src/Microsoft.Windows.CsWin32/GeneratorOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,13 @@ public record FriendlyOverloadOptions
/// which normally appear as spans.
/// </summary>
public bool IncludePointerOverloads { get; set; } = false;

/// <summary>
/// Gets or sets a value indicating whether to generate generic <c>&lt;T&gt;</c> overloads for methods
/// with the IID_PPV_ARGS pattern (a <c>Guid*</c> parameter immediately preceding a <c>void**</c> <c>[ComOutPtr]</c> parameter),
/// where the GUID is derived from <c>typeof(T).GUID</c> and the output pointer is typed as <c>T</c>.
/// </summary>
/// <value>The default value is <see langword="true"/>.</value>
public bool ComOutPtrGenericOverloads { get; set; } = true;
}
}
5 changes: 5 additions & 0 deletions src/Microsoft.Windows.CsWin32/settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
"description": "A value indicating whether to also generate overloads that use pointer types for parameters that are [MemorySize] annotated buffers which normally appear as spans.",
"type": "boolean",
"default": false
},
"comOutPtrGenericOverloads": {
"description": "A value indicating whether to generate generic <T> overloads for methods with the IID_PPV_ARGS pattern (a Guid* parameter immediately preceding a void** [ComOutPtr] parameter), where the GUID is derived from typeof(T).GUID and the output pointer is typed as T.",
"type": "boolean",
"default": true
}
}
},
Expand Down

This file was deleted.

This file was deleted.

2 changes: 1 addition & 1 deletion test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public async Task DelegatesGetStructsGenerated()
// Optional and MemorySize-d struct params, optional params included
["SetupDiGetClassInstallParams", "SetupDiGetClassInstallParams", "SafeHandle DeviceInfoSet, [Optional] winmdroot.Devices.DeviceAndDriverInstallation.SP_DEVINFO_DATA? DeviceInfoData, [Optional] Span<byte> ClassInstallParams, out uint RequiredSize"],
["IEnumString", "Next", "this winmdroot.System.Com.IEnumString @this, Span<winmdroot.Foundation.PWSTR> rgelt, out uint pceltFetched"],
["PSCreateMemoryPropertyStore", "PSCreateMemoryPropertyStore", "in global::System.Guid riid, out object ppv"],
["PSCreateMemoryPropertyStore", "PSCreateMemoryPropertyStore", "out T ppv"],
["DeviceIoControl", "DeviceIoControl", "SafeHandle hDevice, uint dwIoControlCode, [Optional] ReadOnlySpan<byte> lpInBuffer, [Optional] Span<byte> lpOutBuffer, out uint lpBytesReturned, [Optional] global::System.Threading.NativeOverlapped* lpOverlapped"],
["DeviceIoControl", "DeviceIoControl", "SafeHandle hDevice, uint dwIoControlCode, [Optional] ReadOnlySpan<byte> lpInBuffer, [Optional] Span<byte> lpOutBuffer, out uint lpBytesReturned, [Optional] global::System.Threading.NativeOverlapped* lpOverlapped", true, "NativeMethods.IncludePointerOverloads.json"],
["NtQueryObject", "NtQueryObject", "[Optional] global::Windows.Win32.Foundation.HANDLE Handle, winmdroot.Foundation.OBJECT_INFORMATION_CLASS ObjectInformationClass, [Optional] Span<byte> ObjectInformation, out uint ReturnLength"],
Expand Down
6 changes: 2 additions & 4 deletions test/GenerationSandbox.BuildTask.Tests/COMTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,8 @@ public void IShellItem_BindToHandler_IStream_ReadWorks()

unsafe
{
PInvoke.SHCreateItemFromParsingName(filePath, null, typeof(IShellItem).GUID, out object shellItemObj).ThrowOnFailure();
IShellItem shellItem = (IShellItem)shellItemObj;
shellItem.BindToHandler(null, bhidStream, typeof(IStream).GUID, out object streamObj);
IStream stream = (IStream)streamObj;
PInvoke.SHCreateItemFromParsingName<IShellItem>(filePath, null, out IShellItem shellItem).ThrowOnFailure();
shellItem.BindToHandler<IStream>(null, bhidStream, out IStream stream);

// Friendly Span overload — the original repro for #1716. In source-generator mode this used to
// throw InvalidCastException because the extension method's `this` parameter was typed as
Expand Down
Loading
Loading