Skip to content

Commit 5667cf7

Browse files
committed
Implement marshal map feature
1 parent 76dd14c commit 5667cf7

8 files changed

+213
-7
lines changed

AnalyzerReleases.Unshipped.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
Rule ID | Category | Severity | Notes
77
--------|----------|----------|-------
88
NGD1001 | Usage | Error | Diagnostics
9-
NGD1003 | Usage | Error | Diagnostics
9+
NGD1003 | Usage | Error | Diagnostics
10+
NGD1004 | Usage | Error | Diagnostics

DelegateMarshalling.Parser.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Monkeymoto.NativeGenericDelegates
1010
{
1111
internal sealed partial class DelegateMarshalling
1212
{
13-
private static class Parser
13+
internal static class Parser
1414
{
1515
private static IReadOnlyList<string?> GetMarshalAsCollectionFromArrayInitializer
1616
(
@@ -185,7 +185,7 @@ CancellationToken cancellationToken
185185
return null;
186186
}
187187

188-
private static string? GetMarshalAsFromOperation
188+
public static string? GetMarshalAsFromOperation
189189
(
190190
IOperation value,
191191
string parameterName,

DelegateMarshalling.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ CancellationToken cancellationToken
3232
)
3333
{
3434
IArgumentOperation? callingConventionArgument = null;
35+
IArgumentOperation? marshalMapArgument = null;
3536
IArgumentOperation? marshalReturnAsArgument = null;
3637
IArgumentOperation? marshalParamsAsArgument = null;
3738
foreach (var argumentNode in invocationExpression.ArgumentList.Arguments)
@@ -42,6 +43,16 @@ CancellationToken cancellationToken
4243
case "callingConvention":
4344
callingConventionArgument = argumentOp;
4445
break;
46+
case "marshalMap":
47+
IConversionOperation? conversion = argumentOp.Value as IConversionOperation;
48+
ILiteralOperation? literal = conversion?.Operand as ILiteralOperation;
49+
if (!argumentOp.ConstantValue.HasValue &&
50+
!(conversion?.ConstantValue.HasValue ?? false) &&
51+
!(literal?.ConstantValue.HasValue ?? false))
52+
{
53+
marshalMapArgument = argumentOp;
54+
}
55+
break;
4556
case "marshalReturnAs":
4657
marshalReturnAsArgument = argumentOp;
4758
break;
@@ -60,6 +71,38 @@ CancellationToken cancellationToken
6071
cancellationToken
6172
);
6273
MarshalReturnAs = Parser.GetMarshalReturnAs(marshalReturnAsArgument, diagnostics, cancellationToken);
74+
var marshalMap = MarshalMap.Parse(marshalMapArgument, diagnostics, cancellationToken);
75+
if (marshalMap is not null)
76+
{
77+
var marshalParamsList = new List<string?>(interfaceDescriptor.InvokeParameterCount);
78+
if (MarshalParamsAs is not null)
79+
{
80+
marshalParamsList.AddRange(MarshalParamsAs);
81+
}
82+
while (marshalParamsList.Count < interfaceDescriptor.InvokeParameterCount)
83+
{
84+
marshalParamsList.Add(null);
85+
}
86+
bool dirty = false;
87+
for (int i = 0; i < interfaceDescriptor.InvokeParameterCount; ++i)
88+
{
89+
if ((marshalParamsList[i] is null) &&
90+
marshalMap.TryGetValue(interfaceDescriptor.TypeArguments[i], out var marshalParamAs))
91+
{
92+
marshalParamsList[i] = marshalParamAs;
93+
dirty = true;
94+
}
95+
}
96+
if (dirty)
97+
{
98+
MarshalParamsAs = marshalParamsList.AsReadOnly();
99+
}
100+
if ((MarshalReturnAs is null) && !interfaceDescriptor.IsAction &&
101+
marshalMap.TryGetValue(interfaceDescriptor.TypeArguments.Last(), out var marshalReturnAs))
102+
{
103+
MarshalReturnAs = marshalReturnAs;
104+
}
105+
}
63106
if (callingConventionArgument is not null)
64107
{
65108
var value = callingConventionArgument.Value;

Diagnostics.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,15 @@ internal static class Diagnostics
2424
DiagnosticSeverity.Error,
2525
true
2626
);
27+
28+
public static readonly DiagnosticDescriptor NGD1004_InvalidMarshalMapArgument = new
29+
(
30+
"NGD1004",
31+
"NGD1004: Invalid MarshalMap argument",
32+
"MarshalMap argument {0} must be null or use collection initializer syntax",
33+
"Usage",
34+
DiagnosticSeverity.Error,
35+
true
36+
);
2737
}
2838
}

ImplementationClassCollection.Key.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23

34
namespace Monkeymoto.NativeGenericDelegates
45
{
@@ -20,11 +21,26 @@ public Key(MethodReference methodReference)
2021
{
2122
MethodReference = methodReference;
2223
hashCode = Hash.Combine(MethodReference.Method, MethodReference.Marshalling);
24+
Debug.WriteLine($"hashCode: {hashCode}");
2325
}
2426

2527
public override bool Equals(object? obj) => obj is Key other && Equals(other);
2628
public bool Equals(Key other) => (MethodReference.Method == other.MethodReference.Method) &&
2729
(MethodReference.Marshalling == other.MethodReference.Marshalling);
30+
//public bool Equals(Key other)
31+
//{
32+
// Debug.WriteLine($"interface: {MethodReference.Method.ContainingInterface.FullName} / {other.MethodReference.Method.ContainingInterface.FullName} / equal? {MethodReference.Method.ContainingInterface == other.MethodReference.Method.ContainingInterface}");
33+
// Debug.WriteLine($"method: {MethodReference.Method.Name} / {other.MethodReference.Method.Name} / equal? {MethodReference.Method == other.MethodReference.Method}");
34+
// Debug.WriteLine($"arity: {MethodReference.Method.Arity} / {other.MethodReference.Method.Arity}");
35+
// Debug.WriteLine($"aritys equal? {MethodReference.Method.Arity == other.MethodReference.Method.Arity}");
36+
// Debug.WriteLine($"interfaces equal? {MethodReference.Method.ContainingInterface == other.MethodReference.Method.ContainingInterface}");
37+
// Debug.WriteLine($"names equal? {MethodReference.Method.Name == other.MethodReference.Method.Name}");
38+
// Debug.WriteLine($"methods equal? {MethodReference.Method == other.MethodReference.Method}");
39+
// //public bool Equals(MethodDescriptor? other) =>
40+
// // (other is not null) && (Arity == other.Arity) && (ContainingInterface == other.ContainingInterface) &&
41+
// // (Name == other.Name);
42+
// return (MethodReference.Method == other.MethodReference.Method) && (MethodReference.Marshalling == other.MethodReference.Marshalling);
43+
//}
2844
public override int GetHashCode() => hashCode;
2945
}
3046
}

