Skip to content

Commit 15c4dc8

Browse files
authored
Merge pull request #1208 from microsoft/fix1199
Avoid `Unsafe.SkipInit` API when it isn't available
2 parents 8a99259 + f15c83a commit 15c4dc8

File tree

7 files changed

+65
-12
lines changed

7 files changed

+65
-12
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,20 @@ Install the `Microsoft.Windows.CsWin32` package:
3333
dotnet add package Microsoft.Windows.CsWin32 --prerelease
3434
```
3535

36-
You should also install the `System.Memory` package when targeting .NET Framework 4.5+ or .NET Standard 2.0,
37-
as that adds APIs that significantly improve much of the code generated by CsWin32:
36+
You should also install the `System.Memory` and `System.Runtime.CompilerServices.Unsafe` packages when targeting .NET Framework 4.5+ or .NET Standard 2.0,
37+
as these add APIs that significantly improve much of the code generated by CsWin32:
3838

3939
```ps1
4040
dotnet add package System.Memory
41+
dotnet add package System.Runtime.CompilerServices.Unsafe
4142
```
4243

43-
Projects targeting .NET Core 2.1+ or .NET 5+ do *not* need to add the `System.Memory` package reference,
44+
Projects targeting .NET Core 2.1+ or .NET 5+ do *not* need to add these package references,
4445
although it is harmless to do so.
4546

47+
Note that while the `System.Memory` package depends on the `System.Runtime.CompilerServices.Unsafe` package,
48+
referencing the latter directly is still important to get the latest version of the APIs it provides.
49+
4650
Your project must allow unsafe code to support the generated code that will likely use pointers.
4751
This does *not* automatically make all your code *unsafe*.
4852
Use of the `unsafe` keyword is required anywhere you use pointers.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public partial class Generator
1010
private readonly bool canUseUnsafeAsRef;
1111
private readonly bool canUseUnsafeAdd;
1212
private readonly bool canUseUnsafeNullRef;
13+
private readonly bool canUseUnsafeSkipInit;
1314
private readonly bool canUseUnmanagedCallersOnlyAttribute;
1415
private readonly bool canUseSetLastPInvokeError;
1516
private readonly bool unscopedRefAttributePredefined;

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -605,11 +605,20 @@ InvocationExpressionSyntax SliceAtNullToString(ExpressionSyntax readOnlySpan)
605605
? MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, resultLocal, valueFieldName) // result.Value
606606
: PrefixUnaryExpression(SyntaxKind.AddressOfExpression, MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, resultLocal, firstElementName!)); // &result._0
607607

608-
// Unsafe.SkipInit(out __char_1 result);
609-
implicitSpanToStruct = implicitSpanToStruct.AddBodyStatements(
610-
ExpressionStatement(InvocationExpression(
611-
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(nameof(Unsafe)), IdentifierName("SkipInit")),
612-
ArgumentList().AddArguments(Argument(nameColon: null, Token(SyntaxKind.OutKeyword), DeclarationExpression(fixedLengthStructName.WithTrailingTrivia(Space), SingleVariableDesignation(resultLocal.Identifier)))))));
608+
if (this.canUseUnsafeSkipInit)
609+
{
610+
// Unsafe.SkipInit(out __char_1 result);
611+
implicitSpanToStruct = implicitSpanToStruct.AddBodyStatements(
612+
ExpressionStatement(InvocationExpression(
613+
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName(nameof(Unsafe)), IdentifierName("SkipInit")),
614+
ArgumentList().AddArguments(Argument(nameColon: null, Token(SyntaxKind.OutKeyword), DeclarationExpression(fixedLengthStructName.WithTrailingTrivia(Space), SingleVariableDesignation(resultLocal.Identifier)))))));
615+
}
616+
else
617+
{
618+
implicitSpanToStruct = implicitSpanToStruct.AddBodyStatements(
619+
LocalDeclarationStatement(VariableDeclaration(fixedLengthStructName)).AddDeclarationVariables(
620+
VariableDeclarator(resultLocal.Identifier).WithInitializer(EqualsValueClause(DefaultExpression(fixedLengthStructName)))));
621+
}
613622

614623
// x.Slice(initLength, Length - initLength).Clear();
615624
StatementSyntax ClearSlice(ExpressionSyntax span) =>

src/Microsoft.Windows.CsWin32/Generator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public Generator(string metadataLibraryPath, Docs? docs, GeneratorOptions option
9090
this.canUseUnsafeAsRef = this.compilation?.GetTypeByMetadataName(typeof(Unsafe).FullName)?.GetMembers("Add").Any() is true;
9191
this.canUseUnsafeAdd = this.compilation?.GetTypeByMetadataName(typeof(Unsafe).FullName)?.GetMembers("AsRef").Any() is true;
9292
this.canUseUnsafeNullRef = this.compilation?.GetTypeByMetadataName(typeof(Unsafe).FullName)?.GetMembers("NullRef").Any() is true;
93+
this.canUseUnsafeSkipInit = this.compilation?.GetTypeByMetadataName(typeof(Unsafe).FullName)?.GetMembers("SkipInit").Any() is true;
9394
this.canUseUnmanagedCallersOnlyAttribute = this.compilation?.GetTypeByMetadataName("System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute") is not null;
9495
this.canUseSetLastPInvokeError = this.compilation?.GetTypeByMetadataName("System.Runtime.InteropServices.Marshal")?.GetMembers("GetLastSystemError").IsEmpty is false;
9596
this.unscopedRefAttributePredefined = this.FindTypeSymbolIfAlreadyAvailable("System.Diagnostics.CodeAnalysis.UnscopedRefAttribute") is not null;

src/Microsoft.Windows.CsWin32/readme.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ as that adds APIs that significantly improve much of the code generated by CsWin
1212

1313
```ps1
1414
dotnet add package System.Memory
15+
dotnet add package System.Runtime.CompilerServices.Unsafe
1516
```
1617

17-
Projects targeting .NET Core 2.1+ or .NET 5+ do *not* need to add the `System.Memory` package reference,
18+
Projects targeting .NET Core 2.1+ or .NET 5+ do *not* need to add these package references,
1819
although it is harmless to do so.
1920

21+
Note that while the `System.Memory` package depends on the `System.Runtime.CompilerServices.Unsafe` package,
22+
referencing the latter directly is still important to get the latest version of the APIs it provides.
23+
2024
Your project must allow unsafe code to support the generated code that will likely use pointers.
2125

2226
Learn more from our README on GitHub: https://github.com/microsoft/CsWin32#readme

test/Microsoft.Windows.CsWin32.Tests/InlineArrayTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,33 @@ public void FixedLengthInlineArray(
3131
this.AssertNoDiagnostics();
3232
}
3333

34+
[Theory, CombinatorialData]
35+
public async Task UnsafeApiReferencesOnlyWhenAvailable(
36+
bool referenceUnsafe,
37+
bool referenceMemory,
38+
[CombinatorialValues("net35", "net472", "netstandard2.0")] string tfm)
39+
{
40+
ReferenceAssemblies referenceAssemblies = tfm switch
41+
{
42+
"net35" => ReferenceAssemblies.NetFramework.Net35.WindowsForms,
43+
"net472" => ReferenceAssemblies.NetFramework.Net472.WindowsForms,
44+
"netstandard2.0" => ReferenceAssemblies.NetStandard.NetStandard20,
45+
_ => throw new ArgumentOutOfRangeException(nameof(tfm)),
46+
};
47+
if (referenceUnsafe)
48+
{
49+
referenceAssemblies = referenceAssemblies.AddPackages([MyReferenceAssemblies.ExtraPackages.Unsafe]);
50+
}
51+
52+
if (referenceMemory)
53+
{
54+
referenceAssemblies = referenceAssemblies.AddPackages([MyReferenceAssemblies.ExtraPackages.Memory]);
55+
}
56+
57+
this.compilation = await this.CreateCompilationAsync(referenceAssemblies, Platform.AnyCpu);
58+
this.GenerateApi("THUMBBUTTON");
59+
}
60+
3461
[Theory, PairwiseData]
3562
public void FixedLengthInlineArray_Pointers(
3663
bool allowMarshaling,

test/Microsoft.Windows.CsWin32.Tests/MyReferenceAssemblies.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ internal static class MyReferenceAssemblies
88
new PackageIdentity("Microsoft.Windows.SDK.Contracts", "10.0.22621.2428"));
99

1010
private static readonly ImmutableArray<PackageIdentity> AdditionalModernPackages = AdditionalLegacyPackages.AddRange(ImmutableArray.Create(
11-
new PackageIdentity("System.Runtime.CompilerServices.Unsafe", "6.0.0"),
12-
new PackageIdentity("System.Memory", "4.5.5"),
13-
new PackageIdentity("Microsoft.Win32.Registry", "5.0.0")));
11+
ExtraPackages.Unsafe,
12+
ExtraPackages.Memory,
13+
ExtraPackages.Registry));
1414

1515
internal static readonly ReferenceAssemblies NetStandard20 = ReferenceAssemblies.NetStandard.NetStandard20.AddPackages(AdditionalModernPackages);
1616
#pragma warning restore SA1202 // Elements should be ordered by access
@@ -26,4 +26,11 @@ internal static class Net
2626
internal static readonly ReferenceAssemblies Net60 = ReferenceAssemblies.Net.Net60.AddPackages(AdditionalModernPackages);
2727
internal static readonly ReferenceAssemblies Net70 = ReferenceAssemblies.Net.Net70.AddPackages(AdditionalModernPackages);
2828
}
29+
30+
internal static class ExtraPackages
31+
{
32+
internal static readonly PackageIdentity Unsafe = new PackageIdentity("System.Runtime.CompilerServices.Unsafe", "6.0.0");
33+
internal static readonly PackageIdentity Memory = new PackageIdentity("System.Memory", "4.5.5");
34+
internal static readonly PackageIdentity Registry = new PackageIdentity("Microsoft.Win32.Registry", "5.0.0");
35+
}
2936
}

0 commit comments

Comments
 (0)