Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions src/Microsoft.Windows.CsWin32/Generator.Struct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,21 @@ private StructDeclarationSyntax DeclareStruct(TypeDefinitionHandle typeDefHandle
.AddDeclarationVariables(fieldDeclarator)
.AddModifiers(TokenWithSpace(this.Visibility));
}
else if (fieldType is PredefinedTypeSyntax { Keyword.RawKind: (int)SyntaxKind.CharKeyword })
{
// If the field is a char, we need to use a helper struct to avoid marshaling issues
// because although C# considered char to be "unmanaged", .NET Framework considers it non-blittable.
this.RequestVariableLengthInlineArrayHelper2(context);
field = FieldDeclaration(
VariableDeclaration(
GenericName($"global::Windows.Win32.VariableLengthInlineArray")
.WithTypeArgumentList(TypeArgumentList().AddArguments(fieldType, PredefinedType(Token(SyntaxKind.UShortKeyword))))))
.AddDeclarationVariables(fieldDeclarator)
.AddModifiers(TokenWithSpace(this.Visibility));
}
else
{
this.RequestVariableLengthInlineArrayHelper(context);
this.RequestVariableLengthInlineArrayHelper1(context);
field = FieldDeclaration(
VariableDeclaration(
GenericName($"global::Windows.Win32.VariableLengthInlineArray")
Expand Down Expand Up @@ -527,21 +539,40 @@ private MethodDeclarationSyntax DeclareSizeOfMethod(TypeSyntax structType, TypeS
return (originalType, default(SyntaxList<MemberDeclarationSyntax>), marshalAs);
}

private void RequestVariableLengthInlineArrayHelper(Context context)
private void RequestVariableLengthInlineArrayHelper1(Context context)
{
if (this.IsWin32Sdk)
{
if (!this.IsTypeAlreadyFullyDeclared($"{this.Namespace}.{this.variableLengthInlineArrayStruct1.Identifier.ValueText}`1"))
{
this.DeclareUnscopedRefAttributeIfNecessary();
this.volatileCode.GenerateSpecialType("VariableLengthInlineArray1", () => this.volatileCode.AddSpecialType("VariableLengthInlineArray1", this.variableLengthInlineArrayStruct1));
}
}
else if (this.SuperGenerator is not null && this.SuperGenerator.TryGetGenerator("Windows.Win32", out Generator? generator))
{
generator.volatileCode.GenerationTransaction(delegate
{
generator.RequestVariableLengthInlineArrayHelper1(context);
});
}
}

private void RequestVariableLengthInlineArrayHelper2(Context context)
{
if (this.IsWin32Sdk)
{
if (!this.IsTypeAlreadyFullyDeclared($"{this.Namespace}.{this.variableLengthInlineArrayStruct.Identifier.ValueText}"))
if (!this.IsTypeAlreadyFullyDeclared($"{this.Namespace}.{this.variableLengthInlineArrayStruct2.Identifier.ValueText}`2"))
{
this.DeclareUnscopedRefAttributeIfNecessary();
this.volatileCode.GenerateSpecialType("VariableLengthInlineArray", () => this.volatileCode.AddSpecialType("VariableLengthInlineArray", this.variableLengthInlineArrayStruct));
this.volatileCode.GenerateSpecialType("VariableLengthInlineArray2", () => this.volatileCode.AddSpecialType("VariableLengthInlineArray2", this.variableLengthInlineArrayStruct2));
}
}
else if (this.SuperGenerator is not null && this.SuperGenerator.TryGetGenerator("Windows.Win32", out Generator? generator))
{
generator.volatileCode.GenerationTransaction(delegate
{
generator.RequestVariableLengthInlineArrayHelper(context);
generator.RequestVariableLengthInlineArrayHelper2(context);
});
}
}
Expand Down
15 changes: 13 additions & 2 deletions src/Microsoft.Windows.CsWin32/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,17 @@ public partial class Generator : IGenerator, IDisposable
private readonly TypeSyntaxSettings errorMessageTypeSettings;

private readonly ClassDeclarationSyntax comHelperClass;
private readonly StructDeclarationSyntax variableLengthInlineArrayStruct;

/// <summary>
/// The struct with one type parameter used to represent a variable-length inline array.
/// </summary>
private readonly StructDeclarationSyntax variableLengthInlineArrayStruct1;

/// <summary>
/// The struct with two type parameters used to represent a variable-length inline array.
/// This is useful when the exposed type parameter is C# unmanaged but runtime unblittable (i.e. <see langword="bool" /> and <see langword="char" />).
/// </summary>
private readonly StructDeclarationSyntax variableLengthInlineArrayStruct2;

private readonly Dictionary<string, IReadOnlyList<ISymbol>> findTypeSymbolIfAlreadyAvailableCache = new(StringComparer.Ordinal);
private readonly Rental<MetadataReader> metadataReader;
Expand Down Expand Up @@ -159,7 +169,8 @@ void AddSymbolIf(bool condition, string symbol)
this.methodsAndConstantsClassName = IdentifierName(options.ClassName);

FetchTemplate("ComHelpers", this, out this.comHelperClass);
FetchTemplate("VariableLengthInlineArray`1", this, out this.variableLengthInlineArrayStruct);
FetchTemplate("VariableLengthInlineArray`1", this, out this.variableLengthInlineArrayStruct1);
FetchTemplate("VariableLengthInlineArray`2", this, out this.variableLengthInlineArrayStruct2);
}

internal enum GeneratingElement
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
internal struct VariableLengthInlineArray<T, TBlittable>
where T : unmanaged
where TBlittable : unmanaged
{
#if canUseUnscopedRef
private TBlittable _e0;

[UnscopedRef]
internal ref T e0 => ref Unsafe.As<TBlittable, T>(ref this._e0);

#if canUseUnsafeAdd
internal ref T this[int index]
{
[UnscopedRef]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref Unsafe.Add(ref this.e0, index);
}
#endif

#if canUseSpan
[UnscopedRef]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Span<T> AsSpan(int length)
{
#if canCallCreateSpan
return MemoryMarshal.CreateSpan(ref this.e0, length);
#else
unsafe
{
fixed (void* p = &this.e0)
{
return new Span<T>(p, length);
}
}
#endif
}
#endif

#else
internal TBlittable e0;
#endif
}
19 changes: 19 additions & 0 deletions test/GenerationSandbox.Tests/FlexibleArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using Windows.Win32.System.Ole;

public class FlexibleArrayTests
Expand Down Expand Up @@ -34,4 +35,22 @@ public void SizeOf_Minimum1Element()
Assert.Equal(PAGESET.SizeOf(1), PAGESET.SizeOf(0));
Assert.Equal(Marshal.SizeOf<PAGERANGE>(), PAGESET.SizeOf(2) - PAGESET.SizeOf(1));
}

[Fact]
[Trait("WindowsOnly", "true")]
public unsafe void NonBlittableType_Char()
{
// The argument values are not relevant here.
// The function is expected to return a failure error code in success cases.
// This is a regression test for https://github.com/microsoft/CsWin32/issues/1184,
// where the mere invocation of the function caused .NET Framework to throw a marshaling exception.
uint size = 0;
Windows.Win32.PInvoke.SetupDiGetDeviceInterfaceDetail(
new SafeFileHandle(IntPtr.Zero, false),
default,
null,
0,
&size,
null);
}
}
1 change: 1 addition & 0 deletions test/GenerationSandbox.Tests/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ RECT
RegLoadAppKey
RM_PROCESS_INFO
S_OK
SetupDiGetDeviceInterfaceDetail
SHDESCRIPTIONID
SHELLFLAGSTATE
ShellLink
Expand Down