Skip to content

Commit 149d70a

Browse files
Generate SafeHandle when freeing method accepts additional reserved parameters (#1603)
* Generate `SafeHandle` when freeing method accepts additional reserved parameters * Fix * Typo Co-authored-by: Jevan Saks <jevansa@microsoft.com> * Rename * Use named argument for release method with reserved parameters * Refactor --------- Co-authored-by: Jevan Saks <jevansa@microsoft.com>
1 parent 845ecb4 commit 149d70a

File tree

2 files changed

+48
-11
lines changed

2 files changed

+48
-11
lines changed

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

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,27 @@ public partial class Generator
4242
TypeHandleInfo releaseMethodParameterTypeHandleInfo = releaseMethodSignature.ParameterTypes[0];
4343
TypeSyntaxAndMarshaling releaseMethodParameterType = releaseMethodParameterTypeHandleInfo.ToTypeSyntax(this.externSignatureTypeSettings, GeneratingElement.HelperClassMember, default);
4444

45-
// If the release method takes more than one parameter, we can't generate a SafeHandle for it.
46-
if (releaseMethodSignature.RequiredParameterCount != 1)
45+
var nonReservedParameterCount = 0;
46+
var handleParameterName = string.Empty;
47+
foreach (ParameterHandle paramHandle in releaseMethodDef.GetParameters())
48+
{
49+
Parameter param = this.Reader.GetParameter(paramHandle);
50+
if (param.SequenceNumber == 0)
51+
{
52+
continue;
53+
}
54+
55+
CustomAttributeHandleCollection paramAttributes = param.GetCustomAttributes();
56+
57+
if (this.FindInteropDecorativeAttribute(paramAttributes, "ReservedAttribute") is null)
58+
{
59+
nonReservedParameterCount++;
60+
handleParameterName = this.Reader.GetString(param.Name);
61+
}
62+
}
63+
64+
// If the release method takes more than one non-reserved parameter, we can't generate a SafeHandle for it.
65+
if (nonReservedParameterCount != 1)
4766
{
4867
safeHandleType = null;
4968
}
@@ -138,13 +157,22 @@ public partial class Generator
138157
.WithExpressionBody(ArrowExpressionClause(overallTest))
139158
.WithSemicolonToken(SemicolonWithLineFeed));
140159

