Skip to content
Closed
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
3 changes: 3 additions & 0 deletions docfx/docs/3rdPartyMetadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ But the general transformation from metadata to C# code may be applied to other
Constructing metadata is outside the scope of this document.
However you may find [the win32metadata architecture](https://github.com/microsoft/win32metadata/blob/main/docs/architecture.md) document instructive.

Use [associated enum annotations](associated-enums.md) to expose enum parameters and fields
while retaining native integer and pointer signatures in metadata.

## Hooking metadata into CsWin32

Metadata is fed into CsWin32 through MSBuild items.
Expand Down
79 changes: 79 additions & 0 deletions docfx/docs/associated-enums.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Associated enums

Win32 metadata can annotate a native integer, pointer or buffer with
`Windows.Win32.Foundation.Metadata.AssociatedEnumAttribute(string Name)`.
CsWin32 uses the named enum in the generated API when its declared underlying type
matches the native value's width and signedness.

For example, an enum declared as `MODE : uint` can represent an annotated native
`uint` parameter or the elements of an annotated `uint*` buffer:

```csharp
public enum MODE : uint { First = 1, Second = 2 }

// Native metadata:
public static extern void SetMode([AssociatedEnum("MODE")] uint mode);
public static extern unsafe void ReadModes(
[In, AssociatedEnum("MODE"), NativeArrayInfo(CountParamIndex = 1)] uint* modes,
uint count);

// Generated signatures, with import attributes omitted:
public static extern void SetMode(MODE mode);
public static extern unsafe void ReadModes(MODE* modes, uint count);
public static unsafe void ReadModes(ReadOnlySpan<MODE> modes);
```

The enum is ABI-compatible with the native value: no scalar conversion or enum
marshaler is required. Its underlying type, member names, values and `[Flags]`
attribute remain as declared in metadata.
This applies with runtime marshaling enabled or disabled, including return
parameters, callbacks, multiple pointer levels, and fixed or flexible buffers.

## Fields

Annotated scalar fields retain the existing native-backed enum-property shape:

```csharp
private uint _Mode;

public MODE Mode
{
get => (MODE)this._Mode;
set => this._Mode = (uint)value;
}
```

This preserves callers that implement interface properties or use property reflection.
Pointer and buffer fields expose enum elements directly when their storage matches.

## Resolving enum names

An unqualified name such as `MODE` prefers the annotated member's declaring namespace.
Otherwise, the name must identify exactly one type in that metadata assembly and
its loaded assembly-reference closure.
A qualified name such as `Windows.Win32.Test.MODE` selects that namespace explicitly.
An unrelated loaded metadata assembly is not searched.
Missing, ambiguous, non-enum, or platform-incompatible targets produce generation errors.

## Incompatible metadata

If the native value and enum have different underlying types, CsWin32 reports
warning **PInvoke015** and ignores that association.
The native parameter, pointer, buffer or field representation is preserved.
For example, `uint*` cannot be projected as a pointer to an enum declared with
`ushort` or `int` storage.
The same warning applies when an existing C# enum has incompatible storage, the
annotated native shape is not integral, or a field is also a `NativeBitfield` backing field.

CsWin32 does not change an enum's underlying type to compensate for inconsistent
metadata, generate converting enum overloads for it, or reinterpret incompatible buffers.
Correct the annotation or enum definition in the metadata.

This also applies to existing annotations in the Windows metadata packages.
For example, the currently shipped annotations for `SHObjectProperties`,
`PathCleanupSpec`, `IShellFolderView.Select`, and `SHDESCRIPTIONID.dwDescriptionId`
disagree with their associated enums' signedness.
These APIs now retain their native integer types and produce PInvoke015 instead
of exposing a converting enum view.
Callers of affected APIs must use native integers or explicit enum casts until
the metadata is corrected.
1 change: 1 addition & 0 deletions docfx/docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
- Generates xml documentation based on and links back to learn.microsoft.com
- Ships no bulky assemblies alongside your application.
- [Layered composition](composition.md): multiple assemblies can extend a single shared `PInvoke` static class so callers reach every native API through one symbol.
- [Associated enums](associated-enums.md): expose ABI-compatible enum parameters and buffer elements from native metadata annotations.

![Animation demonstrating p/invoke code generation](../images/demo.gif)
1 change: 1 addition & 0 deletions docfx/docs/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ items:
- href: composition.md
- href: ArchSpecificAPIs.md
- href: 3rdPartyMetadata.md
- href: associated-enums.md
13 changes: 9 additions & 4 deletions src/CsWin32Generator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,11 @@ private bool GenerateAndWriteFiles(SuperGenerator superGenerator, DirectoryInfo
fileCount++;
}

foreach (Diagnostic diagnostic in superGenerator.Diagnostics)
{
this.ReportWarning(diagnostic.GetMessage(), code: diagnostic.Id);
}

var generatedFilesTxt = Path.Combine(outputPath.FullName, "CsWin32GeneratedFiles.txt");

File.WriteAllLines(generatedFilesTxt, outputFiles);
Expand Down Expand Up @@ -661,19 +666,19 @@ private void ReportError(string message, string? file = null, int? line = null)
}
}

private void ReportWarning(string message, string? file = null, int? line = null)
private void ReportWarning(string message, string? file = null, int? line = null, string? code = null)
{
if (file is not null && line is not null)
{
this.output.WriteLine($"{file}({line}): CsWin32 warning : {message}");
this.output.WriteLine($"{file}({line}): CsWin32 warning {code}: {message}");
}
else if (file is not null)
{
this.output.WriteLine($"{file}: CsWin32 warning : {message}");
this.output.WriteLine($"{file}: CsWin32 warning {code}: {message}");
}
else
{
this.output.WriteLine($"CsWin32 : warning : {message}");
this.output.WriteLine($"CsWin32 : warning {code}: {message}");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ PInvoke011 | Configuration | Error | SourceGenerator
PInvoke012 | Configuration | Error | SourceGenerator
PInvoke013 | Configuration | Error | SourceGenerator
PInvoke014 | Configuration | Error | SourceGenerator
PInvoke015 | Functionality | Warning | SourceGenerator
Loading
Loading