MarshalMap.cs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp.Syntax;
3+
using Microsoft.CodeAnalysis.Operations;
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
using System.Collections.Immutable;
7+
using System.Linq;
8+
using System.Threading;
9+
10+
namespace Monkeymoto.NativeGenericDelegates
11+
{
12+
internal sealed class MarshalMap : IReadOnlyDictionary<string, string?>
13+
{
14+
private readonly ImmutableDictionary<string, string?> map;
15+
16+
public string? this[string key] => map[key];
17+
public int Count => map.Count;
18+
public IEnumerable<string> Keys => map.Keys;
19+
public IEnumerable<string?> Values => map.Values;
20+
21+
public static MarshalMap? Parse
22+
(
23+
IArgumentOperation? marshalMapArgument,
24+
IList<Diagnostic> diagnostics,
25+
CancellationToken cancellationToken
26+
)
27+
{
28+
_ = diagnostics;
29+
if (marshalMapArgument is null)
30+
{
31+
return null;
32+
}
33+
var builder = ImmutableDictionary.CreateBuilder<string, string?>();
34+
var value = marshalMapArgument.Value;
35+
var invalidArgumentDiagnostic = Diagnostic.Create
36+
(
37+
Diagnostics.NGD1004_InvalidMarshalMapArgument,
38+
marshalMapArgument.Syntax.GetLocation(),
39+
marshalMapArgument.Parameter!.Name
40+
);
41+
if (value is IFieldReferenceOperation fieldReference && fieldReference.Field.IsReadOnly)
42+
{
43+
var fieldDeclaration = fieldReference.Field.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken);
44+
var equalsValueClause = fieldDeclaration.ChildNodes().OfType<EqualsValueClauseSyntax>()
45+
.FirstOrDefault();
46+
SemanticModel? semanticModel = equalsValueClause is not null ?
47+
fieldReference.SemanticModel!.Compilation.GetSemanticModel(equalsValueClause.SyntaxTree) :
48+
null;
49+
if (semanticModel?.GetOperation(equalsValueClause!, cancellationToken) is not
50+
IFieldInitializerOperation fieldInitializer)
51+
{
52+
diagnostics.Add(invalidArgumentDiagnostic);
53+
return null;
54+
}
55+
value = fieldInitializer.Value;
56+
}
57+
IObjectCreationOperation? mapCreation = value switch
58+
{
59+
IConversionOperation conversion => conversion.Operand as IObjectCreationOperation,
60+
_ => value as IObjectCreationOperation
61+
};
62+
if ((mapCreation?.Initializer is null) || (mapCreation.Initializer.Initializers.Length == 0))
63+
{
64+
return new(builder.ToImmutable());
65+
}
66+
var initializers = mapCreation.Initializer.Initializers;
67+
foreach (var op in initializers)
68+
{
69+
cancellationToken.ThrowIfCancellationRequested();
70+
ITypeOfOperation? typeOf;
71+
IOperation? marshalAs;
72+
if (op is IInvocationOperation invocation)
73+
{
74+
if (invocation.Arguments.Length != 2)
75+
{
76+
diagnostics.Add(invalidArgumentDiagnostic);
77+
return null;
78+
}
79+
typeOf = invocation.Arguments[0].Value as ITypeOfOperation;
80+
value = invocation.Arguments[1].Value;
81+
marshalAs = value switch
82+
{
83+
IConversionOperation conversion => conversion.Operand as IObjectCreationOperation,
84+
_ => value as IObjectCreationOperation
85+
};
86+
}
87+
else
88+
{
89+
diagnostics.Add(invalidArgumentDiagnostic);
90+
return null;
91+
}
92+
if ((typeOf is null) || (marshalAs is null))
93+
{
94+
diagnostics.Add(invalidArgumentDiagnostic);
95+
return null;
96+
}
97+
var key = typeOf.TypeOperand.ToDisplayString();
98+
var marshalAsValue = DelegateMarshalling.Parser.GetMarshalAsFromOperation
99+
(
100+
marshalAs,
101+
marshalMapArgument.Parameter!.Name,
102+
diagnostics,
103+
diagnosticTypeSuffix: "",
104+
cancellationToken
105+
);
106+
builder[key] = marshalAsValue;
107+
}
108+
return new(builder.ToImmutable());
109+
}
110+
111+
private MarshalMap(ImmutableDictionary<string, string?> dictionary)
112+
{
113+
map = dictionary;
114+
}
115+
116+
public bool ContainsKey(string key) => map.ContainsKey(key);
117+
public IEnumerator<KeyValuePair<string, string?>> GetEnumerator() => map.GetEnumerator();
118+
IEnumerator IEnumerable.GetEnumerator() => map.GetEnumerator();
119+
public bool TryGetValue(string key, out string? value) => map.TryGetValue(key, out value);
120+
}
121+
}

