Skip to content

Commit 9eba543

Browse files
authored
Merge pull request #730 from microsoft/fix228
Declare ZZ-family of string types
2 parents c6f0c63 + 4c14e64 commit 9eba543

File tree

19 files changed

+614
-337
lines changed

19 files changed

+614
-337
lines changed

src/Microsoft.Windows.CsWin32/Generator.cs

Lines changed: 72 additions & 243 deletions
Large diffs are not rendered by default.

src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Reflection;
66
using System.Reflection.Metadata;
77
using System.Runtime.InteropServices;
8-
using Microsoft.CodeAnalysis;
98
using Microsoft.CodeAnalysis.CSharp;
109
using Microsoft.CodeAnalysis.CSharp.Syntax;
1110
using static Microsoft.Windows.CsWin32.FastSyntaxFactory;
@@ -77,13 +76,25 @@ internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs
7776
return new TypeSyntaxAndMarshaling(bclType);
7877
}
7978

80-
if (simpleName is "PWSTR" or "PSTR" && (this.IsConstantField || customAttributes?.Any(ah => MetadataUtilities.IsAttribute(this.reader, this.reader.GetCustomAttribute(ah), Generator.InteropDecorationNamespace, "ConstAttribute")) is true))
79+
if (simpleName is "PWSTR" or "PSTR")
8180
{
82-
string specialName = "PC" + simpleName.Substring(1);
81+
bool isConst = this.IsConstantField || MetadataUtilities.FindAttribute(this.reader, customAttributes, Generator.InteropDecorationNamespace, "ConstAttribute").HasValue;
82+
bool isEmptyStringTerminatedList = MetadataUtilities.FindAttribute(this.reader, customAttributes, Generator.InteropDecorationNamespace, "NullNullTerminatedAttribute").HasValue;
83+
string constChar = isConst ? "C" : string.Empty;
84+
string listChars = isEmptyStringTerminatedList ? "ZZ" : string.Empty;
85+
string nameEnding = simpleName.Substring(1);
86+
string specialName = $"P{constChar}{listChars}{nameEnding}";
8387
if (inputs.Generator is object)
8488
{
85-
inputs.Generator.RequestSpecialTypeDefStruct(specialName, out string fullyQualifiedName);
86-
return new TypeSyntaxAndMarshaling(ParseName(Generator.ReplaceCommonNamespaceWithAlias(inputs.Generator, fullyQualifiedName)));
89+
if (Generator.SpecialTypeDefNames.Contains(specialName))
90+
{
91+
inputs.Generator.RequestSpecialTypeDefStruct(specialName, out string fullyQualifiedName);
92+
return new TypeSyntaxAndMarshaling(ParseName(Generator.ReplaceCommonNamespaceWithAlias(inputs.Generator, fullyQualifiedName)));
93+
}
94+
else
95+
{
96+
this.RequestTypeGeneration(inputs.Generator, this.GetContext(inputs));
97+
}
8798
}
8899
else
89100
{

src/Microsoft.Windows.CsWin32/MetadataIndex.cs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -162,28 +162,22 @@ void PopulateNamespace(NamespaceDefinition ns, string? parentNamespace)
162162
TypeReference baseType = mr.GetTypeReference((TypeReferenceHandle)td.BaseType);
163163
if (mr.StringComparer.Equals(baseType.Name, nameof(ValueType)) && mr.StringComparer.Equals(baseType.Namespace, nameof(System)))
164164
{
165-
foreach (CustomAttributeHandle h in td.GetCustomAttributes())
165+
if (MetadataUtilities.FindAttribute(mr, td.GetCustomAttributes(), Generator.InteropDecorationNamespace, Generator.RAIIFreeAttribute) is CustomAttribute att)
166166
{
167-
CustomAttribute att = mr.GetCustomAttribute(h);
168-
if (MetadataUtilities.IsAttribute(mr, att, Generator.InteropDecorationNamespace, Generator.RAIIFreeAttribute))
167+
CustomAttributeValue<CodeAnalysis.CSharp.Syntax.TypeSyntax> args = att.DecodeValue(CustomAttributeTypeProvider.Instance);
168+
if (args.FixedArguments[0].Value is string freeMethodName)
169169
{
170-
CustomAttributeValue<CodeAnalysis.CSharp.Syntax.TypeSyntax> args = att.DecodeValue(CustomAttributeTypeProvider.Instance);
171-
if (args.FixedArguments[0].Value is string freeMethodName)
170+
this.handleTypeReleaseMethod.Add(tdh, freeMethodName);
171+
this.releaseMethods.Add(freeMethodName);
172+
173+
using FieldDefinitionHandleCollection.Enumerator fieldEnum = td.GetFields().GetEnumerator();
174+
fieldEnum.MoveNext();
175+
FieldDefinitionHandle fieldHandle = fieldEnum.Current;
176+
FieldDefinition fieldDef = mr.GetFieldDefinition(fieldHandle);
177+
if (fieldDef.DecodeSignature(SignatureHandleProvider.Instance, null) is PrimitiveTypeHandleInfo { PrimitiveTypeCode: PrimitiveTypeCode.IntPtr or PrimitiveTypeCode.UIntPtr })
172178
{
173-
this.handleTypeReleaseMethod.Add(tdh, freeMethodName);
174-
this.releaseMethods.Add(freeMethodName);
175-
176-
using FieldDefinitionHandleCollection.Enumerator fieldEnum = td.GetFields().GetEnumerator();
177-
fieldEnum.MoveNext();
178-
FieldDefinitionHandle fieldHandle = fieldEnum.Current;
179-
FieldDefinition fieldDef = mr.GetFieldDefinition(fieldHandle);
180-
if (fieldDef.DecodeSignature(SignatureHandleProvider.Instance, null) is PrimitiveTypeHandleInfo { PrimitiveTypeCode: PrimitiveTypeCode.IntPtr or PrimitiveTypeCode.UIntPtr })
181-
{
182-
this.handleTypeStructsWithIntPtrSizeFields.Add(typeName);
183-
}
179+
this.handleTypeStructsWithIntPtrSizeFields.Add(typeName);
184180
}
185-
186-
break;
187181
}
188182
}
189183
}

src/Microsoft.Windows.CsWin32/MetadataUtilities.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,21 @@ internal static bool IsAttribute(MetadataReader reader, CustomAttribute attribut
7979

8080
return reader.StringComparer.Equals(actualName, name) && reader.StringComparer.Equals(actualNamespace, ns);
8181
}
82+
83+
internal static CustomAttribute? FindAttribute(MetadataReader reader, CustomAttributeHandleCollection? customAttributeHandles, string attributeNamespace, string attributeName)
84+
{
85+
if (customAttributeHandles is not null)
86+
{
87+
foreach (CustomAttributeHandle handle in customAttributeHandles)
88+
{
89+
CustomAttribute att = reader.GetCustomAttribute(handle);
90+
if (IsAttribute(reader, att, attributeNamespace, attributeName))
91+
{
92+
return att;
93+
}
94+
}
95+
}
96+
97+
return null;
98+
}
8299
}

src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs
3838
{
3939
if (elementTypeDetails.Type is PredefinedTypeSyntax { Keyword: { RawKind: (int)SyntaxKind.ObjectKeyword } } && inputs.Generator is not null && inputs.Generator.Options.ComInterop.UseIntPtrForComOutPointers)
4040
{
41-
bool isComOutPtr = inputs.Generator?.Reader is not null && customAttributes is not null && customAttributes.Value.Any(ah => inputs.Generator.IsAttribute(inputs.Generator.Reader.GetCustomAttribute(ah), Generator.InteropDecorationNamespace, "ComOutPtrAttribute"));
41+
bool isComOutPtr = inputs.Generator.FindInteropDecorativeAttribute(customAttributes, "ComOutPtrAttribute").HasValue;
4242
return new TypeSyntaxAndMarshaling(IdentifierName(nameof(IntPtr)))
4343
{
4444
ParameterModifier = Token(SyntaxKind.OutKeyword),
@@ -70,8 +70,7 @@ internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs
7070
elementTypeDetails.NativeArrayInfo);
7171
}
7272
}
73-
else if (inputs.AllowMarshaling && inputs.Generator is object
74-
&& customAttributes?.Any(ah => MetadataUtilities.IsAttribute(inputs.Generator.Reader, inputs.Generator!.Reader.GetCustomAttribute(ah), Generator.InteropDecorationNamespace, "ComOutPtrAttribute")) is true)
73+
else if (inputs.AllowMarshaling && inputs.Generator?.FindInteropDecorativeAttribute(customAttributes, "ComOutPtrAttribute") is not null)
7574
{
7675
return new TypeSyntaxAndMarshaling(PredefinedType(Token(SyntaxKind.ObjectKeyword)), new MarshalAsAttribute(UnmanagedType.IUnknown), null);
7776
}

src/Microsoft.Windows.CsWin32/templates/BSTR.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,12 @@
44
/// Gets the length of the BSTR in characters.
55
/// </summary>
66
internal unsafe int Length => this.Value is null ? 0 : checked((int)(*(((uint*)this.Value) - 1) / sizeof(char)));
7+
8+
public override string ToString() => this.Value != null ? Marshal.PtrToStringBSTR(new IntPtr(this.Value)) : null;
9+
10+
#if canUseSpan
11+
public static unsafe implicit operator ReadOnlySpan<char>(BSTR bstr) => bstr.Value != null ? new ReadOnlySpan<char>(bstr.Value, *((int*)bstr.Value - 1) / 2) : default(ReadOnlySpan<char>);
12+
13+
internal ReadOnlySpan<char> AsSpan() => this;
14+
#endif
715
}

