Skip to content

Commit 1dad145

Browse files
committed
Support runtime fallback for CallingConvention
1 parent 6e695cd commit 1dad145

File tree

5 files changed

+95
-51
lines changed

5 files changed

+95
-51
lines changed

AnalyzerReleases.Unshipped.md

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

ClosedGenericInterceptor.cs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Collections.Immutable;
44
using System.Linq;
5+
using System.Runtime.InteropServices;
56
using System.Text;
67

78
namespace Monkeymoto.NativeGenericDelegates
@@ -43,22 +44,44 @@ private string GetSourceText()
4344
var sb = new StringBuilder($"{Constants.NewLineIndent2}");
4445
foreach (var reference in InterceptedMethodReferences.Select(x => x.MethodReference))
4546
{
46-
_ = sb.Append($"{Constants.NewLineIndent2}").Append(reference.InterceptorAttributeSourceText);
47+
_ = sb.Append(reference.InterceptorAttributeSourceText).Append($"{Constants.NewLineIndent2}");
4748
}
4849
var method = InterceptsMethod;
4950
var typeParameters = GetTypeParameters(method.ContainingInterface.Arity, method.Arity);
5051
_ = sb.Append
5152
(
52-
$@"
53-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
53+
$@"[MethodImpl(MethodImplOptions.AggressiveInlining)]
5454
public static {method.ContainingInterface.FullName} {method.Name}{typeParameters}
5555
(
5656
{method.Parameters}
5757
)
58-
{{
58+
{{"
59+
);
60+
if (ImplementationClass.Marshalling.StaticCallingConvention is not null)
61+
{
62+
_ = sb.Append
63+
(
64+
$@"
5965
return new {ImplementationClass.ClassName}({method.FirstParameterName});
6066
}}"
61-
);
67+
);
68+
}
69+
else
70+
{
71+
_ = sb.Append
72+
(
73+
$@"
74+
return callingConvention switch
75+
{{
76+
CallingConvention.Cdecl => ({method.ContainingInterface.FullName})new {ImplementationClass.ClassName}_{nameof(CallingConvention.Cdecl)}({method.FirstParameterName}),
77+
CallingConvention.StdCall => new {ImplementationClass.ClassName}_{nameof(CallingConvention.StdCall)}({method.FirstParameterName}),
78+
CallingConvention.ThisCall => new {ImplementationClass.ClassName}_{nameof(CallingConvention.ThisCall)}({method.FirstParameterName}),
79+
CallingConvention.Winapi => new {ImplementationClass.ClassName}_{nameof(CallingConvention.Winapi)}({method.FirstParameterName}),
80+
_ => throw new NotImplementedException()
81+
}};
82+
}}"
83+
);
84+
}
6285
return sb.ToString();
6386
}
6487
internal static string GetTypeParameters(int interfaceArity, int methodArity)

