Skip to content

Commit 460f683

Browse files
committed
Start of the source generator that will be used to generate the native calls.
1 parent 98a2b44 commit 460f683

10 files changed

+352
-741
lines changed

src/Magick.NET.SourceGenerator/Extensions/ISymbolExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
using System.Xml.XPath;
88
using Microsoft.CodeAnalysis;
99

10-
namespace ImageMagick;
10+
namespace ImageMagick.SourceGenerator;
1111

1212
internal static class ISymbolExtensions
1313
{
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Text;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp.Syntax;
9+
10+
namespace ImageMagick.SourceGenerator;
11+
12+
internal static class TypeSyntaxExtensions
13+
{
14+
public static string ToNativeString(this TypeSyntax? type)
15+
{
16+
if (type is null)
17+
return "unknown";
18+
19+
return type.ToString();
20+
}
21+
}

src/Magick.NET.SourceGenerator/Magick.NET.SourceGenerator.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
<EmbeddedResource Include="MagickColors\MagickColorsAttribute.cs">
2121
<LogicalName>MagickColorsAttribute</LogicalName>
2222
</EmbeddedResource>
23+
<EmbeddedResource Include="NativeInterop\NativeInteropAttribute.cs">
24+
<LogicalName>NativeInteropAttribute</LogicalName>
25+
</EmbeddedResource>
2326
<EmbeddedResource Include="Paths\PathsAttribute.cs">
2427
<LogicalName>PathsAttribute</LogicalName>
2528
</EmbeddedResource>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
6+
namespace ImageMagick.SourceGenerator;
7+
8+
[AttributeUsage(AttributeTargets.Class)]
9+
internal sealed class NativeInteropAttribute : Attribute;
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.Linq;
5+
using System.Text;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CSharp;
8+
using Microsoft.CodeAnalysis.CSharp.Syntax;
9+
using Microsoft.CodeAnalysis.Text;
10+
11+
namespace ImageMagick.SourceGenerator;
12+
13+
[Generator]
14+
internal class NativeInteropGenerator : IIncrementalGenerator
15+
{
16+
public void Initialize(IncrementalGeneratorInitializationContext context)
17+
{
18+
context.RegisterPostInitializationOutput(context => context.AddAttributeSource<NativeInteropAttribute>());
19+
context.RegisterAttributeCodeGenerator<NativeInteropAttribute, NativeInteropInfo>(GetClass, GenerateCode);
20+
}
21+
22+
private static NativeInteropInfo GetClass(GeneratorAttributeSyntaxContext context)
23+
=> new(context.TargetNode);
24+
25+
private static void GenerateCode(SourceProductionContext context, NativeInteropInfo info)
26+
{
27+
var codeBuilder = new CodeBuilder();
28+
codeBuilder.AppendLine("#nullable enable");
29+
30+
codeBuilder.AppendLine();
31+
codeBuilder.AppendLine("using System.Runtime.InteropServices;");
32+
33+
codeBuilder.AppendLine();
34+
codeBuilder.AppendLine("namespace ImageMagick;");
35+
codeBuilder.AppendLine();
36+
37+
codeBuilder.Append("public partial class ");
38+
codeBuilder.AppendLine(info.ParentClassName);
39+
codeBuilder.AppendLine("{");
40+
codeBuilder.Indent++;
41+
42+
AppendNativeInterop(codeBuilder, info);
43+
44+
codeBuilder.AppendLine();
45+
46+
AppendNativeClass(codeBuilder, info);
47+
48+
codeBuilder.Indent--;
49+
codeBuilder.AppendLine("}");
50+
51+
context.AddSource($"{info.ParentClassName}.g.cs", SourceText.From(codeBuilder.ToString(), Encoding.UTF8));
52+
}
53+
54+
private static void AppendNativeInterop(CodeBuilder codeBuilder, NativeInteropInfo info)
55+
{
56+
codeBuilder.AppendLine("private unsafe static class NativeMethods");
57+
codeBuilder.AppendLine("{");
58+
codeBuilder.Indent++;
59+
AppendNativeInterop(codeBuilder, info, "x64");
60+
AppendNativeInterop(codeBuilder, info, "arm64");
61+
AppendNativeInterop(codeBuilder, info, "x86");
62+
codeBuilder.Indent--;
63+
codeBuilder.AppendLine("}");
64+
}
65+
66+
private static void AppendNativeInterop(CodeBuilder codeBuilder, NativeInteropInfo info, string platform)
67+
{
68+
var name = platform.ToUpperInvariant();
69+
70+
codeBuilder.Append("#if PLATFORM_");
71+
codeBuilder.Append(platform);
72+
codeBuilder.AppendLine(" || PLATFORM_AnyCPU");
73+
codeBuilder.Append("public static class ");
74+
codeBuilder.AppendLine(name);
75+
codeBuilder.AppendLine("{");
76+
codeBuilder.Indent++;
77+
78+
foreach (var method in info.Methods)
79+
{
80+
codeBuilder.Append("[DllImport(NativeLibrary.");
81+
codeBuilder.Append(name);
82+
codeBuilder.AppendLine("Name, CallingConvention = CallingConvention.Cdecl)]");
83+
codeBuilder.Append("public static extern ");
84+
codeBuilder.Append(method.ReturnType.ToString());
85+
codeBuilder.Append(" ");
86+
codeBuilder.Append(info.ParentClassName);
87+
codeBuilder.Append("_");
88+
codeBuilder.Append(method.Identifier.Text);
89+
codeBuilder.Append("(");
90+
for (var i = 0; i < method.ParameterList.Parameters.Count; i++)
91+
{
92+
var parameter = method.ParameterList.Parameters[i];
93+
if (i > 0)
94+
codeBuilder.Append(", ");
95+
96+
codeBuilder.Append(parameter.Type.ToNativeString());
97+
codeBuilder.Append(" ");
98+
codeBuilder.Append(parameter.Identifier.Text);
99+
}
100+
101+
codeBuilder.Append(")");
102+
codeBuilder.AppendLine(";");
103+
}
104+
105+
codeBuilder.Indent--;
106+
codeBuilder.AppendLine("}");
107+
codeBuilder.AppendLine("#endif");
108+
}
109+
110+
private static void AppendNativeClass(CodeBuilder codeBuilder, NativeInteropInfo info)
111+
{
112+
codeBuilder.Append("private unsafe partial class ");
113+
codeBuilder.AppendLine(info.ClassName);
114+
codeBuilder.AppendLine("{");
115+
codeBuilder.Indent++;
116+
117+
codeBuilder.Append("static ");
118+
codeBuilder.Append(info.ClassName);
119+
codeBuilder.AppendLine("() { Environment.Initialize(); }");
120+
121+
foreach (var method in info.Methods)
122+
{
123+
var isVoid = method.ReturnType.ToString() == "void";
124+
125+
codeBuilder.Append("public ");
126+
if (method.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.StaticKeyword)))
127+
codeBuilder.Append("static ");
128+
codeBuilder.Append("partial ");
129+
codeBuilder.Append(method.ReturnType.ToString());
130+
codeBuilder.Append(" ");
131+
codeBuilder.Append(method.Identifier.Text);
132+
codeBuilder.Append("(");
133+
for (var i = 0; i < method.ParameterList.Parameters.Count; i++)
134+
{
135+
var parameter = method.ParameterList.Parameters[i];
136+
if (i > 0)
137+
codeBuilder.Append(", ");
138+
139+
codeBuilder.Append(parameter.Type!.ToString());
140+
codeBuilder.Append(" ");
141+
codeBuilder.Append(parameter.Identifier.Text);
142+
}
143+
144+
codeBuilder.Append(")");
145+
codeBuilder.AppendLine();
146+
codeBuilder.AppendLine("{");
147+
codeBuilder.Indent++;
148+
149+
if (!isVoid)
150+
{
151+
codeBuilder.Append(method.ReturnType.ToNativeString());
152+
codeBuilder.AppendLine(" result;");
153+
}
154+
155+
codeBuilder.AppendLine("#if PLATFORM_AnyCPU");
156+
codeBuilder.AppendLine("if (Runtime.IsArm64)");
157+
codeBuilder.AppendLine("#endif");
158+
AppendMethodImplementation(codeBuilder, info, method, "arm64");
159+
codeBuilder.AppendLine("#if PLATFORM_AnyCPU");
160+
codeBuilder.AppendLine("else if (Runtime.Is64Bit)");
161+
codeBuilder.AppendLine("#endif");
162+
AppendMethodImplementation(codeBuilder, info, method, "x64");
163+
codeBuilder.AppendLine("#if PLATFORM_AnyCPU");
164+
codeBuilder.AppendLine("else");
165+
codeBuilder.AppendLine("#endif");
166+
AppendMethodImplementation(codeBuilder, info, method, "x86");
167+
168+
if (!isVoid)
169+
codeBuilder.AppendLine("return result;");
170+
171+
codeBuilder.Indent--;
172+
codeBuilder.AppendLine("}");
173+
}
174+
175+
codeBuilder.Indent--;
176+
codeBuilder.AppendLine("}");
177+
}
178+
179+
private static void AppendMethodImplementation(CodeBuilder codeBuilder, NativeInteropInfo info, MethodDeclarationSyntax method, string platform)
180+
{
181+
codeBuilder.Append("#if PLATFORM_");
182+
codeBuilder.Append(platform);
183+
codeBuilder.AppendLine(" || PLATFORM_AnyCPU");
184+
if (method.ReturnType.ToString() != "void")
185+
codeBuilder.Append("result = ");
186+
codeBuilder.Append("NativeMethods.");
187+
codeBuilder.Append(platform.ToUpperInvariant());
188+
codeBuilder.Append(".");
189+
codeBuilder.Append(info.ParentClassName);
190+
codeBuilder.Append("_");
191+
codeBuilder.Append(method.Identifier.Text);
192+
codeBuilder.Append("(");
193+
for (var i = 0; i < method.ParameterList.Parameters.Count; i++)
194+
{
195+
var parameter = method.ParameterList.Parameters[i];
196+
if (i > 0)
197+
codeBuilder.Append(", ");
198+
199+
codeBuilder.Append(parameter.Identifier.Text);
200+
}
201+
202+
codeBuilder.AppendLine(");");
203+
codeBuilder.AppendLine("#endif");
204+
}
205+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CSharp.Syntax;
8+
9+
namespace ImageMagick.SourceGenerator;
10+
11+
internal class NativeInteropInfo
12+
{
13+
private readonly ClassDeclarationSyntax _class;
14+
15+
public NativeInteropInfo(SyntaxNode syntaxNode)
16+
{
17+
_class = (ClassDeclarationSyntax)syntaxNode;
18+
Methods = _class.Members.OfType<MethodDeclarationSyntax>().ToList();
19+
}
20+
21+
public string ClassName
22+
=> _class.Identifier.Text;
23+
24+
public List<MethodDeclarationSyntax> Methods { get; }
25+
26+
public string ParentClassName
27+
=> ((ClassDeclarationSyntax)_class.Parent!).Identifier.Text;
28+
}