src/Microsoft.Windows.CsWin32/templates/PCSTR.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ internal unsafe readonly partial struct PCSTR
1212
internal PCSTR(byte* value) => this.Value = value;
1313
public static implicit operator byte*(PCSTR value) => value.Value;
1414
public static explicit operator PCSTR(byte* value) => new PCSTR(value);
15-
public static implicit operator PCSTR(PSTR value) => new PCSTR(value.Value);
1615
public bool Equals(PCSTR other) => this.Value == other.Value;
1716
public override bool Equals(object obj) => obj is PCSTR other && this.Equals(other);
1817
public override int GetHashCode() => unchecked((int)this.Value);
@@ -39,5 +38,12 @@ internal int Length
3938
/// <returns>A <see langword="string"/>, or <see langword="null"/> if <see cref="Value"/> is <see langword="null"/>.</returns>
4039
public override string ToString() => this.Value is null ? null : new string((sbyte*)this.Value, 0, this.Length, global::System.Text.Encoding.UTF8);
4140

41+
#if canUseSpan
42+
/// <summary>
43+
/// Returns a span of the characters in this string.
44+
/// </summary>
45+
internal ReadOnlySpan<byte> AsSpan() => this.Value is null ? default(ReadOnlySpan<byte>) : new ReadOnlySpan<byte>(this.Value, this.Length);
46+
#endif
47+
4248
private string DebuggerDisplay => this.ToString();
4349
}

