Skip to content

Commit

Permalink
Update to metadata v10.0.19041.5-preview.20
Browse files Browse the repository at this point in the history
Closes #121
Fixes #64
  • Loading branch information
AArnott committed Feb 22, 2021
1 parent 90925eb commit 659d266
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 93 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>

<MetadataVersion>10.0.19041.5-preview.5</MetadataVersion>
<MetadataVersion>10.0.19041.5-preview.20</MetadataVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
282 changes: 206 additions & 76 deletions src/Microsoft.Windows.CsWin32/Generator.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<YamlDocOutputPath Include="../../obj/$(Configuration)/apidocs.yml" Visible="false" />
</ItemGroup>

<ItemGroup>
<Compile Remove="templates\*.cs" />
</ItemGroup>

<ItemGroup>
<None Update="readme.txt" Pack="true" PackagePath="" />
<None Include="build\**">
Expand All @@ -31,6 +35,7 @@
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="templates\*.cs" />
<EmbeddedResource Include="@(YamlDocOutputPath)" />
</ItemGroup>

Expand Down
47 changes: 47 additions & 0 deletions src/Microsoft.Windows.CsWin32/templates/PCSTR.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/// <summary>
/// A pointer to a constant character string.
/// </summary>
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
internal unsafe readonly partial struct PCSTR : IEquatable<PCSTR>
{
/// <summary>
/// A pointer to the first character in the string. The content should be considered readonly, as it was typed as constant in the SDK.
/// </summary>
internal readonly byte* Value;
internal PCSTR(byte* value) => this.Value = value;
public static implicit operator byte*(PCSTR value) => value.Value;
public static explicit operator PCSTR(byte* value) => new PCSTR(value);
public static implicit operator PCSTR(PSTR value) => new PCSTR(value.Value);
public bool Equals(PCSTR other) => this.Value == other.Value;
public override bool Equals(object obj) => obj is PCSTR other && this.Equals(other);
public override int GetHashCode() => unchecked((int)this.Value);

/// <summary>
/// Gets the number of characters up to the first null character (exclusive).
/// </summary>
internal int Length
{
get
{
byte* p = this.Value;
if (p is null)
return 0;
while (*p != 0)
p++;
return checked((int)(p - this.Value));
}
}

/// <summary>
/// Returns a <see langword="string"/> with a copy of this character array, decoding as UTF-8.
/// </summary>
/// <returns>A <see langword="string"/>, or <see langword="null"/> if <see cref="Value"/> is <see langword="null"/>.</returns>
public override string ToString() => this.Value is null ? null : new string((sbyte*)this.Value, 0, this.Length, global::System.Text.Encoding.UTF8);

/// <summary>
/// Returns a span of the characters in this string.
/// </summary>
internal ReadOnlySpan<byte> AsSpan() => this.Value is null ? default(ReadOnlySpan<byte>) : new ReadOnlySpan<byte>(this.Value, this.Length);

private string DebuggerDisplay => this.ToString();
}
47 changes: 47 additions & 0 deletions src/Microsoft.Windows.CsWin32/templates/PCWSTR.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/// <summary>
/// A pointer to a constant character string.
/// </summary>
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
internal unsafe readonly partial struct PCWSTR : IEquatable<PCWSTR>
{
/// <summary>
/// A pointer to the first character in the string. The content should be considered readonly, as it was typed as constant in the SDK.
/// </summary>
internal readonly char* Value;
internal PCWSTR(char* value) => this.Value = value;
public static explicit operator char*(PCWSTR value) => value.Value;
public static implicit operator PCWSTR(char* value) => new PCWSTR(value);
public static implicit operator PCWSTR(PWSTR value) => new PCWSTR(value.Value);
public bool Equals(PCWSTR other) => this.Value == other.Value;
public override bool Equals(object obj) => obj is PCWSTR other && this.Equals(other);
public override int GetHashCode() => unchecked((int)this.Value);

/// <summary>
/// Gets the number of characters up to the first null character (exclusive).
/// </summary>
internal int Length
{
get
{
char* p = this.Value;
if (p is null)
return 0;
while (*p != '\0')
p++;
return checked((int)(p - this.Value));
}
}

/// <summary>
/// Returns a <see langword="string"/> with a copy of this character array.
/// </summary>
/// <returns>A <see langword="string"/>, or <see langword="null"/> if <see cref="Value"/> is <see langword="null"/>.</returns>
public override string ToString() => this.Value is null ? null : new string(this.Value);

/// <summary>
/// Returns a span of the characters in this string.
/// </summary>
internal ReadOnlySpan<char> AsSpan() => this.Value is null ? default(ReadOnlySpan<char>) : new ReadOnlySpan<char>(this.Value, this.Length);

private string DebuggerDisplay => this.ToString();
}
67 changes: 60 additions & 7 deletions test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public void SimplestMethod()
[InlineData("GetDiskFreeSpaceExW")] // ULARGE_INTEGER replaced with keyword: ulong.
public void InterestingAPIs(string api)
{
this.generator = new Generator(this.metadataStream, options: new GeneratorOptions { EmitSingleFile = true }, compilation: this.compilation, parseOptions: this.parseOptions);
this.generator = new Generator(this.metadataStream, options: new GeneratorOptions { EmitSingleFile = true, WideCharOnly = false }, compilation: this.compilation, parseOptions: this.parseOptions);
Assert.True(this.generator.TryGenerate(api, CancellationToken.None));
this.CollectGeneratedCode(this.generator);
this.AssertNoDiagnostics();
Expand Down Expand Up @@ -164,7 +164,7 @@ public void CreateFileUsesSafeHandles()
Assert.True(this.generator.TryGenerate("CreateFile", CancellationToken.None));
this.CollectGeneratedCode(this.generator);
this.AssertNoDiagnostics();
MethodDeclarationSyntax? createFileMethod = this.FindGeneratedMethod("CreateFile");
MethodDeclarationSyntax? createFileMethod = this.FindGeneratedMethod("CreateFile").FirstOrDefault();
Assert.NotNull(createFileMethod);
Assert.Equal("Microsoft.Win32.SafeHandles.SafeFileHandle", createFileMethod!.ReturnType.ToString());
Assert.Equal("SafeHandle", createFileMethod.ParameterList.Parameters.Last().Type?.ToString());
Expand All @@ -177,19 +177,30 @@ public void BOOL_ReturnTypeBecomes_Boolean()
Assert.True(this.generator.TryGenerate("WinUsb_FlushPipe", CancellationToken.None));
this.CollectGeneratedCode(this.generator);
this.AssertNoDiagnostics();
MethodDeclarationSyntax? createFileMethod = this.FindGeneratedMethod("WinUsb_FlushPipe");
MethodDeclarationSyntax? createFileMethod = this.FindGeneratedMethod("WinUsb_FlushPipe").FirstOrDefault();
Assert.NotNull(createFileMethod);
Assert.Equal(SyntaxKind.BoolKeyword, Assert.IsType<PredefinedTypeSyntax>(createFileMethod!.ReturnType).Keyword.Kind());
}

