diff --git a/Directory.Packages.Analyzers.props b/Directory.Packages.Analyzers.props index ba9b8b7d..b0ebe285 100644 --- a/Directory.Packages.Analyzers.props +++ b/Directory.Packages.Analyzers.props @@ -11,6 +11,6 @@ - + diff --git a/Directory.Packages.props b/Directory.Packages.props index 5734bcb7..de531702 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,7 +6,7 @@ true 2.0.201 - 68.0.4-preview + 69.0.7-preview 0.13.25-experimental 0.1.42-alpha diff --git a/docfx/docs/Examples.md b/docfx/docs/Examples.md index 40bb828c..46e6d0c3 100644 --- a/docfx/docs/Examples.md +++ b/docfx/docs/Examples.md @@ -2,17 +2,7 @@ When, for example, marshaling is enabled and `useSafeHandles` is `true`, the code can be different than that in C++. Here we show a few code examples of using CsWin32. -```json -{ - "$schema": "https://aka.ms/CsWin32.schema.json", - "useSafeHandles": false, - "comInterop": { - "preserveSigMethods": ["*"] - } -} -``` - -## Scenario 1: Retrieving the last-write time +## Retrieving the last-write time Based on: [Retrieving the Last-Write Time](https://learn.microsoft.com/en-us/windows/win32/sysinfo/retrieving-the-last-write-time) @@ -29,7 +19,6 @@ MAX_PATH ```cs static void Main(string[] args) { - SafeHandle hFile; Span szBuf = stackalloc char[(int)PInvoke.MAX_PATH]; if (args.Length is not 1) @@ -38,7 +27,7 @@ static void Main(string[] args) return; } - hFile = PInvoke.CreateFile( + using SafeHandle hFile = PInvoke.CreateFile( args[0], (uint)GENERIC_ACCESS_RIGHTS.GENERIC_READ, FILE_SHARE_MODE.FILE_SHARE_READ, @@ -52,8 +41,6 @@ static void Main(string[] args) if (GetLastWriteTime(hFile, szBuf)) Console.WriteLine("Last write time is: {0}\n", szBuf.ToString()); - - hFile.Close(); } static unsafe bool GetLastWriteTime(SafeHandle hFile, Span lpszString) @@ -77,7 +64,7 @@ static unsafe bool GetLastWriteTime(SafeHandle hFile, Span lpszString) } ``` -## Scenario 2: Retrieving information about all of the display monitors +## Retrieving information about all of the display monitors ``` EnumDisplayMonitors @@ -130,7 +117,7 @@ static unsafe BOOL MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, RECT* lprc } ``` -## Scenario 3: Use of SHCreateItemFromParsingName +## Use of SHCreateItemFromParsingName ``` IShellItem @@ -185,3 +172,152 @@ static unsafe void Main(string[] args) psi->Release(); } ``` + +## Using COM classes (NetFwMgr, INetFwMgr) + +In this example we see how to activate a COM class and call methods on it via its primary interface. When marshalling +is enabled, we can use C# cast to do the "QueryInterface" that you would have seen in a native C++ sample. + +``` +NetFwMgr +INetFwMgr +IEnumVARIANT +INetFwAuthorizedApplication +``` + +### Marshalling enabled (built-in COM Interop, not AOT-compatible) + +```cs +var fwMgr = (INetFwMgr)new NetFwMgr(); +var authorizedApplications = fwMgr.LocalPolicy.CurrentProfile.AuthorizedApplications; +var aaObjects = new object[authorizedApplications.Count]; +var applicationsEnum = (IEnumVARIANT)authorizedApplications._NewEnum; +applicationsEnum.Next((uint)authorizedApplications.Count, aaObjects, out uint fetched); +foreach (var aaObject in aaObjects) +{ + var app = (INetFwAuthorizedApplication)aaObject; + Console.WriteLine("---"); + Console.WriteLine($"Name: {app.Name.ToString()}"); + Console.WriteLine($"Enabled: {(bool)app.Enabled}"); + Console.WriteLine($"Remote Addresses: {app.RemoteAddresses.ToString()}"); + Console.WriteLine($"Scope: {app.Scope}"); + Console.WriteLine($"Process Image Filename: {app.ProcessImageFileName.ToString()}"); + Console.WriteLine($"IP Version: {app.IpVersion}"); +} +``` + +### Marshalling enabled (COM wrappers, AOT compatible) + +Note that in COM wrappers mode, the generated interfaces have get_ methods instead of properties. Some parameters +are also ComVariant instead of object because source generated COM does not support as much automatic marshalling +as built-in COM does. + +```cs +var fwMgr = NetFwMgr.CreateInstance(); +var authorizedApplications = fwMgr.get_LocalPolicy().get_CurrentProfile().get_AuthorizedApplications(); +var aaObjects = new ComVariant[authorizedApplications.get_Count()]; +var applicationsEnum = (IEnumVARIANT)authorizedApplications.get__NewEnum(); +applicationsEnum.Next((uint)authorizedApplications.get_Count(), aaObjects, out uint fetched); +foreach (var aaObject in aaObjects) +{ + var app = (INetFwAuthorizedApplication)ComVariantMarshaller.ConvertToManaged(aaObject)!; + + Console.WriteLine("---"); + Console.WriteLine($"Name: {app.get_Name().ToString()}"); + Console.WriteLine($"Enabled: {(bool)app.get_Enabled()}"); + Console.WriteLine($"Remote Addresses: {app.get_RemoteAddresses().ToString()}"); + Console.WriteLine($"Scope: {app.get_Scope()}"); + Console.WriteLine($"Process Image Filename: {app.get_ProcessImageFileName().ToString()}"); + Console.WriteLine($"IP Version: {app.get_IpVersion()}"); + + aaObject.Dispose(); +} +``` + +## Use PNP APIs (shows omitted optional params and cbSize-d struct) + +This sample shows how to call an API where we've omitted some optional params -- note that we must +use named parameters when passing parameters past the omitted optional ones. This also shows how to +use Span APIs, and in this case one where we first call the API to get the buffer size, create the buffer +and then call again to populate the buffer. + +``` +SetupDiGetClassDevs +SetupDiEnumDeviceInfo +SetupDiGetDeviceInstanceId +``` + +```cs +using SafeHandle hDevInfo = PInvoke.SetupDiGetClassDevs( + Flags: SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_ALLCLASSES | SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_PRESENT); + +var devInfo = new SP_DEVINFO_DATA { cbSize = (uint)sizeof(SP_DEVINFO_DATA) }; + +uint index = 0; +while (PInvoke.SetupDiEnumDeviceInfo(hDevInfo, index++, ref devInfo)) +{ + PInvoke.SetupDiGetDeviceInstanceId(hDevInfo, in devInfo, RequiredSize: out uint requiredSize); + + Span instanceIdSpan = new char[(int)requiredSize]; + PInvoke.SetupDiGetDeviceInstanceId(hDevInfo, in devInfo, instanceIdSpan); + + Console.WriteLine($"Device {devInfo.ClassGuid} Instance ID: {instanceIdSpan.ToString()}"); +} +``` + +## Pass struct as a Span + +In this short example, we see how to pass a struct to a method that accepts a `Span`. `new Span(ref fileInfo)` lets us +get a `Span` and then `MemoryMarshal.AsBytes` reinterprets that same Span as a `Span` with the expected size. The cswin32 +method will pass the Span's length to the native method as the "cb" count bytes parameter. + +```cs +SHFILEINFOW fileInfo = default; +PInvoke.SHGetFileInfo( + "c:\\windows\\notepad.exe", + FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, + MemoryMarshal.AsBytes(new Span(ref fileInfo)), + SHGFI_FLAGS.SHGFI_DISPLAYNAME); +``` + +## Omitting optional out/ref parameters + +APIs with optional `in` parameters are tagged with `[Optional]` attribute and such parameters can be omitted, but APIs +with optional `out` or `ref` parameters must always be passed. When the native method has these as `[optional]` and you need +to pass _some_ but not all of those parameters, you can pass "null" to the native method using `ref Unsafe.NullRef()`. + +This sample also shows passing `null` for SafeHandle-typed parameters which are not optional per the SDK headers but the +implementation allows for them to be null. + +This sample shows a number of advanced COM marshalling scenarios + +### Marshalling enabled (COM wrappers, AOT compatible) + +```cs +// CoCreateInstance CLSID_WbemLocator +IWbemLocator locator = WbemLocator.CreateInstance(); + +var ns = new SysFreeStringSafeHandle(Marshal.StringToBSTR(@"ROOT\Microsoft\Windows\Defender"), true); +locator.ConnectServer(ns, new SysFreeStringSafeHandle(), new SysFreeStringSafeHandle(), new SysFreeStringSafeHandle(), 0, new SafeFileHandle(), null, out IWbemServices services); + +unsafe +{ + PInvoke.CoSetProxyBlanket( + services, + 10, // RPC_C_AUTHN_WINNT is 10 + 0, // RPC_C_AUTHZ_NONE is 0 + pServerPrincName: null, + dwAuthnLevel: RPC_C_AUTHN_LEVEL.RPC_C_AUTHN_LEVEL_CALL, + dwImpLevel: RPC_C_IMP_LEVEL.RPC_C_IMP_LEVEL_IMPERSONATE, + pAuthInfo: null, + dwCapabilities: EOLE_AUTHENTICATION_CAPABILITIES.EOAC_NONE); +} + +var className = new SysFreeStringSafeHandle(Marshal.StringToBSTR("MSFT_MpScan"), true); +IWbemClassObject? classObj = null; // out param + +services.GetObject(className, WBEM_GENERIC_FLAG_TYPE.WBEM_FLAG_RETURN_WBEM_COMPLETE, null, ref classObj, ref Unsafe.NullRef()); + +classObj.GetMethod("Start", 0, out IWbemClassObject pInParamsSignature, out IWbemClassObject ppOutSignature); + +``` diff --git a/src/Microsoft.Windows.CsWin32/FastSyntaxFactory.cs b/src/Microsoft.Windows.CsWin32/FastSyntaxFactory.cs index a4c620a4..31e7a249 100644 --- a/src/Microsoft.Windows.CsWin32/FastSyntaxFactory.cs +++ b/src/Microsoft.Windows.CsWin32/FastSyntaxFactory.cs @@ -74,6 +74,8 @@ internal static SyntaxToken Token(SyntaxKind kind) internal static BlockSyntax Block(params StatementSyntax[] statements) => SyntaxFactory.Block(OpenBrace, List(statements), CloseBrace); + internal static BlockSyntax Block(IEnumerable statements) => SyntaxFactory.Block(OpenBrace, List(statements), CloseBrace); + internal static ImplicitArrayCreationExpressionSyntax ImplicitArrayCreationExpression(InitializerExpressionSyntax initializerExpression) => SyntaxFactory.ImplicitArrayCreationExpression(Token(SyntaxKind.NewKeyword), Token(SyntaxKind.OpenBracketToken), default, Token(SyntaxKind.CloseBracketToken), initializerExpression); internal static ForStatementSyntax ForStatement(VariableDeclarationSyntax? declaration, ExpressionSyntax condition, SeparatedSyntaxList incrementors, StatementSyntax statement) @@ -100,10 +102,12 @@ internal static ForStatementSyntax ForStatement(VariableDeclarationSyntax? decla internal static DeclarationExpressionSyntax DeclarationExpression(TypeSyntax type, VariableDesignationSyntax designation) => SyntaxFactory.DeclarationExpression(type, designation); - internal static VariableDeclaratorSyntax VariableDeclarator(SyntaxToken identifier) => SyntaxFactory.VariableDeclarator(identifier); + internal static VariableDeclaratorSyntax VariableDeclarator(SyntaxToken identifier, EqualsValueClauseSyntax? initializer = null) => SyntaxFactory.VariableDeclarator(identifier, argumentList: null, initializer: initializer); internal static VariableDeclarationSyntax VariableDeclaration(TypeSyntax type) => SyntaxFactory.VariableDeclaration(type.WithTrailingTrivia(TriviaList(Space))); + internal static VariableDeclarationSyntax VariableDeclaration(TypeSyntax type, params VariableDeclaratorSyntax[] variables) => SyntaxFactory.VariableDeclaration(type.WithTrailingTrivia(TriviaList(Space)), SeparatedList(variables)); + internal static SizeOfExpressionSyntax SizeOfExpression(TypeSyntax type) => SyntaxFactory.SizeOfExpression(Token(SyntaxKind.SizeOfKeyword), Token(SyntaxKind.OpenParenToken), type, Token(SyntaxKind.CloseParenToken)); internal static MemberAccessExpressionSyntax MemberAccessExpression(SyntaxKind kind, ExpressionSyntax expression, SimpleNameSyntax name) => SyntaxFactory.MemberAccessExpression(kind, expression, Token(GetMemberAccessExpressionOperatorTokenKind(kind)), name); @@ -190,7 +194,7 @@ internal static ForStatementSyntax ForStatement(VariableDeclarationSyntax? decla internal static InitializerExpressionSyntax InitializerExpression(SyntaxKind kind, SeparatedSyntaxList expressions) => SyntaxFactory.InitializerExpression(kind, OpenBrace, expressions, CloseBrace); - internal static ObjectCreationExpressionSyntax ObjectCreationExpression(TypeSyntax type) => SyntaxFactory.ObjectCreationExpression(Token(TriviaList(), SyntaxKind.NewKeyword, TriviaList(Space)), type, ArgumentList(), null); + internal static ObjectCreationExpressionSyntax ObjectCreationExpression(TypeSyntax type, SeparatedSyntaxList arguments = default) => SyntaxFactory.ObjectCreationExpression(Token(TriviaList(), SyntaxKind.NewKeyword, TriviaList(Space)), type, ArgumentList(arguments), null); internal static ArrayCreationExpressionSyntax ArrayCreationExpression(ArrayTypeSyntax type, InitializerExpressionSyntax? initializer = null) => SyntaxFactory.ArrayCreationExpression(Token(SyntaxKind.NewKeyword), type, initializer); @@ -295,7 +299,7 @@ internal static SyntaxList SingletonList(TNode node) internal static AttributeArgumentListSyntax AttributeArgumentList(SeparatedSyntaxList arguments = default) => SyntaxFactory.AttributeArgumentList(Token(SyntaxKind.OpenParenToken), arguments, Token(SyntaxKind.CloseParenToken)); - internal static AttributeListSyntax AttributeList() => SyntaxFactory.AttributeList(Token(SyntaxKind.OpenBracketToken), null, SeparatedList(), TokenWithLineFeed(SyntaxKind.CloseBracketToken)); + internal static AttributeListSyntax AttributeList(params SeparatedSyntaxList attributes) => SyntaxFactory.AttributeList(Token(SyntaxKind.OpenBracketToken), null, attributes, TokenWithLineFeed(SyntaxKind.CloseBracketToken)); internal static SyntaxList List() where TNode : SyntaxNode => SyntaxFactory.List(); @@ -305,7 +309,7 @@ internal static SyntaxList List(IEnumerable nodes) internal static ParameterListSyntax ParameterList() => SyntaxFactory.ParameterList(Token(SyntaxKind.OpenParenToken), SeparatedList(), Token(SyntaxKind.CloseParenToken)); - internal static ArgumentListSyntax ArgumentList(SeparatedSyntaxList arguments = default) => SyntaxFactory.ArgumentList(Token(SyntaxKind.OpenParenToken), arguments, Token(SyntaxKind.CloseParenToken)); + internal static ArgumentListSyntax ArgumentList(params SeparatedSyntaxList arguments) => SyntaxFactory.ArgumentList(Token(SyntaxKind.OpenParenToken), arguments, Token(SyntaxKind.CloseParenToken)); internal static AssignmentExpressionSyntax AssignmentExpression(SyntaxKind kind, ExpressionSyntax left, ExpressionSyntax right) => SyntaxFactory.AssignmentExpression(kind, left, Token(GetAssignmentExpressionOperatorTokenKind(kind)).WithLeadingTrivia(Space), right); diff --git a/src/Microsoft.Windows.CsWin32/Generator.Features.cs b/src/Microsoft.Windows.CsWin32/Generator.Features.cs index 1e286683..a7591320 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.Features.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.Features.cs @@ -22,6 +22,7 @@ public partial class Generator private readonly bool unscopedRefAttributePredefined; private readonly bool canUseComVariant; private readonly bool canUseMemberFunctionCallingConvention; + private readonly bool canUseMarshalInitHandle; private readonly INamedTypeSymbol? runtimeFeatureClass; private readonly bool generateSupportedOSPlatformAttributes; private readonly bool generateSupportedOSPlatformAttributesOnInterfaces; // only supported on net6.0 (https://github.com/dotnet/runtime/pull/48838) diff --git a/src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs b/src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs index 6987a082..798705e5 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs @@ -378,19 +378,54 @@ private IEnumerable DeclareFriendlyOverload( .WithModifiers(TokenList(TokenWithSpace(SyntaxKind.OutKeyword))); // HANDLE SomeLocal; - leadingStatements.Add(LocalDeclarationStatement(VariableDeclaration(pointedElementInfo.ToTypeSyntax(parameterTypeSyntaxSettings, GeneratingElement.FriendlyOverload, null).Type).AddVariables( - VariableDeclarator(typeDefHandleName.Identifier)))); + leadingStatements.Add( + LocalDeclarationStatement( + VariableDeclaration( + pointedElementInfo.ToTypeSyntax(parameterTypeSyntaxSettings, GeneratingElement.FriendlyOverload, null).Type, + VariableDeclarator(typeDefHandleName.Identifier)))); + + ArgumentSyntax ownsHandleArgument = Argument( + NameColon(IdentifierName("ownsHandle")), + refKindKeyword: default, + LiteralExpression(doNotRelease ? SyntaxKind.FalseLiteralExpression : SyntaxKind.TrueLiteralExpression)); + + if (this.canUseMarshalInitHandle) + { + // Some = new SafeHandle(default, ownsHandle: true); + leadingStatements.Add( + ExpressionStatement( + AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + origName, + ObjectCreationExpression(safeHandleType, [Argument(LiteralExpression(SyntaxKind.DefaultLiteralExpression)), ownsHandleArgument])))); + + // Marshal.InitHandle(Some, SomeLocal); + trailingStatements.Add( + ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nameof(Marshal)), + IdentifierName("InitHandle")), + ArgumentList( + [ + Argument(origName), + Argument(this.GetIntPtrFromTypeDef(typeDefHandleName, pointedElementInfo)), + ])))); + } + else + { + // Some = new SafeHandle(SomeLocal, ownsHandle: true); + trailingStatements.Add(ExpressionStatement(AssignmentExpression( + SyntaxKind.SimpleAssignmentExpression, + origName, + ObjectCreationExpression(safeHandleType).AddArgumentListArguments( + Argument(this.GetIntPtrFromTypeDef(typeDefHandleName, pointedElementInfo)), + ownsHandleArgument)))); + } // Argument: &SomeLocal arguments[paramIndex] = Argument(PrefixUnaryExpression(SyntaxKind.AddressOfExpression, typeDefHandleName)); - - // Some = new SafeHandle(SomeLocal, ownsHandle: true); - trailingStatements.Add(ExpressionStatement(AssignmentExpression( - SyntaxKind.SimpleAssignmentExpression, - origName, - ObjectCreationExpression(safeHandleType).AddArgumentListArguments( - Argument(this.GetIntPtrFromTypeDef(typeDefHandleName, pointedElementInfo)), - Argument(LiteralExpression(doNotRelease ? SyntaxKind.FalseLiteralExpression : SyntaxKind.TrueLiteralExpression)).WithNameColon(NameColon(IdentifierName("ownsHandle"))))))); } } else if (this.options.UseSafeHandles && isIn && !isOut && !isReleaseMethod && parameterTypeInfo is HandleTypeHandleInfo parameterHandleTypeInfo && this.TryGetHandleReleaseMethod(parameterHandleTypeInfo.Handle, paramAttributes, out string? releaseMethod) && !this.Reader.StringComparer.Equals(methodDefinition.Name, releaseMethod) @@ -1108,7 +1143,46 @@ bool TryHandleCountParam(TypeSyntax elementType, bool nullableSource) && returnTypeHandleInfo.Generator.TryGetHandleReleaseMethod(returnTypeHandleInfo.Handle, returnTypeAttributes, out string? returnReleaseMethod) ? this.RequestSafeHandle(returnReleaseMethod) : null; - if ((returnSafeHandleType is object || minorSignatureChange) && !signatureChanged) + IdentifierNameSyntax resultLocal = IdentifierName("__result"); + + if (this.canUseMarshalInitHandle && returnSafeHandleType is not null) + { + IdentifierNameSyntax resultSafeHandleLocal = IdentifierName("__resultSafeHandle"); + + // SafeHandle __resultSafeHandle = new SafeHandle(default, ownsHandle: true); + leadingStatements.Add( + LocalDeclarationStatement( + VariableDeclaration( + returnSafeHandleType, + VariableDeclarator( + resultSafeHandleLocal.Identifier, + EqualsValueClause( + ObjectCreationExpression( + returnSafeHandleType, + [ + Argument(LiteralExpression(SyntaxKind.DefaultLiteralExpression)), + Argument( + NameColon(IdentifierName("ownsHandle")), + refKindKeyword: default, + LiteralExpression(doNotRelease ? SyntaxKind.FalseLiteralExpression : SyntaxKind.TrueLiteralExpression)) + ])))))); + + // Marshal.InitHandle(__resultSafeHandle, __result); + trailingStatements.Add( + ExpressionStatement( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName(nameof(Marshal)), + IdentifierName("InitHandle")), + ArgumentList( + [ + Argument(resultSafeHandleLocal), + Argument(this.GetIntPtrFromTypeDef(resultLocal, originalSignature.ReturnType)), + ])))); + } + + if ((returnSafeHandleType is not null || minorSignatureChange) && !signatureChanged) { // The parameter types are all the same, but we need a friendly overload with a different return type. // Our only choice is to rename the friendly overload. @@ -1145,20 +1219,33 @@ bool TryHandleCountParam(TypeSyntax elementType, bool nullableSource) }) .WithArgumentList(FixTrivia(ArgumentList().AddArguments(arguments.ToArray()))); bool hasVoidReturn = externMethodReturnType is PredefinedTypeSyntax { Keyword: { RawKind: (int)SyntaxKind.VoidKeyword } }; - BlockSyntax? body = Block().AddStatements(leadingStatements.ToArray()); - IdentifierNameSyntax resultLocal = IdentifierName("__result"); - if (returnSafeHandleType is object) + BlockSyntax? body = Block(leadingStatements); + if (returnSafeHandleType is not null) { - //// HANDLE result = invocation(); + // HANDLE result = invocation(); body = body.AddStatements(LocalDeclarationStatement(VariableDeclaration(externMethodReturnType) .AddVariables(VariableDeclarator(resultLocal.Identifier).WithInitializer(EqualsValueClause(externInvocation))))); body = body.AddStatements(trailingStatements.ToArray()); - //// return new SafeHandle(result, ownsHandle: true); - body = body.AddStatements(ReturnStatement(ObjectCreationExpression(returnSafeHandleType).AddArgumentListArguments( - Argument(this.GetIntPtrFromTypeDef(resultLocal, originalSignature.ReturnType)), - Argument(LiteralExpression(doNotRelease ? SyntaxKind.FalseLiteralExpression : SyntaxKind.TrueLiteralExpression)).WithNameColon(NameColon(IdentifierName("ownsHandle")))))); + ReturnStatementSyntax returnStatement; + if (this.canUseMarshalInitHandle) + { + // return __resultSafeHandle; + returnStatement = ReturnStatement(IdentifierName("__resultSafeHandle")); + } + else + { + // return new SafeHandle(result, ownsHandle: true); + returnStatement = ReturnStatement(ObjectCreationExpression(returnSafeHandleType).AddArgumentListArguments( + Argument(this.GetIntPtrFromTypeDef(resultLocal, originalSignature.ReturnType)), + Argument( + NameColon(IdentifierName("ownsHandle")), + refKindKeyword: default, + LiteralExpression(doNotRelease ? SyntaxKind.FalseLiteralExpression : SyntaxKind.TrueLiteralExpression)))); + } + + body = body.AddStatements(returnStatement); } else if (hasVoidReturn) { diff --git a/src/Microsoft.Windows.CsWin32/Generator.Handle.cs b/src/Microsoft.Windows.CsWin32/Generator.Handle.cs index 3d4a9bed..5c300b8c 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.Handle.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.Handle.cs @@ -42,8 +42,27 @@ public partial class Generator TypeHandleInfo releaseMethodParameterTypeHandleInfo = releaseMethodSignature.ParameterTypes[0]; TypeSyntaxAndMarshaling releaseMethodParameterType = releaseMethodParameterTypeHandleInfo.ToTypeSyntax(this.externSignatureTypeSettings, GeneratingElement.HelperClassMember, default); - // If the release method takes more than one parameter, we can't generate a SafeHandle for it. - if (releaseMethodSignature.RequiredParameterCount != 1) + var nonReservedParameterCount = 0; + var handleParameterName = string.Empty; + foreach (ParameterHandle paramHandle in releaseMethodDef.GetParameters()) + { + Parameter param = this.Reader.GetParameter(paramHandle); + if (param.SequenceNumber == 0) + { + continue; + } + + CustomAttributeHandleCollection paramAttributes = param.GetCustomAttributes(); + + if (this.FindInteropDecorativeAttribute(paramAttributes, "ReservedAttribute") is null) + { + nonReservedParameterCount++; + handleParameterName = this.Reader.GetString(param.Name); + } + } + + // If the release method takes more than one non-reserved parameter, we can't generate a SafeHandle for it. + if (nonReservedParameterCount != 1) { safeHandleType = null; } @@ -104,29 +123,32 @@ public partial class Generator VariableDeclarator(invalidValueFieldName.Identifier).WithInitializer(EqualsValueClause(invalidHandleIntPtr)))) .AddModifiers(TokenWithSpace(SyntaxKind.PrivateKeyword), TokenWithSpace(SyntaxKind.StaticKeyword), TokenWithSpace(SyntaxKind.ReadOnlyKeyword))); + SyntaxToken visibilityModifier = TokenWithSpace(this.Visibility); + // public SafeHandle() : base(INVALID_HANDLE_VALUE, true) members.Add(ConstructorDeclaration(safeHandleTypeIdentifier.Identifier) - .AddModifiers(TokenWithSpace(this.Visibility)) + .AddModifiers(visibilityModifier) .WithInitializer(ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, ArgumentList().AddArguments( Argument(invalidValueFieldName), Argument(LiteralExpression(SyntaxKind.TrueLiteralExpression))))) .WithBody(Block())); // public SafeHandle(IntPtr preexistingHandle, bool ownsHandle = true) : base(INVALID_HANDLE_VALUE, ownsHandle) { this.SetHandle(preexistingHandle); } - const string preexistingHandleName = "preexistingHandle"; - const string ownsHandleName = "ownsHandle"; + IdentifierNameSyntax preexistingHandleName = IdentifierName("preexistingHandle"); + IdentifierNameSyntax ownsHandleName = IdentifierName("ownsHandle"); members.Add(ConstructorDeclaration(safeHandleTypeIdentifier.Identifier) - .AddModifiers(TokenWithSpace(this.Visibility)) + .AddModifiers(visibilityModifier) .AddParameterListParameters( - Parameter(Identifier(preexistingHandleName)).WithType(IntPtrTypeSyntax.WithTrailingTrivia(TriviaList(Space))), - Parameter(Identifier(ownsHandleName)).WithType(PredefinedType(TokenWithSpace(SyntaxKind.BoolKeyword))) + Parameter(preexistingHandleName.Identifier).WithType(IntPtrTypeSyntax.WithTrailingTrivia(TriviaList(Space))), + Parameter(ownsHandleName.Identifier) + .WithType(PredefinedType(TokenWithSpace(SyntaxKind.BoolKeyword))) .WithDefault(EqualsValueClause(LiteralExpression(SyntaxKind.TrueLiteralExpression)))) .WithInitializer(ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, ArgumentList().AddArguments( Argument(invalidValueFieldName), - Argument(IdentifierName(ownsHandleName))))) + Argument(ownsHandleName)))) .WithBody(Block().AddStatements( ExpressionStatement(InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("SetHandle"))) - .WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(IdentifierName(preexistingHandleName))))))))); + .WithArgumentList(ArgumentList(SingletonSeparatedList(Argument(preexistingHandleName)))))))); // public override bool IsInvalid => this.handle.ToInt64() == 0 || this.handle.ToInt64() == -1; ExpressionSyntax thisHandleToInt64 = InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, thisHandle, IdentifierName(nameof(IntPtr.ToInt64))), ArgumentList()); @@ -138,13 +160,22 @@ public partial class Generator .WithExpressionBody(ArrowExpressionClause(overallTest)) .WithSemicolonToken(SemicolonWithLineFeed)); + // If there are more than 1 parameter other parameters are reserved. + // Otherwise we would have quit earlier + var releaseMethodHasReservedParameters = releaseMethodSignature.RequiredParameterCount > 1; + // (struct)this.handle or (struct)checked((fieldType)(nint))this.handle, as appropriate. + // If we have other reserved parameters make this a named argument to be extra sure + // we are passing value for correct parameter bool implicitConversion = typeDefStructFieldType is PrimitiveTypeHandleInfo { PrimitiveTypeCode: PrimitiveTypeCode.IntPtr } or PointerTypeHandleInfo; - ArgumentSyntax releaseHandleArgument = Argument(CastExpression( - releaseMethodParameterType.Type, - implicitConversion ? thisHandle : CheckedExpression(CastExpression(typeDefStructFieldType!.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.HelperClassMember, null).Type, CastExpression(IdentifierName("nint"), thisHandle))))); - - // protected override bool ReleaseHandle() => ReleaseMethod((struct)this.handle); + ArgumentSyntax releaseHandleArgument = Argument( + releaseMethodHasReservedParameters ? NameColon(IdentifierName(handleParameterName)) : null, + default, + CastExpression( + releaseMethodParameterType.Type, + implicitConversion ? thisHandle : CheckedExpression(CastExpression(typeDefStructFieldType!.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.HelperClassMember, null).Type, CastExpression(IdentifierName("nint"), thisHandle))))); + + // protected override [unsafe] bool ReleaseHandle() => ReleaseMethod((struct)this.handle); // Special case release functions based on their return type as follows: (https://github.com/microsoft/win32metadata/issues/25) // * bool => true is success // * int => zero is success @@ -157,7 +188,11 @@ public partial class Generator IdentifierName(renamedReleaseMethod ?? releaseMethod)), ArgumentList().AddArguments(releaseHandleArgument)); BlockSyntax? releaseBlock = null; - bool releaseMethodIsUnsafe = false; + + // Reserved parameters can be pointers. + // Thus we need unsafe modifier even though we don't pass values for reserved parameters explicitly. + // As an example of that see WlanCloseHandle function. + var releaseMethodIsUnsafe = releaseMethodHasReservedParameters; if (!(releaseMethodReturnType.Type is PredefinedTypeSyntax { Keyword: { RawKind: (int)SyntaxKind.BoolKeyword } } || releaseMethodReturnType.Type is QualifiedNameSyntax { Right: { Identifier: { ValueText: "BOOL" } } })) { @@ -241,6 +276,7 @@ public partial class Generator MethodDeclarationSyntax releaseHandleDeclaration = MethodDeclaration(PredefinedType(TokenWithSpace(SyntaxKind.BoolKeyword)), Identifier("ReleaseHandle")) .AddModifiers(TokenWithSpace(SyntaxKind.ProtectedKeyword), TokenWithSpace(SyntaxKind.OverrideKeyword)); + if (releaseMethodIsUnsafe) { releaseHandleDeclaration = releaseHandleDeclaration.AddModifiers(TokenWithSpace(SyntaxKind.UnsafeKeyword)); @@ -254,14 +290,16 @@ public partial class Generator .WithBody(releaseBlock); members.Add(releaseHandleDeclaration); + IEnumerable xmlDocParameterTypes = releaseMethodSignature.ParameterTypes.Select(p => p.ToTypeSyntax(this.externSignatureTypeSettings, GeneratingElement.HelperClassMember, default).Type); + ClassDeclarationSyntax safeHandleDeclaration = ClassDeclaration(Identifier(safeHandleClassName)) - .AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.PartialKeyword)) + .AddModifiers(visibilityModifier, TokenWithSpace(SyntaxKind.PartialKeyword)) .WithBaseList(BaseList(SingletonSeparatedList(SimpleBaseType(SafeHandleTypeSyntax)))) .AddMembers(members.ToArray()) .AddAttributeLists(AttributeList().AddAttributes(GeneratedCodeAttribute)) .WithLeadingTrivia(ParseLeadingTrivia($@" /// -/// Represents a Win32 handle that can be closed with . +/// Represents a Win32 handle that can be closed with . /// ")); diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index f037cded..6ef0cb0a 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -127,6 +127,7 @@ public Generator(string metadataLibraryPath, Docs? docs, IEnumerable add this.canUseIPropertyValue = this.compilation?.GetTypeByMetadataName("Windows.Foundation.IPropertyValue")?.DeclaredAccessibility == Accessibility.Public; this.canUseComVariant = this.compilation?.GetTypeByMetadataName("System.Runtime.InteropServices.Marshalling.ComVariant") is not null; this.canUseMemberFunctionCallingConvention = this.compilation?.GetTypeByMetadataName("System.Runtime.CompilerServices.CallConvMemberFunction") is not null; + this.canUseMarshalInitHandle = this.compilation?.GetTypeByMetadataName(typeof(Marshal).FullName)?.GetMembers("InitHandle").Length > 0; if (this.FindTypeSymbolIfAlreadyAvailable("System.Runtime.Versioning.SupportedOSPlatformAttribute") is { } attribute) { this.generateSupportedOSPlatformAttributes = true; diff --git a/test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs b/test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs index 9fd075ff..8b294488 100644 --- a/test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs +++ b/test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs @@ -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 ClassInstallParams, out uint RequiredSize"], ["IEnumString", "Next", "this winmdroot.System.Com.IEnumString @this, Span rgelt, out uint pceltFetched"], - ["PSCreateMemoryPropertyStore", "PSCreateMemoryPropertyStore", "in global::System.Guid riid, out void* ppv"], + ["PSCreateMemoryPropertyStore", "PSCreateMemoryPropertyStore", "in global::System.Guid riid, out object ppv"], ["DeviceIoControl", "DeviceIoControl", "SafeHandle hDevice, uint dwIoControlCode, [Optional] ReadOnlySpan lpInBuffer, [Optional] Span lpOutBuffer, out uint lpBytesReturned, [Optional] global::System.Threading.NativeOverlapped* lpOverlapped"], ["DeviceIoControl", "DeviceIoControl", "SafeHandle hDevice, uint dwIoControlCode, [Optional] ReadOnlySpan lpInBuffer, [Optional] Span 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 ObjectInformation, out uint ReturnLength"], @@ -220,7 +220,9 @@ public async Task DelegatesGetStructsGenerated() ["EnumProcessModules", "EnumProcessModules", "SafeHandle hProcess, Span lphModule, out uint lpcbNeeded"], ["Windows.Win32.NetworkManagement.WindowsFilteringPlatform.FwpmProviderAdd0", "FwpmProviderAdd0", "SafeHandle engineHandle, in winmdroot.NetworkManagement.WindowsFilteringPlatform.FWPM_PROVIDER0 provider, [Optional] winmdroot.Security.PSECURITY_DESCRIPTOR sd"], // Verify the ABI signature has [Optional] on Optional and Reserved parameters. - ["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"], + ["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.NetworkManagement.WindowsFilteringPlatform.FWPM_ENGINE_HANDLE* engineHandle"], + // WlanCloseHandle accepts an additional reserved parameter. We can still generate safe hanlde for WlanOpenHandle then + ["WlanOpenHandle", "WlanOpenHandle", "uint dwClientVersion, out uint pdwNegotiatedVersion, out global::Windows.Win32.WlanCloseHandleSafeHandle phClientHandle"], ]; [Theory] @@ -245,9 +247,9 @@ private async Task VerifySignatureWorker(string api, string member, string signa this.nativeMethodsJson = nativeMethodsJson; // Make a unique name based on the signature - await this.InvokeGeneratorAndCompile($"{api}_{member}_{tfm}_{signature.Select(x => (int)x).Aggregate((x, y) => x + y).ToString("X")}"); + await this.InvokeGeneratorAndCompile($"{api}_{member}_{tfm}_{signature.Select(x => (int)x).Aggregate((x, y) => x + y):X}"); - var generatedMemberSignatures = this.FindGeneratedMethod(member).Select(x => x.ParameterList.ToString()); + var generatedMemberSignatures = this.FindGeneratedMethod(member).Select(x => x.ParameterList.ToString()).ToArray(); foreach (var generatedSignature in generatedMemberSignatures) { @@ -617,4 +619,30 @@ public async Task TestComVariantReturnValue() var method = Assert.Single(methods, m => m.Identifier.Text == "GetCachedPropertyValue"); Assert.Contains("ComVariant", method.ReturnType.ToString()); } + + [Theory, CombinatorialData] // https://github.com/microsoft/CsWin32/issues/1430 + public void UseInitHandleApiWhenPossible( + [CombinatorialValues( + "SysAllocString", // Returns owning safe handle + "ShellExecute", // Returns non-owning safe handle + "OpenProcessToken")] // Returns owning safe handle as an out parameter + string api, + bool initHandleApiAvailable) + { + this.compilation = this.starterCompilations[initHandleApiAvailable ? "net8.0" : "net472"]; + this.GenerateApi(api); + + MethodDeclarationSyntax friendlyOverload = Assert.Single( + this.FindGeneratedMethod(api), + m => !m.AttributeLists.Any(al => al.Attributes.Any(a => a.Name.ToString() == "DllImport"))); + + if (initHandleApiAvailable) + { + Assert.Contains(friendlyOverload.DescendantNodes(), n => n is IdentifierNameSyntax { Identifier.Text: "InitHandle" }); + } + else + { + Assert.DoesNotContain(friendlyOverload.DescendantNodes(), n => n is IdentifierNameSyntax { Identifier.Text: "InitHandle" }); + } + } } diff --git a/test/GenerationSandbox.BuildTask.Tests/COMTests.cs b/test/GenerationSandbox.BuildTask.Tests/COMTests.cs index a1b20b31..90e70c6e 100644 --- a/test/GenerationSandbox.BuildTask.Tests/COMTests.cs +++ b/test/GenerationSandbox.BuildTask.Tests/COMTests.cs @@ -4,20 +4,25 @@ #pragma warning disable IDE0005 #pragma warning disable SA1201, SA1512, SA1005, SA1507, SA1515, SA1403, SA1402, SA1411, SA1300, SA1313, SA1134, SA1307, SA1308, SA1202 +using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; using Microsoft.Win32.SafeHandles; using Windows.System; using Windows.UI.Composition; using Windows.Win32; +using Windows.Win32.Devices.DeviceAndDriverInstallation; using Windows.Win32.Foundation; using Windows.Win32.Graphics.Direct2D; using Windows.Win32.Graphics.Direct2D.Common; using Windows.Win32.Graphics.Direct3D; using Windows.Win32.Graphics.Direct3D11; using Windows.Win32.Graphics.Dxgi.Common; +using Windows.Win32.NetworkManagement.WindowsFirewall; using Windows.Win32.Storage.FileSystem; using Windows.Win32.System.Com; +using Windows.Win32.System.Ole; using Windows.Win32.System.WinRT.Composition; using Windows.Win32.System.Wmi; using Windows.Win32.UI.Shell; @@ -26,8 +31,10 @@ namespace GenerationSandbox.BuildTask.Tests; [Trait("WindowsOnly", "true")] -public partial class COMTests +public partial class COMTests(ITestOutputHelper outputHelper) { + private ITestOutputHelper outputHelper = outputHelper; + [Fact] public async Task CanInteropWithICompositorInterop() { @@ -228,4 +235,58 @@ public void IWbemServices_GetObject_Works() Assert.NotNull(pInParamsSignature); } + + [Fact] + [Trait("TestCategory", "FailsInCloudTest")] + public void CanCallINetFwMgrApis() + { + Assert.SkipUnless(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Test calls Windows-specific APIs"); + + var fwMgr = NetFwMgr.CreateInstance(); + var authorizedApplications = fwMgr.get_LocalPolicy().get_CurrentProfile().get_AuthorizedApplications(); + + var aaObjects = new ComVariant[authorizedApplications.get_Count()]; + + var applicationsEnum = (IEnumVARIANT)authorizedApplications.get__NewEnum(); + applicationsEnum.Next((uint)authorizedApplications.get_Count(), aaObjects, out uint fetched); + + foreach (var aaObject in aaObjects) + { + var app = (INetFwAuthorizedApplication)ComVariantMarshaller.ConvertToManaged(aaObject)!; + + this.outputHelper.WriteLine("---"); + this.outputHelper.WriteLine($"Name: {app.get_Name().ToString()}"); + this.outputHelper.WriteLine($"Enabled: {(bool)app.get_Enabled()}"); + this.outputHelper.WriteLine($"Remote Addresses: {app.get_RemoteAddresses().ToString()}"); + this.outputHelper.WriteLine($"Scope: {app.get_Scope()}"); + this.outputHelper.WriteLine($"Process Image Filename: {app.get_ProcessImageFileName().ToString()}"); + this.outputHelper.WriteLine($"IP Version: {app.get_IpVersion()}"); + + aaObject.Dispose(); + } + } + + [Fact] + [Trait("TestCategory", "FailsInCloudTest")] + public unsafe void CanCallPnPAPIs() + { + Assert.SkipUnless(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Test calls Windows-specific APIs"); + + using SafeHandle hDevInfo = PInvoke.SetupDiGetClassDevs( + Flags: SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_ALLCLASSES | SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_PRESENT); + + var devInfo = new SP_DEVINFO_DATA { cbSize = (uint)sizeof(SP_DEVINFO_DATA) }; + + uint index = 0; + while (PInvoke.SetupDiEnumDeviceInfo(hDevInfo, index++, ref devInfo)) + { + // NOTE: Omitting DeviceInstanceId requires naming the RequiredSize parameter. + PInvoke.SetupDiGetDeviceInstanceId(hDevInfo, in devInfo, RequiredSize: out uint requiredSize); + + Span instanceIdSpan = new char[(int)requiredSize]; + PInvoke.SetupDiGetDeviceInstanceId(hDevInfo, in devInfo, instanceIdSpan); + + this.outputHelper.WriteLine($"Device {devInfo.ClassGuid} Instance ID: {instanceIdSpan.ToString()}"); + } + } } diff --git a/test/GenerationSandbox.BuildTask.Tests/NativeMethods.txt b/test/GenerationSandbox.BuildTask.Tests/NativeMethods.txt index 96650b23..06cbb1bf 100644 --- a/test/GenerationSandbox.BuildTask.Tests/NativeMethods.txt +++ b/test/GenerationSandbox.BuildTask.Tests/NativeMethods.txt @@ -76,4 +76,11 @@ InitializeProcThreadAttributeList UpdateProcThreadAttribute DeleteProcThreadAttributeList PROC_THREAD_ATTRIBUTE_MACHINE_TYPE -FwpmProviderAdd0 \ No newline at end of file +FwpmProviderAdd0 +INetFwMgr +NetFwMgr +IEnumVARIANT +INetFwAuthorizedApplication +SetupDiGetClassDevs +SetupDiEnumDeviceInfo +SetupDiGetDeviceInstanceId \ No newline at end of file diff --git a/test/GenerationSandbox.Tests/ComRuntimeTests.cs b/test/GenerationSandbox.Tests/ComRuntimeTests.cs index 03ce2a81..f5f53274 100644 --- a/test/GenerationSandbox.Tests/ComRuntimeTests.cs +++ b/test/GenerationSandbox.Tests/ComRuntimeTests.cs @@ -9,14 +9,18 @@ using Windows.Win32.Graphics.Direct2D; using Windows.Win32.Graphics.Direct2D.Common; using Windows.Win32.Graphics.Dxgi.Common; +using Windows.Win32.NetworkManagement.WindowsFirewall; using Windows.Win32.System.Com; +using Windows.Win32.System.Ole; using Windows.Win32.System.Wmi; using Windows.Win32.UI.Shell; using Windows.Win32.UI.WindowsAndMessaging; using IServiceProvider = Windows.Win32.System.Com.IServiceProvider; -public class ComRuntimeTests +public class ComRuntimeTests(ITestOutputHelper outputHelper) { + private ITestOutputHelper outputHelper = outputHelper; + [Fact] [Trait("TestCategory", "FailsInCloudTest")] public void RemotableInterface() @@ -182,4 +186,32 @@ public void CanCallIDispatchOnlyMethods() _ = folderView.Application; // Throws InvalidOleVariantTypeException "Specified OLE variant is invalid" } + + [Fact] + [Trait("TestCategory", "FailsInCloudTest")] + public void CanCallINetFwMgrApis() + { + Assert.SkipUnless(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Test calls Windows-specific APIs"); + + var fwMgr = (INetFwMgr)new NetFwMgr(); + var authorizedApplications = fwMgr.LocalPolicy.CurrentProfile.AuthorizedApplications; + + var aaObjects = new object[authorizedApplications.Count]; + + var applicationsEnum = (IEnumVARIANT)authorizedApplications._NewEnum; + applicationsEnum.Next((uint)authorizedApplications.Count, aaObjects, out uint fetched); + + foreach (var aaObject in aaObjects) + { + var app = (INetFwAuthorizedApplication)aaObject; + + this.outputHelper.WriteLine("---"); + this.outputHelper.WriteLine($"Name: {app.Name.ToString()}"); + this.outputHelper.WriteLine($"Enabled: {(bool)app.Enabled}"); + this.outputHelper.WriteLine($"Remote Addresses: {app.RemoteAddresses.ToString()}"); + this.outputHelper.WriteLine($"Scope: {app.Scope}"); + this.outputHelper.WriteLine($"Process Image Filename: {app.ProcessImageFileName.ToString()}"); + this.outputHelper.WriteLine($"IP Version: {app.IpVersion}"); + } + } } diff --git a/test/GenerationSandbox.Tests/NativeMethods.txt b/test/GenerationSandbox.Tests/NativeMethods.txt index 79940515..10206954 100644 --- a/test/GenerationSandbox.Tests/NativeMethods.txt +++ b/test/GenerationSandbox.Tests/NativeMethods.txt @@ -100,4 +100,8 @@ RPC_C_IMP_LEVEL IShellFolderViewDual SID_STopLevelBrowser IShellBrowser -_SVGIO \ No newline at end of file +_SVGIO +INetFwMgr +NetFwMgr +IEnumVARIANT +INetFwAuthorizedApplication