Skip to content

Commit 853742b

Browse files
authored
Merge pull request #30 from monkey0506/unmanaged-interfaces
Implement unmanaged interface APIs
2 parents e136063 + a8f3d4b commit 853742b

13 files changed

+445
-50
lines changed

ClosedGenericInterceptor.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,34 @@ private string GetSourceText()
4646
_ = sb.Append(reference.Location.AttributeSourceText).Append($"{Constants.NewLineIndent2}");
4747
}
4848
var method = InterceptsMethod;
49-
var typeParameters = Constants.InterceptorTypeParameters[method.ContainingInterface.Arity];
49+
var typeParameters = method.ContainingInterface.IsUnmanaged ?
50+
Constants.InterceptorUnmanagedTypeParameters[method.ContainingInterface.BaseInterfaceArity] :
51+
Constants.InterceptorTypeParameters[method.ContainingInterface.Arity];
5052
typeParameters = method.Arity != 0 ?
5153
$"<{typeParameters}, XMarshaller>" :
5254
typeParameters.Length != 0 ?
5355
$"<{typeParameters}>" :
5456
typeParameters;
57+
var constraints = method.ContainingInterface.IsUnmanaged ?
58+
Constants.InterceptorUnmanagedTypeConstraints[method.ContainingInterface.BaseInterfaceArity] :
59+
Constants.InterceptorTypeConstraints[method.ContainingInterface.Arity];
60+
var unsafeKeyword = method.IsFromUnsafeFunctionPointer ? "unsafe " : string.Empty;
5561
_ = sb.Append
5662
(
5763
$@"[MethodImpl(MethodImplOptions.AggressiveInlining)]
58-
public static {method.ContainingInterface.FullName} {method.Name}{typeParameters}
64+
public static {unsafeKeyword}{method.ContainingInterface.FullName} {method.Name}{typeParameters}
5965
(
6066
{method.Parameters}
61-
){Constants.InterceptorAntiConstraints[method.ContainingInterface.Arity]}
67+
){constraints}
6268
{{"
6369
);
6470
if (ImplementationClass.MarshalInfo.StaticCallingConvention is not null)
6571
{
72+
var cast = method.IsFromUnsafeFunctionPointer ? "(nint)" : string.Empty;
6673
_ = sb.Append
6774
(
6875
$@"
69-
return new {ImplementationClass.ClassName}({method.FirstParameterName});
76+
return new {ImplementationClass.ClassName}({cast}{method.FirstParameterName});
7077
}}"
7178
);
7279
}

Constants.Actions.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace Monkeymoto.NativeGenericDelegates
1+
using System.Linq;
2+
3+
namespace Monkeymoto.NativeGenericDelegates
24
{
35
internal static partial class Constants
46
{
@@ -24,11 +26,37 @@ internal static class Actions
2426
$"{RootNamespace}.INativeAction`13",
2527
$"{RootNamespace}.INativeAction`14",
2628
$"{RootNamespace}.INativeAction`15",
27-
$"{RootNamespace}.INativeAction`16"
29+
$"{RootNamespace}.INativeAction`16",
30+
"",
31+
$"{RootNamespace}.IUnmanagedAction`2",
32+
$"{RootNamespace}.IUnmanagedAction`4",
33+
$"{RootNamespace}.IUnmanagedAction`6",
34+
$"{RootNamespace}.IUnmanagedAction`8",
35+
$"{RootNamespace}.IUnmanagedAction`10",
36+
$"{RootNamespace}.IUnmanagedAction`12",
37+
$"{RootNamespace}.IUnmanagedAction`14",
38+
$"{RootNamespace}.IUnmanagedAction`16",
39+
$"{RootNamespace}.IUnmanagedAction`18",
40+
$"{RootNamespace}.IUnmanagedAction`20",
41+
$"{RootNamespace}.IUnmanagedAction`22",
42+
$"{RootNamespace}.IUnmanagedAction`24",
43+
$"{RootNamespace}.IUnmanagedAction`26",
44+
$"{RootNamespace}.IUnmanagedAction`28",
45+
$"{RootNamespace}.IUnmanagedAction`30",
46+
$"{RootNamespace}.IUnmanagedAction`32"
2847
];
2948

3049
public static readonly string[] QualifiedTypeParameters = Constants.QualifiedTypeParameters;
3150
public static readonly string[] TypeParameters = Constants.TypeParameters;
51+
52+
public static readonly string[] UnmanagedConstraints =
53+
[
54+
.. AntiConstraints.Select(static x =>
55+
{
56+
var unmanaged = x.Replace(": allows", ": unmanaged, allows").Replace('T', 'U');
57+
return $"{x}{unmanaged}";
58+
})
59+
];
3260
}
3361
}
3462
}