160+
// If there are more than 1 parameter other parameters are reserved.
161+
// Otherwise we would have quit earlier
162+
var releaseMethodHasReservedParameters = releaseMethodSignature.RequiredParameterCount > 1;
163+
141164
// (struct)this.handle or (struct)checked((fieldType)(nint))this.handle, as appropriate.
165+
// If we have other reserved parameters make this a named argument to be extra sure
166+
// we are passing value for correct parameter
142167
bool implicitConversion = typeDefStructFieldType is PrimitiveTypeHandleInfo { PrimitiveTypeCode: PrimitiveTypeCode.IntPtr } or PointerTypeHandleInfo;
143-
ArgumentSyntax releaseHandleArgument = Argument(CastExpression(
144-
releaseMethodParameterType.Type,
145-
implicitConversion ? thisHandle : CheckedExpression(CastExpression(typeDefStructFieldType!.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.HelperClassMember, null).Type, CastExpression(IdentifierName("nint"), thisHandle)))));
146-
147-
// protected override bool ReleaseHandle() => ReleaseMethod((struct)this.handle);
168+
ArgumentSyntax releaseHandleArgument = Argument(
169+
releaseMethodHasReservedParameters ? NameColon(IdentifierName(handleParameterName)) : null,
170+
default,
171+
CastExpression(
172+
releaseMethodParameterType.Type,
173+
implicitConversion ? thisHandle : CheckedExpression(CastExpression(typeDefStructFieldType!.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.HelperClassMember, null).Type, CastExpression(IdentifierName("nint"), thisHandle)))));
174+
175+
// protected override [unsafe] bool ReleaseHandle() => ReleaseMethod((struct)this.handle);
148176
// Special case release functions based on their return type as follows: (https://github.com/microsoft/win32metadata/issues/25)
149177
// * bool => true is success
150178
// * int => zero is success
@@ -157,7 +185,11 @@ public partial class Generator
157185
IdentifierName(renamedReleaseMethod ?? releaseMethod)),
158186
ArgumentList().AddArguments(releaseHandleArgument));
159187
BlockSyntax? releaseBlock = null;
160-
bool releaseMethodIsUnsafe = false;
188+
189+
// Reserved parameters can be pointers.
190+
// Thus we need unsafe modifier even though we don't pass values for reserved parameters explicitly.
191+
// As an example of that see WlanCloseHandle function.
192+
var releaseMethodIsUnsafe = releaseMethodHasReservedParameters;
161193
if (!(releaseMethodReturnType.Type is PredefinedTypeSyntax { Keyword: { RawKind: (int)SyntaxKind.BoolKeyword } } ||
162194
releaseMethodReturnType.Type is QualifiedNameSyntax { Right: { Identifier: { ValueText: "BOOL" } } }))
163195
{
@@ -241,6 +273,7 @@ public partial class Generator
241273

242274
MethodDeclarationSyntax releaseHandleDeclaration = MethodDeclaration(PredefinedType(TokenWithSpace(SyntaxKind.BoolKeyword)), Identifier("ReleaseHandle"))
243275
.AddModifiers(TokenWithSpace(SyntaxKind.ProtectedKeyword), TokenWithSpace(SyntaxKind.OverrideKeyword));
276+
244277
if (releaseMethodIsUnsafe)
245278
{
246279
releaseHandleDeclaration = releaseHandleDeclaration.AddModifiers(TokenWithSpace(SyntaxKind.UnsafeKeyword));
@@ -254,14 +287,16 @@ public partial class Generator
254287
.WithBody(releaseBlock);
255288
members.Add(releaseHandleDeclaration);
256289

290+
IEnumerable<TypeSyntax> xmlDocParameterTypes = releaseMethodSignature.ParameterTypes.Select(p => p.ToTypeSyntax(this.externSignatureTypeSettings, GeneratingElement.HelperClassMember, default).Type);
291+
257292
ClassDeclarationSyntax safeHandleDeclaration = ClassDeclaration(Identifier(safeHandleClassName))
258293
.AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.PartialKeyword))
259294
.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(SimpleBaseType(SafeHandleTypeSyntax))))
260295
.AddMembers(members.ToArray())
261296
.AddAttributeLists(AttributeList().AddAttributes(GeneratedCodeAttribute))
262297
.WithLeadingTrivia(ParseLeadingTrivia($@"
263298
/// <summary>
264-
/// Represents a Win32 handle that can be closed with <see cref=""{this.options.ClassName}.{renamedReleaseMethod ?? releaseMethod}({releaseMethodParameterType.Type})""/>.
299+
/// Represents a Win32 handle that can be closed with <see cref=""{this.options.ClassName}.{renamedReleaseMethod ?? releaseMethod}({string.Join(", ", xmlDocParameterTypes)})""/>.
265300
/// </summary>
266301
"));
267302

test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ public async Task DelegatesGetStructsGenerated()
221221
["Windows.Win32.NetworkManagement.WindowsFilteringPlatform.FwpmProviderAdd0", "FwpmProviderAdd0", "SafeHandle engineHandle, in winmdroot.NetworkManagement.WindowsFilteringPlatform.FWPM_PROVIDER0 provider, [Optional] winmdroot.Security.PSECURITY_DESCRIPTOR sd"],
222222
// Verify the ABI signature has [Optional] on Optional and Reserved parameters.
223223
["Windows.Win32.NetworkManagement.WindowsFilteringPlatform.FwpmEngineOpen0", "FwpmEngineOpen0", "[Optional] winmdroot.Foundation.PCWSTR serverName, uint authnService, [Optional] winmdroot.System.Rpc.SEC_WINNT_AUTH_IDENTITY_W* authIdentity, [Optional] winmdroot.NetworkManagement.WindowsFilteringPlatform.FWPM_SESSION0* session, winmdroot.Foundation.HANDLE* engineHandle"],
224+
// WlanCloseHandle accepts an additional reserved parameter. We can still generate safe hanlde for WlanOpenHandle then
225+
["WlanOpenHandle", "WlanOpenHandle", "uint dwClientVersion, out uint pdwNegotiatedVersion, out global::Windows.Win32.WlanCloseHandleSafeHandle phClientHandle"],
224226
];
225227

226228
[Theory]
@@ -245,9 +247,9 @@ private async Task VerifySignatureWorker(string api, string member, string signa
245247
this.nativeMethodsJson = nativeMethodsJson;
246248

247249
// Make a unique name based on the signature
248-
await this.InvokeGeneratorAndCompile($"{api}_{member}_{tfm}_{signature.Select(x => (int)x).Aggregate((x, y) => x + y).ToString("X")}");
250+
await this.InvokeGeneratorAndCompile($"{api}_{member}_{tfm}_{signature.Select(x => (int)x).Aggregate((x, y) => x + y):X}");
249251

250-
var generatedMemberSignatures = this.FindGeneratedMethod(member).Select(x => x.ParameterList.ToString());
252+
var generatedMemberSignatures = this.FindGeneratedMethod(member).Select(x => x.ParameterList.ToString()).ToArray();
251253

252254
foreach (var generatedSignature in generatedMemberSignatures)
253255
{

0 commit comments

Comments
 (0)