[Fact]
public void NativeArray_SizeParamIndex_ProducesSimplerFriendlyOverload()
{
this.generator = new Generator(this.metadataStream, compilation: this.compilation, parseOptions: this.parseOptions);
Assert.True(this.generator.TryGenerate("EvtNext", CancellationToken.None));
this.CollectGeneratedCode(this.generator);
this.AssertNoDiagnostics();
IEnumerable<MethodDeclarationSyntax> overloads = this.FindGeneratedMethod("EvtNext");
Assert.NotEmpty(overloads.Where(o => o.ParameterList.Parameters.Count == 5 && (o.ParameterList.Parameters[1].Type?.ToString().StartsWith("Span<", StringComparison.Ordinal) ?? false)));
}

[Fact]
public void BOOL_ReturnTypeBecomes_Boolean_InCOMInterface()
{
this.generator = new Generator(this.metadataStream, compilation: this.compilation, parseOptions: this.parseOptions);
Assert.True(this.generator.TryGenerate("ISpellCheckerFactory", CancellationToken.None));
this.CollectGeneratedCode(this.generator);
this.AssertNoDiagnostics();
MethodDeclarationSyntax? method = this.FindGeneratedMethod("IsSupported");
MethodDeclarationSyntax? method = this.FindGeneratedMethod("IsSupported").FirstOrDefault();
Assert.NotNull(method);
Assert.Equal(SyntaxKind.BoolKeyword, Assert.IsType<PredefinedTypeSyntax>(method!.ParameterList.Parameters.Last().Type).Keyword.Kind());
}
Expand Down Expand Up @@ -221,6 +232,48 @@ public void BSTR_FieldsDoNotBecomeSafeHandles()
Assert.Equal("BSTR", ((IdentifierNameSyntax)bstrField.Declaration.Type).Identifier.ValueText);
}

/// <summary>
/// Verifies that MSIHANDLE is not replaced with a SafeHandle, since it uses uint instead of IntPtr for its underlying type.
/// </summary>
[Fact]
public void MSIHANDLE_DoesNotBecomeSafeHandle()
{
this.generator = new Generator(this.metadataStream, compilation: this.compilation, parseOptions: this.parseOptions);
Assert.True(this.generator.TryGenerate("MsiGetLastErrorRecord", CancellationToken.None));
this.CollectGeneratedCode(this.generator);
this.AssertNoDiagnostics();

MethodDeclarationSyntax? method = this.FindGeneratedMethod("MsiGetLastErrorRecord").FirstOrDefault();
Assert.NotNull(method);
Assert.Equal("MSIHANDLE", method!.ReturnType?.ToString());

MethodDeclarationSyntax? releaseMethod = this.FindGeneratedMethod("MsiCloseHandle").FirstOrDefault();
Assert.NotNull(method);
Assert.Equal("MSIHANDLE", Assert.IsType<IdentifierNameSyntax>(releaseMethod!.ParameterList.Parameters[0].Type).Identifier.ValueText);
}