DelegateMarshalling.cs

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ internal sealed partial class DelegateMarshalling : IEquatable<DelegateMarshalli
1313
{
1414
private readonly int hashCode;
1515

16-
public CallingConvention CallingConvention { get; }
1716
public IReadOnlyList<string?>? MarshalParamsAs { get; }
1817
public string? MarshalReturnAs { get; }
18+
public string? RuntimeCallingConvention { get; }
19+
public CallingConvention? StaticCallingConvention { get; }
1920

2021
public static bool operator ==(DelegateMarshalling? left, DelegateMarshalling? right) =>
2122
left?.Equals(right) ?? right is null;
@@ -51,45 +52,45 @@ CancellationToken cancellationToken
5152
break;
5253
}
5354
}
54-
CallingConvention callingConvention = CallingConvention.Winapi;
55+
MarshalParamsAs = Parser.GetMarshalParamsAs
56+
(
57+
marshalParamsAsArgument,
58+
interfaceDescriptor.InvokeParameterCount,
59+
diagnostics,
60+
cancellationToken
61+
);
62+
MarshalReturnAs = Parser.GetMarshalReturnAs(marshalReturnAsArgument, diagnostics, cancellationToken);
5563
if (callingConventionArgument is not null)
5664
{
57-
bool isValid = false;
58-
var field = (callingConventionArgument.Value as IFieldReferenceOperation)?.Field;
65+
var value = callingConventionArgument.Value;
66+
var field = (value as IFieldReferenceOperation)?.Field;
5967
if (field is not null && SymbolEqualityComparer.Default.Equals(field.ContainingType, field.Type) &&
60-
Enum.TryParse(field?.Name, false, out callingConvention))
68+
Enum.TryParse(field?.Name, false, out CallingConvention callingConvention))
6169
{
62-
isValid = true;
70+
RuntimeCallingConvention = null;
71+
StaticCallingConvention = callingConvention;
6372
}
64-
if (!isValid)
73+
else
6574
{
66-
callingConvention = CallingConvention.Winapi;
67-
diagnostics.Add
68-
(
69-
Diagnostic.Create
70-
(
71-
Diagnostics.NGD1002_InvalidCallingConventionArgument,
72-
callingConventionArgument.Syntax.GetLocation()
73-
)
74-
);
75+
RuntimeCallingConvention = value.ToString();
76+
StaticCallingConvention = null;
7577
}
7678
}
77-
CallingConvention = callingConvention;
78-
MarshalParamsAs = Parser.GetMarshalParamsAs
79+
hashCode = Hash.Combine
7980
(
80-
marshalParamsAsArgument,
81-
interfaceDescriptor.InvokeParameterCount,
82-
diagnostics,
83-
cancellationToken
81+
MarshalParamsAs,
82+
MarshalReturnAs,
83+
RuntimeCallingConvention,
84+
StaticCallingConvention
8485
);
85-
MarshalReturnAs = Parser.GetMarshalReturnAs(marshalReturnAsArgument, diagnostics, cancellationToken);
86-
hashCode = Hash.Combine(CallingConvention, MarshalParamsAs, MarshalReturnAs);
8786
}
8887

8988
public override bool Equals(object? obj) => obj is DelegateMarshalling other && Equals(other);
9089
public bool Equals(DelegateMarshalling? other) =>
91-
(other is not null) && (CallingConvention == other.CallingConvention) &&
92-
MarshalParamsAs.SequenceEqual(other.MarshalParamsAs) && (MarshalReturnAs == other.MarshalReturnAs);
90+
(other is not null) && MarshalParamsAs.SequenceEqual(other.MarshalParamsAs) &&
91+
(MarshalReturnAs == other.MarshalReturnAs) &&
92+
(RuntimeCallingConvention == other.RuntimeCallingConvention) &&
93+
(StaticCallingConvention == other.StaticCallingConvention);
9394
public override int GetHashCode() => hashCode;
9495
}
9596
}

Diagnostics.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,6 @@ internal static class Diagnostics
1515
true
1616
);
1717