src/Microsoft.Windows.CsWin32/templates/PCWSTR.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ internal unsafe readonly partial struct PCWSTR
1212
internal PCWSTR(char* value) => this.Value = value;
1313
public static explicit operator char*(PCWSTR value) => value.Value;
1414
public static implicit operator PCWSTR(char* value) => new PCWSTR(value);
15-
public static implicit operator PCWSTR(PWSTR value) => new PCWSTR(value.Value);
1615
public bool Equals(PCWSTR other) => this.Value == other.Value;
1716
public override bool Equals(object obj) => obj is PCWSTR other && this.Equals(other);
1817
public override int GetHashCode() => unchecked((int)this.Value);
@@ -39,5 +38,12 @@ internal int Length
3938
/// <returns>A <see langword="string"/>, or <see langword="null"/> if <see cref="Value"/> is <see langword="null"/>.</returns>
4039
public override string ToString() => this.Value is null ? null : new string(this.Value);
4140

41+
#if canUseSpan
42+
/// <summary>
43+
/// Returns a span of the characters in this string.
44+
/// </summary>
45+
internal ReadOnlySpan<char> AsSpan() => this.Value is null ? default(ReadOnlySpan<char>) : new ReadOnlySpan<char>(this.Value, this.Length);
46+
#endif
47+
4248
private string DebuggerDisplay => this.ToString();
4349
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/// <summary>
2+
/// A pointer to a constant, empty-string terminated list of null-terminated strings with 1-byte characters (often UTF-8).
3+
/// </summary>
4+
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
5+
internal unsafe readonly partial struct PCZZSTR
6+
: IEquatable<PCZZSTR>
7+
{
8+
/// <summary>
9+
/// A pointer to the first character in the string. The content should be considered readonly, as it was typed as constant in the SDK.
10+
/// </summary>
11+
internal readonly byte* Value;
12+
internal PCZZSTR(byte* value) => this.Value = value;
13+
public static implicit operator byte*(PCZZSTR value) => value.Value;
14+
public static explicit operator PCZZSTR(byte* value) => new PCZZSTR(value);
15+
public bool Equals(PCZZSTR other) => this.Value == other.Value;
16+
public override bool Equals(object obj) => obj is PCZZSTR other && this.Equals(other);
17+
public override int GetHashCode() => unchecked((int)this.Value);
18+
19+
/// <summary>
20+
/// Gets the number of characters in this null-terminated string list, excluding the final null terminator.
21+
/// </summary>
22+
internal int Length
23+
{
24+
get
25+
{
26+
PCSTR str = new PCSTR(this.Value);
27+
while (true)
28+
{
29+
int len = str.Length;
30+
if (len > 0)
31+
{
32+
str = new PCSTR(str.Value + len + 1);
33+
}
34+
else
35+
{
36+
break;
37+
}
38+
}
39+
40+
return checked((int)(str.Value - this.Value));
41+
}
42+
}
43+
44+
/// <summary>
45+
/// Returns a <see langword="string"/> with a copy of this character array, decoding as UTF-8.
46+
/// </summary>
47+
/// <returns>A <see langword="string"/>, or <see langword="null"/> if <see cref="Value"/> is <see langword="null"/>.</returns>
48+
public override string ToString() => this.Value is null ? null : new string((sbyte*)this.Value, 0, this.Length, global::System.Text.Encoding.UTF8);
49+
50+
#if canUseSpan
51+
/// <summary>
52+
/// Returns a span of the characters in this string.
53+
/// </summary>
54+
internal ReadOnlySpan<byte> AsSpan() => this.Value is null ? default(ReadOnlySpan<byte>) : new ReadOnlySpan<byte>(this.Value, this.Length);
55+
#endif
56+
57+
private string DebuggerDisplay => this.ToString();
58+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/// <summary>
2+
/// A pointer to a constant, empty-string terminated list of null-terminated strings that uses UTF-16 encoding.
3+
/// </summary>
4+
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
5+
internal unsafe readonly partial struct PCZZWSTR
6+
: IEquatable<PCZZWSTR>
7+
{
8+
/// <summary>
9+
/// A pointer to the first character in the string. The content should be considered readonly, as it was typed as constant in the SDK.
10+
/// </summary>
11+
internal readonly char* Value;
12+
internal PCZZWSTR(char* value) => this.Value = value;
13+
public static explicit operator char*(PCZZWSTR value) => value.Value;
14+
public static implicit operator PCZZWSTR(char* value) => new PCZZWSTR(value);
15+
public bool Equals(PCZZWSTR other) => this.Value == other.Value;
16+
public override bool Equals(object obj) => obj is PCZZWSTR other && this.Equals(other);
17+
public override int GetHashCode() => unchecked((int)this.Value);
18+
19+
/// <summary>
20+
/// Gets the number of characters in this null-terminated string list, excluding the final null terminator.
21+
/// </summary>
22+
internal int Length
23+
{
24+
get
25+
{
26+
PCWSTR str = new PCWSTR(this.Value);
27+
while (true)
28+
{
29+
int len = str.Length;
30+
if (len > 0)
31+
{
32+
str = new PCWSTR(str.Value + len + 1);
33+
}
34+
else
35+
{
36+
break;
37+
}
38+
}
39+
40+
return checked((int)(str.Value - this.Value));
41+
}
42+
}
43+
44+
/// <summary>
45+
/// Returns a <see langword="string"/> with a copy of this character array.
46+
/// </summary>
47+
/// <returns>A <see langword="string"/>, or <see langword="null"/> if <see cref="Value"/> is <see langword="null"/>.</returns>
48+
public override string ToString() => this.Value is null ? null : new string(this.Value, 0, this.Length);
49+
50+
#if canUseSpan
51+
/// <summary>
52+
/// Returns a span of the characters in this string.
53+
/// </summary>
54+
internal ReadOnlySpan<char> AsSpan() => this.Value is null ? default(ReadOnlySpan<char>) : new ReadOnlySpan<char>(this.Value, this.Length);
55+
#endif
56+
57+
private string DebuggerDisplay => this.ToString();
58+
}

0 commit comments

Comments
 (0)