Constants.Funcs.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,24 @@ .. Constants.AntiConstraints.Skip(1)
3131
$"{RootNamespace}.INativeFunc`14",
3232
$"{RootNamespace}.INativeFunc`15",
3333
$"{RootNamespace}.INativeFunc`16",
34-
$"{RootNamespace}.INativeFunc`17"
34+
$"{RootNamespace}.INativeFunc`17",
35+
$"{RootNamespace}.IUnmanagedFunc`2",
36+
$"{RootNamespace}.IUnmanagedFunc`4",
37+
$"{RootNamespace}.IUnmanagedFunc`6",
38+
$"{RootNamespace}.IUnmanagedFunc`8",
39+
$"{RootNamespace}.IUnmanagedFunc`10",
40+
$"{RootNamespace}.IUnmanagedFunc`12",
41+
$"{RootNamespace}.IUnmanagedFunc`14",
42+
$"{RootNamespace}.IUnmanagedFunc`16",
43+
$"{RootNamespace}.IUnmanagedFunc`18",
44+
$"{RootNamespace}.IUnmanagedFunc`20",
45+
$"{RootNamespace}.IUnmanagedFunc`22",
46+
$"{RootNamespace}.IUnmanagedFunc`24",
47+
$"{RootNamespace}.IUnmanagedFunc`26",
48+
$"{RootNamespace}.IUnmanagedFunc`28",
49+
$"{RootNamespace}.IUnmanagedFunc`30",
50+
$"{RootNamespace}.IUnmanagedFunc`32",
51+
$"{RootNamespace}.IUnmanagedFunc`34"
3552
];
3653

3754
public static readonly string[] QualifiedTypeParameters =
@@ -45,6 +62,15 @@ .. Constants.QualifiedTypeParameters.Skip(1).Select(x => $"{x}, out TResult")
4562
"TResult",
4663
.. Constants.TypeParameters.Skip(1).Select(x => $"{x}, TResult")
4764
];
65+
66+
public static readonly string[] UnmanagedConstraints =
67+
[
68+
.. AntiConstraints.Select(static x =>
69+
{
70+
var unmanaged = x.Replace(": allows", ": unmanaged, allows").Replace('T', 'U');
71+
return $"{x}{unmanaged}";
72+
})
73+
];
4874
}
4975
}
5076
}

Constants.cs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,6 @@ internal static partial class Constants
6060
private const string AntiConstraint_T1_T16 =
6161
$"{AntiConstraint_T1_T15}{NewLineIndent2}where T16 : allows ref struct";
6262