[Fact]
public void Const_PWSTR_Becomes_PCWSTR_and_String()
{
this.generator = new Generator(this.metadataStream, compilation: this.compilation, parseOptions: this.parseOptions);
Assert.True(this.generator.TryGenerate("StrCmpLogical", CancellationToken.None));
this.CollectGeneratedCode(this.generator);
this.AssertNoDiagnostics();

bool foundPCWSTROverload = false;
bool foundStringOverload = false;
IEnumerable<MethodDeclarationSyntax> overloads = this.FindGeneratedMethod("StrCmpLogical");
foreach (MethodDeclarationSyntax method in overloads)
{
foundPCWSTROverload |= method!.ParameterList.Parameters[0].Type?.ToString() == "PCWSTR";
foundStringOverload |= method!.ParameterList.Parameters[0].Type?.ToString() == "string";
}

Assert.True(foundPCWSTROverload, "PCWSTR overload is missing.");
Assert.True(foundStringOverload, "string overload is missing.");
Assert.Equal(2, overloads.Count());
}

[Theory]
[InlineData("BOOL")]
[InlineData("HRESULT")]
Expand Down Expand Up @@ -249,7 +302,7 @@ public void DeleteObject_TakesTypeDefStruct()
Assert.True(this.generator.TryGenerate("DeleteObject", CancellationToken.None));
this.CollectGeneratedCode(this.generator);
this.AssertNoDiagnostics();
MethodDeclarationSyntax? deleteObjectMethod = this.FindGeneratedMethod("DeleteObject");
MethodDeclarationSyntax? deleteObjectMethod = this.FindGeneratedMethod("DeleteObject").FirstOrDefault();
Assert.NotNull(deleteObjectMethod);
Assert.Equal("HGDIOBJ", Assert.IsType<IdentifierNameSyntax>(deleteObjectMethod!.ParameterList.Parameters[0].Type).Identifier.ValueText);
}
Expand Down Expand Up @@ -334,11 +387,11 @@ private CSharpCompilation AddGeneratedCode(CSharpCompilation compilation, Genera

private void CollectGeneratedCode(Generator generator) => this.compilation = this.AddGeneratedCode(this.compilation, generator);

private MethodDeclarationSyntax? FindGeneratedMethod(string name) => this.compilation.SyntaxTrees.SelectMany(st => st.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>()).FirstOrDefault(md => md.Identifier.ValueText == name);
private IEnumerable<MethodDeclarationSyntax> FindGeneratedMethod(string name) => this.compilation.SyntaxTrees.SelectMany(st => st.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>()).Where(md => md.Identifier.ValueText == name);

private BaseTypeDeclarationSyntax? FindGeneratedType(string name) => this.compilation.SyntaxTrees.SelectMany(st => st.GetRoot().DescendantNodes().OfType<BaseTypeDeclarationSyntax>()).FirstOrDefault(md => md.Identifier.ValueText == name);

private bool IsMethodGenerated(string name) => this.FindGeneratedMethod(name) is object;
private bool IsMethodGenerated(string name) => this.FindGeneratedMethod(name).Any();

private void AssertNoDiagnostics(bool logGeneratedCode = true) => this.AssertNoDiagnostics(this.compilation, logGeneratedCode);

Expand Down
3 changes: 2 additions & 1 deletion test/SpellChecker/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ SpellCheckerFactory
ISpellCheckerFactory
ISpellChecker
CLSCTX
S_FALSE
S_FALSE
S_OK
14 changes: 6 additions & 8 deletions test/SpellChecker/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
text,
out IEnumSpellingError* errors).ThrowOnFailure();

Span<PWSTR> suggestionResult = stackalloc PWSTR[1];
while (true)
{
if (errors->Next(out ISpellingError* error).ThrowOnFailure() == S_FALSE)
Expand All @@ -57,25 +58,22 @@
Console.WriteLine(@"Delete ""{0}""", word);
break;
case CORRECTIVE_ACTION.CORRECTIVE_ACTION_REPLACE:
// KNOWN ISSUE: ushort will be changed to string (https://github.com/microsoft/CsWin32/issues/121)
error->get_Replacement(out ushort* replacement).ThrowOnFailure();
Console.WriteLine(@"Replace ""{0}"" with ""{1}""", word, new string((char*)replacement));
error->get_Replacement(out PWSTR replacement).ThrowOnFailure();
Console.WriteLine(@"Replace ""{0}"" with ""{1}""", word, replacement);
CoTaskMemFree(replacement);
break;
case CORRECTIVE_ACTION.CORRECTIVE_ACTION_GET_SUGGESTIONS:
var l = new List<string>();
spellChecker->Suggest(word, out IEnumString* suggestions).ThrowOnFailure();
while (true)
{
// KNOWN ISSUE: ushort will be changed to string (https://github.com/microsoft/CsWin32/issues/121)
ushort* suggestion;
if (suggestions->Next(1, &suggestion, null).ThrowOnFailure() != 0)
if (suggestions->Next(suggestionResult, null).ThrowOnFailure() != S_OK)
{
break;
}

l.Add(new string((char*)suggestion));
CoTaskMemFree(suggestion);
l.Add(suggestionResult[0].ToString());
CoTaskMemFree(suggestionResult[0]);
}

suggestions->Release();
Expand Down

0 comments on commit 659d266

Please sign in to comment.