18-
public static readonly DiagnosticDescriptor NGD1002_InvalidCallingConventionArgument = new
19-
(
20-
"NGD1002",
21-
"NGD1002: Invalid CallingConvention argument",
22-
"CallingConvention argument must be literal or static readonly field",
23-
"Usage",
24-
DiagnosticSeverity.Error,
25-
true
26-
);
27-
2818
public static readonly DiagnosticDescriptor NGD1003_MarshalAsArgumentSpreadElementNotSupported = new
2919
(
3020
"NGD1003",

ImplementationClass.cs

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Runtime.InteropServices;
45
using System.Text;
56

67
namespace Monkeymoto.NativeGenericDelegates
@@ -46,22 +47,22 @@ IReadOnlyList<MethodReference> methodReferences
4647
public bool Equals(ImplementationClass? other) => (other is not null) && (SourceText == other.SourceText);
4748
public override int GetHashCode() => hashCode;
4849

49-
private string GetFromDelegateConstructor()
50+
private string GetFromDelegateConstructor(string classSuffix)
5051
{
5152
var firstParam = Method.FirstParameterName;
5253
return
53-
$@"internal {ClassName}({Method.FirstParameterType} {firstParam})
54+
$@"internal {ClassName}{classSuffix}({Method.FirstParameterType} {firstParam})
5455
{{
5556
ArgumentNullException.ThrowIfNull({firstParam});
5657
handler = (Handler)Delegate.CreateDelegate(typeof(Handler), {firstParam}.Target, {firstParam}.Method);
5758
functionPtr = Marshal.GetFunctionPointerForDelegate(handler);
5859
}}";
5960
}
6061

61-
private string GetFromFunctionPointerConstructor()
62+
private string GetFromFunctionPointerConstructor(string classSuffix)
6263
{
6364
return
64-
$@"internal {ClassName}(nint functionPtr)
65+
$@"internal {ClassName}{classSuffix}(nint functionPtr)
6566
{{
6667
if (functionPtr == nint.Zero)
6768
{{
@@ -104,10 +105,33 @@ private string GetInvokeParameters()
104105

105106
private string GetSourceText()
106107
{
107-
var callingConvention = Marshalling.CallingConvention;
108+
if (Marshalling.StaticCallingConvention is not null)
109+
{
110+
return
111+
GetSourceText(Marshalling.StaticCallingConvention.Value);
112+
}
113+
var interceptor = Interceptor?.SourceText;
114+
if (interceptor is not null)
115+
{
116+
interceptor =
117+
$@"
118+
file static class {ClassName}
119+
{{{interceptor}
120+
}}";
121+
}
122+
return
123+
$@"{GetSourceText(CallingConvention.Cdecl, $"_{nameof(CallingConvention.Cdecl)}")}
124+
{GetSourceText(CallingConvention.StdCall, $"_{nameof(CallingConvention.StdCall)}")}
125+
{GetSourceText(CallingConvention.ThisCall, $"_{nameof(CallingConvention.ThisCall)}")}
126+
{GetSourceText(CallingConvention.Winapi, $"_{nameof(CallingConvention.Winapi)}")}{interceptor ?? string.Empty}";
127+
}
128+
129+
private string GetSourceText(CallingConvention callingConvention, string? classSuffix = null)
130+
{
131+
classSuffix ??= string.Empty;
108132
var constructor = Method.IsFromFunctionPointer ?
109-
GetFromFunctionPointerConstructor() :
110-
GetFromDelegateConstructor();
133+
GetFromFunctionPointerConstructor(classSuffix) :
134+
GetFromDelegateConstructor(classSuffix);
111135
var interfaceFullName = Method.ContainingInterface.FullName;
112136
var invokeParameterCount = Method.ContainingInterface.InvokeParameterCount;
113137
var invokeParameters = GetInvokeParameters();
@@ -127,8 +151,15 @@ private string GetSourceText()
127151
returnType = Method.ContainingInterface.TypeArguments.Last();
128152
break;
129153
}
154+
var interceptor = Marshalling.StaticCallingConvention is not null ?
155+
Interceptor?.SourceText ?? string.Empty :
156+
string.Empty;
157+
if (interceptor != string.Empty)
158+
{
159+
interceptor = $"{Constants.NewLineIndent2}{interceptor}";
160+
}
130161
return
131-
$@"file sealed class {ClassName} : {interfaceFullName}
162+
$@"file sealed class {ClassName}{classSuffix} : {interfaceFullName}
132163
{{
133164
private readonly Handler handler;
134165
private readonly nint functionPtr;
@@ -145,7 +176,7 @@ private string GetSourceText()
145176
{returnMarshalAsAttribute}public {returnType} Invoke{invokeParameters}
146177
{{
147178
{returnKeyword}handler({Constants.Arguments[invokeParameterCount]});
148-
}}{Interceptor?.SourceText ?? string.Empty}
179+
}}{interceptor}
149180
}}
150181
";
151182
}

0 commit comments

Comments
 (0)