Skip to content

Commit

Permalink
Add support for multiple NativeMethods.txt files in a project
Browse files Browse the repository at this point in the history
Any that do not appear in the project directory must be added as an `AdditionalFiles` item by the project itself. Only the one in the project directory itself is automatically added by the CsWin32 nuget package.
  • Loading branch information
AArnott committed May 31, 2022
1 parent 74e738a commit 9b84714
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 63 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ Constants are defined on the same class as the p/invoke methods (by default, the
Other supporting types are defined within or under the `Windows.Win32` namespace.
Discovery of the namespace for a given type can be done with the Go To All feature (Ctrl+T) in Visual Studio with the type name as the search query.

A project may include many NativeMethods.txt files. CsWin32 will read them all to generate APIs, provided these files are included as `AdditionalFiles` in the project.
A `NativeMethods.txt` file directly in the project directory is added automatically to `AdditionalFiles`.
Files in other directories must be added to the project file manually.

### Customizing generated code

Several aspects of the generated code can be customized, including:
Expand Down
129 changes: 66 additions & 63 deletions src/Microsoft.Windows.CsWin32/SourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ public void Execute(GeneratorExecutionContext context)
options = new GeneratorOptions();
}

AdditionalText? nativeMethodsTxtFile = context.AdditionalFiles
.FirstOrDefault(af => string.Equals(Path.GetFileName(af.Path), NativeMethodsTxtAdditionalFileName, StringComparison.OrdinalIgnoreCase));
if (nativeMethodsTxtFile is null)
IEnumerable<AdditionalText> nativeMethodsTxtFiles = context.AdditionalFiles
.Where(af => string.Equals(Path.GetFileName(af.Path), NativeMethodsTxtAdditionalFileName, StringComparison.OrdinalIgnoreCase));
if (!nativeMethodsTxtFiles.Any())
{
return;
}
Expand All @@ -184,95 +184,98 @@ public void Execute(GeneratorExecutionContext context)
try
{
SuperGenerator.Combine(generators);
SourceText? nativeMethodsTxt = nativeMethodsTxtFile.GetText(context.CancellationToken);
if (nativeMethodsTxt is null)
foreach (AdditionalText nativeMethodsTxtFile in nativeMethodsTxtFiles)
{
return;
}

foreach (TextLine line in nativeMethodsTxt.Lines)
{
context.CancellationToken.ThrowIfCancellationRequested();
string name = line.ToString();
if (string.IsNullOrWhiteSpace(name) || name.StartsWith("//", StringComparison.InvariantCulture))
SourceText? nativeMethodsTxt = nativeMethodsTxtFile.GetText(context.CancellationToken);
if (nativeMethodsTxt is null)
{
continue;
return;
}

name = name.Trim();
var location = Location.Create(nativeMethodsTxtFile.Path, line.Span, nativeMethodsTxt.Lines.GetLinePositionSpan(line.Span));
try
foreach (TextLine line in nativeMethodsTxt.Lines)
{
if (Generator.GetBannedAPIs(options).TryGetValue(name, out string? reason))
context.CancellationToken.ThrowIfCancellationRequested();
string name = line.ToString();
if (string.IsNullOrWhiteSpace(name) || name.StartsWith("//", StringComparison.InvariantCulture))
{
context.ReportDiagnostic(Diagnostic.Create(BannedApi, location, reason));
continue;
}

if (name.EndsWith(".*", StringComparison.Ordinal))
name = name.Trim();
var location = Location.Create(nativeMethodsTxtFile.Path, line.Span, nativeMethodsTxt.Lines.GetLinePositionSpan(line.Span));
try
{
string? moduleName = name.Substring(0, name.Length - 2);
int matches = 0;
if (Generator.GetBannedAPIs(options).TryGetValue(name, out string? reason))
{
context.ReportDiagnostic(Diagnostic.Create(BannedApi, location, reason));
continue;
}

if (name.EndsWith(".*", StringComparison.Ordinal))
{
string? moduleName = name.Substring(0, name.Length - 2);
int matches = 0;
foreach (Generator generator in generators)
{
if (generator.TryGenerateAllExternMethods(moduleName, context.CancellationToken))
{
matches++;
}
}

switch (matches)
{
case 0:
context.ReportDiagnostic(Diagnostic.Create(NoMethodsForModule, location, moduleName));
break;
case > 1:
context.ReportDiagnostic(Diagnostic.Create(AmbiguousMatchError, location, moduleName));
break;
}

continue;
}

List<string> matchingApis = new();
foreach (Generator generator in generators)
{
if (generator.TryGenerateAllExternMethods(moduleName, context.CancellationToken))
if (generator.TryGenerate(name, out IReadOnlyList<string> preciseApi, context.CancellationToken))
{
matches++;
matchingApis.AddRange(preciseApi);
continue;
}

matchingApis.AddRange(preciseApi);
if (generator.TryGetEnumName(name, out string? declaringEnum))
{
context.ReportDiagnostic(Diagnostic.Create(UseEnumValueDeclaringType, location, declaringEnum));
generator.TryGenerate(declaringEnum, out preciseApi, context.CancellationToken);
matchingApis.AddRange(preciseApi);
}
}

switch (matches)
switch (matchingApis.Count)
{
case 0:
context.ReportDiagnostic(Diagnostic.Create(NoMethodsForModule, location, moduleName));
ReportNoMatch(location, name);
break;
case > 1:
context.ReportDiagnostic(Diagnostic.Create(AmbiguousMatchError, location, moduleName));
context.ReportDiagnostic(Diagnostic.Create(AmbiguousMatchErrorWithSuggestions, location, name, ConcatSuggestions(matchingApis)));
break;
}

continue;
}

List<string> matchingApis = new();
foreach (Generator generator in generators)
catch (GenerationFailedException ex)
{
if (generator.TryGenerate(name, out IReadOnlyList<string> preciseApi, context.CancellationToken))
if (Generator.IsPlatformCompatibleException(ex))
{
matchingApis.AddRange(preciseApi);
continue;
context.ReportDiagnostic(Diagnostic.Create(CpuArchitectureIncompatibility, location));
}

matchingApis.AddRange(preciseApi);
if (generator.TryGetEnumName(name, out string? declaringEnum))
else
{
context.ReportDiagnostic(Diagnostic.Create(UseEnumValueDeclaringType, location, declaringEnum));
generator.TryGenerate(declaringEnum, out preciseApi, context.CancellationToken);
matchingApis.AddRange(preciseApi);
// Build up a complete error message.
context.ReportDiagnostic(Diagnostic.Create(InternalError, location, AssembleFullExceptionMessage(ex)));
}
}

switch (matchingApis.Count)
{
case 0:
ReportNoMatch(location, name);
break;
case > 1:
context.ReportDiagnostic(Diagnostic.Create(AmbiguousMatchErrorWithSuggestions, location, name, ConcatSuggestions(matchingApis)));
break;
}
}
catch (GenerationFailedException ex)
{
if (Generator.IsPlatformCompatibleException(ex))
{
context.ReportDiagnostic(Diagnostic.Create(CpuArchitectureIncompatibility, location));
}
else
{
// Build up a complete error message.
context.ReportDiagnostic(Diagnostic.Create(InternalError, location, AssembleFullExceptionMessage(ex)));
}
}
}

Expand Down

0 comments on commit 9b84714

Please sign in to comment.