Skip to content

Commit d8a1abc

Browse files
authored
Add more examples to the docs (#1597)
1 parent 4d68987 commit d8a1abc

File tree

5 files changed

+261
-21
lines changed

5 files changed

+261
-21
lines changed

docfx/docs/Examples.md

Lines changed: 153 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,7 @@
22

33
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.
44

5-
```json
6-
{
7-
"$schema": "https://aka.ms/CsWin32.schema.json",
8-
"useSafeHandles": false,
9-
"comInterop": {
10-
"preserveSigMethods": ["*"]
11-
}
12-
}
13-
```
14-
15-
## Scenario 1: Retrieving the last-write time
5+
## Retrieving the last-write time
166

177
Based on: [Retrieving the Last-Write Time](https://learn.microsoft.com/en-us/windows/win32/sysinfo/retrieving-the-last-write-time)
188

@@ -29,7 +19,6 @@ MAX_PATH
2919
```cs
3020
static void Main(string[] args)
3121
{
32-
SafeHandle hFile;
3322
Span<char> szBuf = stackalloc char[(int)PInvoke.MAX_PATH];
3423

3524
if (args.Length is not 1)
@@ -38,7 +27,7 @@ static void Main(string[] args)
3827
return;
3928
}
4029

41-
hFile = PInvoke.CreateFile(
30+
using SafeHandle hFile = PInvoke.CreateFile(
4231
args[0],
4332
(uint)GENERIC_ACCESS_RIGHTS.GENERIC_READ,
4433
FILE_SHARE_MODE.FILE_SHARE_READ,
@@ -52,8 +41,6 @@ static void Main(string[] args)
5241

5342
if (GetLastWriteTime(hFile, szBuf))
5443
Console.WriteLine("Last write time is: {0}\n", szBuf.ToString());
55-
56-
hFile.Close();
5744
}
5845

5946
static unsafe bool GetLastWriteTime(SafeHandle hFile, Span<char> lpszString)
@@ -77,7 +64,7 @@ static unsafe bool GetLastWriteTime(SafeHandle hFile, Span<char> lpszString)
7764
}
7865
```
7966

80-
## Scenario 2: Retrieving information about all of the display monitors
67+
## Retrieving information about all of the display monitors
8168

8269
```
8370
EnumDisplayMonitors
@@ -130,7 +117,7 @@ static unsafe BOOL MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, RECT* lprc
130117
}
131118
```
132119

133-
## Scenario 3: Use of SHCreateItemFromParsingName
120+
## Use of SHCreateItemFromParsingName
134121

135122
```
136123
IShellItem
@@ -185,3 +172,152 @@ static unsafe void Main(string[] args)
185172
psi->Release();
186173
}
187174
```
175+
176+
## Using COM classes (NetFwMgr, INetFwMgr)
177+
178+
In this example we see how to activate a COM class and call methods on it via its primary interface. When marshalling
179+
is enabled, we can use C# cast to do the "QueryInterface" that you would have seen in a native C++ sample.
180+
181+
```
182+
NetFwMgr
183+
INetFwMgr
184+
IEnumVARIANT
185+
INetFwAuthorizedApplication
186+
```
187+
188+
### Marshalling enabled (built-in COM Interop, not AOT-compatible)
189+
190+
```cs
191+
var fwMgr = (INetFwMgr)new NetFwMgr();
192+
var authorizedApplications = fwMgr.LocalPolicy.CurrentProfile.AuthorizedApplications;
193+
var aaObjects = new object[authorizedApplications.Count];
194+
var applicationsEnum = (IEnumVARIANT)authorizedApplications._NewEnum;
195+
applicationsEnum.Next((uint)authorizedApplications.Count, aaObjects, out uint fetched);
196+
foreach (var aaObject in aaObjects)
197+
{
198+
var app = (INetFwAuthorizedApplication)aaObject;
199+
Console.WriteLine("---");
200+
Console.WriteLine($"Name: {app.Name.ToString()}");
201+
Console.WriteLine($"Enabled: {(bool)app.Enabled}");
202+
Console.WriteLine($"Remote Addresses: {app.RemoteAddresses.ToString()}");
203+
Console.WriteLine($"Scope: {app.Scope}");
204+
Console.WriteLine($"Process Image Filename: {app.ProcessImageFileName.ToString()}");
205+
Console.WriteLine($"IP Version: {app.IpVersion}");
206+
}
207+
```
208+
209+
### Marshalling enabled (COM wrappers, AOT compatible)
210+
211+
Note that in COM wrappers mode, the generated interfaces have get_ methods instead of properties. Some parameters
212+
are also ComVariant instead of object because source generated COM does not support as much automatic marshalling
213+
as built-in COM does.
214+
215+
```cs
216+
var fwMgr = NetFwMgr.CreateInstance<INetFwMgr>();
217+
var authorizedApplications = fwMgr.get_LocalPolicy().get_CurrentProfile().get_AuthorizedApplications();
218+
var aaObjects = new ComVariant[authorizedApplications.get_Count()];
219+
var applicationsEnum = (IEnumVARIANT)authorizedApplications.get__NewEnum();
220+
applicationsEnum.Next((uint)authorizedApplications.get_Count(), aaObjects, out uint fetched);
221+
foreach (var aaObject in aaObjects)
222+
{
223+
var app = (INetFwAuthorizedApplication)ComVariantMarshaller.ConvertToManaged(aaObject)!;
224+
225+
Console.WriteLine("---");
226+
Console.WriteLine($"Name: {app.get_Name().ToString()}");
227+
Console.WriteLine($"Enabled: {(bool)app.get_Enabled()}");
228+
Console.WriteLine($"Remote Addresses: {app.get_RemoteAddresses().ToString()}");
229+
Console.WriteLine($"Scope: {app.get_Scope()}");
230+
Console.WriteLine($"Process Image Filename: {app.get_ProcessImageFileName().ToString()}");
231+
Console.WriteLine($"IP Version: {app.get_IpVersion()}");
232+
233+
aaObject.Dispose();
234+
}
235+
```
236+
237+
## Use PNP APIs (shows omitted optional params and cbSize-d struct)
238+
239+
This sample shows how to call an API where we've omitted some optional params -- note that we must
240+
use named parameters when passing parameters past the omitted optional ones. This also shows how to
241+
use Span APIs, and in this case one where we first call the API to get the buffer size, create the buffer
242+
and then call again to populate the buffer.
243+
244+
```
245+
SetupDiGetClassDevs
246+
SetupDiEnumDeviceInfo
247+
SetupDiGetDeviceInstanceId
248+
```
249+
250+
```cs
251+
using SafeHandle hDevInfo = PInvoke.SetupDiGetClassDevs(
252+
Flags: SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_ALLCLASSES | SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_PRESENT);
253+
254+
var devInfo = new SP_DEVINFO_DATA { cbSize = (uint)sizeof(SP_DEVINFO_DATA) };
255+
256+
uint index = 0;
257+
while (PInvoke.SetupDiEnumDeviceInfo(hDevInfo, index++, ref devInfo))
258+
{
259+
PInvoke.SetupDiGetDeviceInstanceId(hDevInfo, in devInfo, RequiredSize: out uint requiredSize);
260+
261+
Span<char> instanceIdSpan = new char[(int)requiredSize];
262+
PInvoke.SetupDiGetDeviceInstanceId(hDevInfo, in devInfo, instanceIdSpan);
263+
264+
Console.WriteLine($"Device {devInfo.ClassGuid} Instance ID: {instanceIdSpan.ToString()}");
265+
}
266+
```
267+
268+
## Pass struct as a Span<byte>
269+
270+
In this short example, we see how to pass a struct to a method that accepts a `Span<byte>`. `new Span<SHFILEINFOW>(ref fileInfo)` lets us
271+
get a `Span<SHFILEINFOW>` and then `MemoryMarshal.AsBytes` reinterprets that same Span as a `Span<byte>` with the expected size. The cswin32
272+
method will pass the Span's length to the native method as the "cb" count bytes parameter.
273+
274+
```cs
275+
SHFILEINFOW fileInfo = default;
276+
PInvoke.SHGetFileInfo(
277+
"c:\\windows\\notepad.exe",
278+
FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL,
279+
MemoryMarshal.AsBytes(new Span<SHFILEINFOW>(ref fileInfo)),
280+
SHGFI_FLAGS.SHGFI_DISPLAYNAME);
281+
```
282+
283+
## Omitting optional out/ref parameters
284+
285+
APIs with optional `in` parameters are tagged with `[Optional]` attribute and such parameters can be omitted, but APIs
286+
with optional `out` or `ref` parameters must always be passed. When the native method has these as `[optional]` and you need
287+
to pass _some_ but not all of those parameters, you can pass "null" to the native method using `ref Unsafe.NullRef<T>()`.
288+
289+
This sample also shows passing `null` for SafeHandle-typed parameters which are not optional per the SDK headers but the
290+
implementation allows for them to be null.
291+
292+
This sample shows a number of advanced COM marshalling scenarios
293+
294+
### Marshalling enabled (COM wrappers, AOT compatible)
295+
296+
```cs
297+
// CoCreateInstance CLSID_WbemLocator
298+
IWbemLocator locator = WbemLocator.CreateInstance<IWbemLocator>();
299+
300+
var ns = new SysFreeStringSafeHandle(Marshal.StringToBSTR(@"ROOT\Microsoft\Windows\Defender"), true);
301+
locator.ConnectServer(ns, new SysFreeStringSafeHandle(), new SysFreeStringSafeHandle(), new SysFreeStringSafeHandle(), 0, new SafeFileHandle(), null, out IWbemServices services);
302+
303+
unsafe
304+
{
305+
PInvoke.CoSetProxyBlanket(
306+
services,
307+
10, // RPC_C_AUTHN_WINNT is 10
308+
0, // RPC_C_AUTHZ_NONE is 0
309+
pServerPrincName: null,
310+
dwAuthnLevel: RPC_C_AUTHN_LEVEL.RPC_C_AUTHN_LEVEL_CALL,
311+
dwImpLevel: RPC_C_IMP_LEVEL.RPC_C_IMP_LEVEL_IMPERSONATE,
312+
pAuthInfo: null,
313+
dwCapabilities: EOLE_AUTHENTICATION_CAPABILITIES.EOAC_NONE);
314+
}
315+
316+
var className = new SysFreeStringSafeHandle(Marshal.StringToBSTR("MSFT_MpScan"), true);
317+
IWbemClassObject? classObj = null; // out param
318+
319+
services.GetObject(className, WBEM_GENERIC_FLAG_TYPE.WBEM_FLAG_RETURN_WBEM_COMPLETE, null, ref classObj, ref Unsafe.NullRef<IWbemCallResult>());
320+
321+
classObj.GetMethod("Start", 0, out IWbemClassObject pInParamsSignature, out IWbemClassObject ppOutSignature);
322+
323+
```

test/GenerationSandbox.BuildTask.Tests/COMTests.cs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,25 @@
44
#pragma warning disable IDE0005
55
#pragma warning disable SA1201, SA1512, SA1005, SA1507, SA1515, SA1403, SA1402, SA1411, SA1300, SA1313, SA1134, SA1307, SA1308, SA1202
66

7+
using System.ComponentModel;
78
using System.Runtime.CompilerServices;
89
using System.Runtime.InteropServices;
10+
using System.Runtime.InteropServices.Marshalling;
911
using Microsoft.Win32.SafeHandles;
1012
using Windows.System;
1113
using Windows.UI.Composition;
1214
using Windows.Win32;
15+
using Windows.Win32.Devices.DeviceAndDriverInstallation;
1316
using Windows.Win32.Foundation;
1417
using Windows.Win32.Graphics.Direct2D;
1518
using Windows.Win32.Graphics.Direct2D.Common;
1619
using Windows.Win32.Graphics.Direct3D;
1720
using Windows.Win32.Graphics.Direct3D11;
1821
using Windows.Win32.Graphics.Dxgi.Common;
22+
using Windows.Win32.NetworkManagement.WindowsFirewall;
1923
using Windows.Win32.Storage.FileSystem;
2024
using Windows.Win32.System.Com;
25+
using Windows.Win32.System.Ole;
2126
using Windows.Win32.System.WinRT.Composition;
2227
using Windows.Win32.System.Wmi;
2328
using Windows.Win32.UI.Shell;
@@ -26,8 +31,10 @@
2631
namespace GenerationSandbox.BuildTask.Tests;
2732

2833
[Trait("WindowsOnly", "true")]
29-
public partial class COMTests
34+
public partial class COMTests(ITestOutputHelper outputHelper)
3035
{
36+
private ITestOutputHelper outputHelper = outputHelper;
37+
3138
[Fact]
3239
public async Task CanInteropWithICompositorInterop()
3340
{
@@ -228,4 +235,58 @@ public void IWbemServices_GetObject_Works()
228235

229236
Assert.NotNull(pInParamsSignature);
230237
}
238+
239+
[Fact]
240+
[Trait("TestCategory", "FailsInCloudTest")]
241+
public void CanCallINetFwMgrApis()
242+
{
243+
Assert.SkipUnless(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Test calls Windows-specific APIs");
244+
245+
var fwMgr = NetFwMgr.CreateInstance<INetFwMgr>();
246+
var authorizedApplications = fwMgr.get_LocalPolicy().get_CurrentProfile().get_AuthorizedApplications();
247+
248+
var aaObjects = new ComVariant[authorizedApplications.get_Count()];
249+
250+
var applicationsEnum = (IEnumVARIANT)authorizedApplications.get__NewEnum();
251+
applicationsEnum.Next((uint)authorizedApplications.get_Count(), aaObjects, out uint fetched);
252+
253+
foreach (var aaObject in aaObjects)
254+
{
255+
var app = (INetFwAuthorizedApplication)ComVariantMarshaller.ConvertToManaged(aaObject)!;
256+
257+
this.outputHelper.WriteLine("---");
258+
this.outputHelper.WriteLine($"Name: {app.get_Name().ToString()}");
259+
this.outputHelper.WriteLine($"Enabled: {(bool)app.get_Enabled()}");
260+
this.outputHelper.WriteLine($"Remote Addresses: {app.get_RemoteAddresses().ToString()}");
261+
this.outputHelper.WriteLine($"Scope: {app.get_Scope()}");
262+
this.outputHelper.WriteLine($"Process Image Filename: {app.get_ProcessImageFileName().ToString()}");
263+
this.outputHelper.WriteLine($"IP Version: {app.get_IpVersion()}");
264+
265+
aaObject.Dispose();
266+
}
267+
}
268+
269+
[Fact]
270+
[Trait("TestCategory", "FailsInCloudTest")]
271+
public unsafe void CanCallPnPAPIs()
272+
{
273+
Assert.SkipUnless(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Test calls Windows-specific APIs");
274+
275+
using SafeHandle hDevInfo = PInvoke.SetupDiGetClassDevs(
276+
Flags: SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_ALLCLASSES | SETUP_DI_GET_CLASS_DEVS_FLAGS.DIGCF_PRESENT);
277+
278+
var devInfo = new SP_DEVINFO_DATA { cbSize = (uint)sizeof(SP_DEVINFO_DATA) };
279+
280+
uint index = 0;
281+
while (PInvoke.SetupDiEnumDeviceInfo(hDevInfo, index++, ref devInfo))
282+
{
283+
// NOTE: Omitting DeviceInstanceId requires naming the RequiredSize parameter.
284+
PInvoke.SetupDiGetDeviceInstanceId(hDevInfo, in devInfo, RequiredSize: out uint requiredSize);
285+
286+
Span<char> instanceIdSpan = new char[(int)requiredSize];
287+
PInvoke.SetupDiGetDeviceInstanceId(hDevInfo, in devInfo, instanceIdSpan);
288+
289+
this.outputHelper.WriteLine($"Device {devInfo.ClassGuid} Instance ID: {instanceIdSpan.ToString()}");
290+
}
291+
}
231292
}

test/GenerationSandbox.BuildTask.Tests/NativeMethods.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,11 @@ InitializeProcThreadAttributeList
7676
UpdateProcThreadAttribute
7777
DeleteProcThreadAttributeList
7878
PROC_THREAD_ATTRIBUTE_MACHINE_TYPE
79-
FwpmProviderAdd0
79+
FwpmProviderAdd0
80+
INetFwMgr
81+
NetFwMgr
82+
IEnumVARIANT
83+
INetFwAuthorizedApplication
84+
SetupDiGetClassDevs
85+
SetupDiEnumDeviceInfo
86+
SetupDiGetDeviceInstanceId

test/GenerationSandbox.Tests/ComRuntimeTests.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@
99
using Windows.Win32.Graphics.Direct2D;
1010
using Windows.Win32.Graphics.Direct2D.Common;
1111
using Windows.Win32.Graphics.Dxgi.Common;
12+
using Windows.Win32.NetworkManagement.WindowsFirewall;
1213
using Windows.Win32.System.Com;
14+
using Windows.Win32.System.Ole;
1315
using Windows.Win32.System.Wmi;
1416
using Windows.Win32.UI.Shell;
1517
using Windows.Win32.UI.WindowsAndMessaging;
1618
using IServiceProvider = Windows.Win32.System.Com.IServiceProvider;
1719

18-
public class ComRuntimeTests
20+
public class ComRuntimeTests(ITestOutputHelper outputHelper)
1921
{
22+
private ITestOutputHelper outputHelper = outputHelper;
23+
2024
[Fact]
2125
[Trait("TestCategory", "FailsInCloudTest")]
2226
public void RemotableInterface()
@@ -182,4 +186,32 @@ public void CanCallIDispatchOnlyMethods()
182186

183187
_ = folderView.Application; // Throws InvalidOleVariantTypeException "Specified OLE variant is invalid"
184188
}
189+
190+
[Fact]
191+
[Trait("TestCategory", "FailsInCloudTest")]
192+
public void CanCallINetFwMgrApis()
193+
{
194+
Assert.SkipUnless(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), "Test calls Windows-specific APIs");
195+
196+
var fwMgr = (INetFwMgr)new NetFwMgr();
197+
var authorizedApplications = fwMgr.LocalPolicy.CurrentProfile.AuthorizedApplications;
198+
199+
var aaObjects = new object[authorizedApplications.Count];
200+
201+
var applicationsEnum = (IEnumVARIANT)authorizedApplications._NewEnum;
202+
applicationsEnum.Next((uint)authorizedApplications.Count, aaObjects, out uint fetched);
203+
204+
foreach (var aaObject in aaObjects)
205+
{
206+
var app = (INetFwAuthorizedApplication)aaObject;
207+
208+
this.outputHelper.WriteLine("---");
209+
this.outputHelper.WriteLine($"Name: {app.Name.ToString()}");
210+
this.outputHelper.WriteLine($"Enabled: {(bool)app.Enabled}");
211+
this.outputHelper.WriteLine($"Remote Addresses: {app.RemoteAddresses.ToString()}");
212+
this.outputHelper.WriteLine($"Scope: {app.Scope}");
213+
this.outputHelper.WriteLine($"Process Image Filename: {app.ProcessImageFileName.ToString()}");
214+
this.outputHelper.WriteLine($"IP Version: {app.IpVersion}");
215+
}
216+
}
185217
}

test/GenerationSandbox.Tests/NativeMethods.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,8 @@ RPC_C_IMP_LEVEL
100100
IShellFolderViewDual
101101
SID_STopLevelBrowser
102102
IShellBrowser
103-
_SVGIO
103+
_SVGIO
104+
INetFwMgr
105+
NetFwMgr
106+
IEnumVARIANT
107+
INetFwAuthorizedApplication

0 commit comments

Comments
 (0)