63-
public static readonly string[] InterceptorAntiConstraints =
64-
[
65-
.. AntiConstraints.Select(static x => x.Replace(" where T", " where X").Replace('T', 'X')),
66-
$"{AntiConstraint_T1_T16.Replace(" where T", " where X")
67-
.Replace('T', 'X')}{NewLineIndent2}where X17 : allows ref struct"
68-
];
69-
7063
public static readonly string[] Arguments =
7164
[
7265
Arguments_T0,
@@ -115,11 +108,6 @@ .. AntiConstraints.Select(static x => x.Replace(" where T", " where X"
115108
public const string IMarshallerInterfaceName = "IMarshaller";
116109
public const string IMarshallerMetadataName = $"{RootNamespace}.{IMarshallerInterfaceName}`1";
117110

118-
/// <summary>
119-
/// Returns the total number of interfaces per category (Action or Func).
120-
/// </summary>
121-
public const int InterfaceSymbolCountPerCategory = 17;
122-
123111
/// <summary>
124112
/// Returns a newline for a source text string.
125113
/// </summary>
@@ -218,12 +206,29 @@ .. AntiConstraints.Select(static x => x.Replace(" where T", " where X"
218206
private const string TypeParameters_T1_T15 = $"{TypeParameters_T1_T14}, T15";
219207
private const string TypeParameters_T1_T16 = $"{TypeParameters_T1_T15}, T16";
220208

209+
public static readonly string[] InterceptorTypeConstraints =
210+
[
211+
.. AntiConstraints.Select(static x => x.Replace(" where T", " where X").Replace('T', 'X')),
212+
$"{AntiConstraint_T1_T16.Replace(" where T", " where X")
213+
.Replace('T', 'X')}{NewLineIndent3}where X17 : allows ref struct"
214+
];
215+
216+
public static readonly string[] InterceptorUnmanagedTypeConstraints =
217+
[
218+
.. InterceptorTypeConstraints.Select(static x => $"{x.Replace("X", "XT")}{x.Replace("X", "XU").Replace(": allows", ": unmanaged, allows")}")
219+
];
220+
221221
public static readonly string[] InterceptorTypeParameters =
222222
[
223223
.. TypeParameters.Select(static x => x.Replace('T', 'X')),
224224
$"{TypeParameters_T1_T16.Replace('T', 'X')}, X17"
225225
];
226226

227+
public static readonly string[] InterceptorUnmanagedTypeParameters =
228+
[
229+
.. InterceptorTypeParameters.Select(static x => $"{x.Replace("X", "XT")}, {x.Replace("X", "XU")}")
230+
];
231+
227232
private static readonly string[] QualifiedTypeParameters =
228233
[.. TypeParameters.Select(x => x.Replace("T", "in T"))];
229234
}

Generator.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
5252
using System.Runtime.InteropServices;
5353
5454
namespace {Constants.RootNamespace}
55-
{{"
55+
{{
56+
file static class NativeGenericDelegates
57+
{{
58+
public static CallingConvention PlatformDefaultCallingConvention =
59+
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
60+
CallingConvention.StdCall :
61+
CallingConvention.Cdecl;
62+
}}"
5663
);
5764
foreach (var implementationClass in implementationClasses)
5865
{

ImplementationClass.cs

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
35
using System.Runtime.InteropServices;
46
using System.Text;
57

@@ -133,6 +135,44 @@ private string GetSourceText(CallingConvention callingConvention, string? classS
133135
GetFromFunctionPointerConstructor(classSuffix) :
134136
GetFromDelegateConstructor(classSuffix);
135137
var interfaceFullName = Method.ContainingInterface.FullName;
138+
string? baseInterfaceFullName;
139+
string? unmanagedProperties;
140+
if (Method.ContainingInterface.IsUnmanaged)
141+
{
142+
var typeArguments = Method.ContainingInterface.TypeArguments;
143+
var baseTypeArguments = typeArguments.Take(typeArguments.Count / 2);
144+
var baseTypeArgumentList = $"<{string.Join(", ", baseTypeArguments)}>";
145+
baseInterfaceFullName =
146+
$"{Method.ContainingInterface.Name.Replace("Unmanaged", "Native")}{baseTypeArgumentList}";
147+
var unmanagedTypeArgumentList = Method.ContainingInterface.UnmanagedTypeArgumentList;
148+
unmanagedProperties =
149+
$@"
150+
151+
#if UNSAFE
152+
public unsafe delegate* unmanaged[Cdecl]{unmanagedTypeArgumentList} AsCdeclPtr
153+
{{
154+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
155+
get => {GetPointerSourceText(callingConvention, CallingConvention.Cdecl, unmanagedTypeArgumentList)};
156+
}}
157+
158+
public unsafe delegate* unmanaged[Stdcall]{unmanagedTypeArgumentList} AsStdCallPtr
159+
{{
160+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
161+
get => {GetPointerSourceText(callingConvention, CallingConvention.StdCall, unmanagedTypeArgumentList)};
162+
}}
163+
164+
public unsafe delegate* unmanaged[Thiscall]{unmanagedTypeArgumentList} AsThisCallPtr
165+
{{
166+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
167+
get => {GetPointerSourceText(callingConvention, CallingConvention.ThisCall, unmanagedTypeArgumentList)};
168+
}}
169+
#endif // UNSAFE";
170+
}
171+
else
172+
{
173+
baseInterfaceFullName = interfaceFullName;
174+
unmanagedProperties = string.Empty;
175+
}
136176
var invokeParameterCount = Method.ContainingInterface.InvokeParameterCount;
137177
var invokeParameters = GetInvokeParameters();
138178
var returnMarshalAsAttribute = MarshalInfo.MarshalReturnAs is not null ?
@@ -154,7 +194,7 @@ private string GetSourceText(CallingConvention callingConvention, string? classS
154194
private readonly nint functionPtr;
155195
156196
[UnmanagedFunctionPointer(CallingConvention.{callingConvention})]
157-
{returnMarshalAsAttribute}public delegate {returnType} Handler{invokeParameters};
197+
{returnMarshalAsAttribute}public delegate {returnType} Handler{invokeParameters};{unmanagedProperties}
158198
159199
{constructor}
160200
@@ -167,10 +207,48 @@ private string GetSourceText(CallingConvention callingConvention, string? classS
167207
{returnKeyword}handler({Constants.Arguments[invokeParameterCount]});
168208
}}
169209
170-
object {interfaceFullName}.Target => handler.Target;
171-
MethodInfo {interfaceFullName}.Method => handler.Method;{interceptor}
210+
object {baseInterfaceFullName}.Target => handler.Target;
211+
MethodInfo {baseInterfaceFullName}.Method => handler.Method;{interceptor}
172212
}}
173213
";
174214
}
215+
216+
private static string GetPointerSourceText
217+
(
218+
CallingConvention actual,
219+
CallingConvention expected,
220+
string typeArgumentList
221+
)
222+
{
223+
if (actual == CallingConvention.Winapi)
224+
{
225+
return expected switch
226+
{
227+
CallingConvention.Cdecl =>
228+
$"NativeGenericDelegates.PlatformDefaultCallingConvention == CallingConvention.Cdecl ?" +
229+
Constants.NewLineIndent4 +
230+
GetPointerSourceText(CallingConvention.Cdecl, expected, typeArgumentList) +
231+
$" : {Constants.NewLineIndent4}null",
232+
CallingConvention.StdCall =>
233+
$"NativeGenericDelegates.PlatformDefaultCallingConvention == CallingConvention.StdCall ?" +
234+
Constants.NewLineIndent4 +
235+
GetPointerSourceText(CallingConvention.StdCall, expected, typeArgumentList) +
236+
$" : {Constants.NewLineIndent4}null",
237+
_ => "null",
238+
};
239+
}
240+
if (actual != expected)
241+
{
242+
return "null";
243+
}
244+
var callingConvention = actual switch
245+
{
246+
CallingConvention.Cdecl => "Cdecl",
247+
CallingConvention.StdCall => "Stdcall",
248+
CallingConvention.ThisCall => "Thiscall",
249+
_ => throw new UnreachableException()
250+
};
251+
return $"(delegate* unmanaged[{callingConvention}]{typeArgumentList})functionPtr";
252+
}
175253
}
176254
}

InterfaceDescriptor.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@ internal sealed class InterfaceDescriptor : IEquatable<InterfaceDescriptor>
1010
private readonly int hashCode;
1111

1212
public int Arity { get; }
13+
public int BaseInterfaceArity { get; }
1314
public string Category { get; }
1415
public string FullName { get; }
1516
public int InvokeParameterCount { get; }
1617
public bool IsAction { get; }
18+
public bool IsUnmanaged { get; }
1719
public string Name { get; }
1820
public string ReturnKeyword { get; }
1921
public string ReturnType { get; }
2022
public IReadOnlyList<string> TypeArguments { get; }
2123
public string TypeArgumentList { get; }
24+
public string UnmanagedTypeArgumentList { get; }
2225

2326
public static bool operator ==(InterfaceDescriptor? left, InterfaceDescriptor? right) =>
2427
left?.Equals(right) ?? right is null;
@@ -27,10 +30,32 @@ internal sealed class InterfaceDescriptor : IEquatable<InterfaceDescriptor>
2730
public InterfaceDescriptor(INamedTypeSymbol interfaceSymbol)
2831
{
2932
bool isAction = interfaceSymbol.Name.Contains(Constants.CategoryAction);
33+
bool isUnmanaged = interfaceSymbol.Name.Contains("Unmanaged");
3034
Arity = interfaceSymbol.Arity;
31-
InvokeParameterCount = Arity - (isAction ? 0 : 1);
32-
Name = interfaceSymbol.Name;
3335
TypeArguments = [.. interfaceSymbol.TypeArguments.Select(static x => x.ToDisplayString())];
36+
if (isUnmanaged)
37+
{
38+
BaseInterfaceArity = Arity / 2;
39+
IsUnmanaged = true;
40+
if (Arity == 0)
41+
{
42+
UnmanagedTypeArgumentList = "<void>";
43+
}
44+
else
45+
{
46+
var unmanagedTypeArguments = TypeArguments.Skip(BaseInterfaceArity);
47+
var voidReturn = isAction ? ", void" : string.Empty;
48+
UnmanagedTypeArgumentList = $"<{string.Join(", ", unmanagedTypeArguments)}{voidReturn}>";
49+
}
50+
}
51+
else
52+
{
53+
BaseInterfaceArity = Arity;
54+
IsUnmanaged = false;
55+
UnmanagedTypeArgumentList = string.Empty;
56+
}
57+
InvokeParameterCount = BaseInterfaceArity - (isAction ? 0 : 1);
58+
Name = interfaceSymbol.Name;
3459
if (isAction)
3560
{
3661
Category = Constants.CategoryAction;

0 commit comments

Comments
 (0)