src/Magick.NET.SourceGenerator/PropertySymbolInfo.cs

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,31 @@
55
using System.Linq;
66
using Microsoft.CodeAnalysis;
77

8-
namespace ImageMagick
8+
namespace ImageMagick.SourceGenerator;
9+
10+
internal sealed class PropertySymbolInfo
911
{
10-
internal sealed class PropertySymbolInfo
11-
{
12-
private readonly IPropertySymbol _symbol;
12+
private readonly IPropertySymbol _symbol;
1313

14-
public PropertySymbolInfo(IPropertySymbol symbol)
15-
{
16-
_symbol = symbol;
17-
ParameterName = symbol.Name.Substring(0, 1).ToLowerInvariant() + symbol.Name.Substring(1);
18-
Documentation = symbol.GetDocumentation().Single();
19-
}
14+
public PropertySymbolInfo(IPropertySymbol symbol)
15+
{
16+
_symbol = symbol;
17+
ParameterName = symbol.Name.Substring(0, 1).ToLowerInvariant() + symbol.Name.Substring(1);
18+
Documentation = symbol.GetDocumentation().Single();
19+
}
2020

21-
public INamedTypeSymbol ContainingType
22-
=> _symbol.ContainingType;
21+
public INamedTypeSymbol ContainingType
22+
=> _symbol.ContainingType;
2323

24-
public string Documentation { get; }
24+
public string Documentation { get; }
2525

26-
public string ParameterName { get; }
26+
public string ParameterName { get; }
2727

28-
public string Type
29-
=> _symbol.Type.ToDisplayString();
28+
public string Type
29+
=> _symbol.Type.ToDisplayString();
3030

31-
public ImmutableArray<string> TypeArguments
32-
=> ((INamedTypeSymbol)_symbol.Type).TypeArguments
33-
.Select(argument => argument.ToDisplayString())
34-
.ToImmutableArray();
35-
}
31+
public ImmutableArray<string> TypeArguments
32+
=> ((INamedTypeSymbol)_symbol.Type).TypeArguments
33+
.Select(argument => argument.ToDisplayString())
34+
.ToImmutableArray();
3635
}

0 commit comments

Comments
 (0)