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