MethodDescriptor.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public bool Equals(MethodDescriptor? other) =>
6969

7070
private string GetParameters(bool getInterceptorParameters)
7171
{
72+
var marshalMap = $"MarshalMap marshalMap,{Constants.NewLineIndent3}";
7273
var marshalReturnAsParam = !ContainingInterface.IsAction ?
7374
$"MarshalAsAttribute marshalReturnAs,{Constants.NewLineIndent3}" :
7475
string.Empty;
@@ -82,8 +83,8 @@ private string GetParameters(bool getInterceptorParameters)
8283
firstParameterType = $"{ContainingInterface.Category}<{typeParameters}>";
8384
}
8485
return
85-
$"{firstParameterType} {FirstParameterName},{Constants.NewLineIndent3}{marshalReturnAsParam}" +
86-
$"{marshalParamsAsParam}CallingConvention callingConvention";
86+
$"{firstParameterType} {FirstParameterName},{Constants.NewLineIndent3}{marshalMap}" +
87+
$"{marshalReturnAsParam}{marshalParamsAsParam}CallingConvention callingConvention";
8788
}
8889
}
8990
}

PostInitialization.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,24 @@ private static void BuildInterfaceDefinition(StringBuilder sb, bool isAction, in
4343
string marshalParamsAsParameter = argumentCount != 0 ?
4444
$",{Constants.NewLineIndent3}MarshalAsAttribute?[]? marshalParamsAs = null" :
4545
string.Empty;
46+
string marshalMap = argumentCount != 0 ?
47+
$",{Constants.NewLineIndent3}MarshalMap? marshalMap = null" :
48+
string.Empty;
4649
_ = sb.Append
4750
(
4851
$@" internal interface INative{type}{qualifiedTypeParameters}
4952
{{
5053
public static INative{genericType} From{type}
5154
(
52-
{genericType} {typeAsArgument}{marshalReturnAsParameter}{marshalParamsAsParameter}{callingConvention}
55+
{genericType} {typeAsArgument}{marshalMap}{marshalReturnAsParameter}{marshalParamsAsParameter}{callingConvention}
5356
)
5457
{{
5558
throw new NotImplementedException();
5659
}}
5760
5861
public static INative{genericType} FromFunctionPointer
5962
(
60-
nint functionPtr{marshalReturnAsParameter}{marshalParamsAsParameter}{callingConvention}
63+
nint functionPtr{marshalMap}{marshalReturnAsParameter}{marshalParamsAsParameter}{callingConvention}
6164
)
6265
{{
6366
throw new NotImplementedException();
@@ -77,6 +80,8 @@ public static string GetSourceText()
7780
(
7881
$@"// <auto-generated/>
7982
using System;
83+
using System.Collections;
84+
using System.Collections.Generic;
8085
using System.Runtime.CompilerServices;
8186
using System.Runtime.InteropServices;
8287
@@ -93,6 +98,15 @@ namespace {Constants.RootNamespace}
9398
_ = source.AppendLine
9499
(
95100
$@"
101+
internal sealed class MarshalMap : IEnumerable<KeyValuePair<Type, MarshalAsAttribute>>
102+
{{
103+
public MarshalMap() {{ }}
104+
public void Add(Type key, MarshalAsAttribute value) {{ }}
105+
IEnumerator<KeyValuePair<Type, MarshalAsAttribute>>
106+
IEnumerable<KeyValuePair<Type, MarshalAsAttribute>>.GetEnumerator() =>
107+
throw new NotImplementedException();
108+
IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
109+
}}
96110
}}
97111
98112
namespace System.Runtime.CompilerServices

0 commit comments

Comments
 (0)