@nativescript/windows 0.1.0-alpha.2 → 0.1.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/framework/__PROJECT_NAME__/App.xaml.cs +39 -2
- package/framework/__PROJECT_NAME__/Assets/SplashScreen.png +0 -0
- package/framework/__PROJECT_NAME__/Assets/Square150x150Logo.png +0 -0
- package/framework/__PROJECT_NAME__/Assets/Square44x44Logo.png +0 -0
- package/framework/__PROJECT_NAME__/Assets/StoreLogo.png +0 -0
- package/framework/__PROJECT_NAME__/Assets/Wide310x150Logo.png +0 -0
- package/framework/__PROJECT_NAME__/CrashDiagnostics.cs +132 -0
- package/framework/__PROJECT_NAME__/Package.appxmanifest +2 -5
- package/framework/__PROJECT_NAME__/RuntimeHost.cs +122 -18
- package/framework/__PROJECT_NAME__/__PROJECT_NAME__.csproj +70 -18
- package/framework/dotnet-bridge/BinaryProtocol.cs +124 -0
- package/framework/dotnet-bridge/Bridge.BinaryDispatch.cs +177 -0
- package/framework/dotnet-bridge/Bridge.Dispatch.cs +348 -0
- package/framework/dotnet-bridge/Bridge.JsDelegate.cs +100 -0
- package/framework/dotnet-bridge/Bridge.cs +44 -320
- package/framework/dotnet-bridge/DispatchTypes.cs +281 -0
- package/framework/libs/arm64/nativescript.dll +0 -0
- package/framework/libs/devtools/arm64/nativescript.dll +0 -0
- package/framework/libs/devtools/x64/nativescript.dll +0 -0
- package/framework/libs/x64/nativescript.dll +0 -0
- package/package.json +1 -1
- package/framework/__PROJECT_NAME__/App/main.js +0 -12
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Buffers;
|
|
3
|
+
using System.Buffers.Binary;
|
|
4
|
+
using System.Text;
|
|
5
|
+
|
|
6
|
+
namespace NativeScriptBridge;
|
|
7
|
+
|
|
8
|
+
internal readonly struct HandleRef(int id)
|
|
9
|
+
{
|
|
10
|
+
public readonly int Id = id;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
internal ref struct BinReader(ReadOnlySpan<byte> buf)
|
|
14
|
+
{
|
|
15
|
+
private readonly ReadOnlySpan<byte> _buf = buf;
|
|
16
|
+
private int _pos = 0;
|
|
17
|
+
|
|
18
|
+
public byte ReadByte() => _buf[_pos++];
|
|
19
|
+
|
|
20
|
+
public int ReadI32()
|
|
21
|
+
{
|
|
22
|
+
var v = BinaryPrimitives.ReadInt32LittleEndian(_buf[_pos..]);
|
|
23
|
+
_pos += 4;
|
|
24
|
+
return v;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public ushort ReadU16()
|
|
28
|
+
{
|
|
29
|
+
var v = BinaryPrimitives.ReadUInt16LittleEndian(_buf[_pos..]);
|
|
30
|
+
_pos += 2;
|
|
31
|
+
return v;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public double ReadF64()
|
|
35
|
+
{
|
|
36
|
+
var v = BinaryPrimitives.ReadDoubleLittleEndian(_buf[_pos..]);
|
|
37
|
+
_pos += 8;
|
|
38
|
+
return v;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public string ReadString16()
|
|
42
|
+
{
|
|
43
|
+
var len = ReadU16();
|
|
44
|
+
var s = Encoding.UTF8.GetString(_buf.Slice(_pos, len));
|
|
45
|
+
_pos += len;
|
|
46
|
+
// Intern so repeated method/type names reuse the same heap string.
|
|
47
|
+
// Eliminates the allocation on every subsequent warm-path call.
|
|
48
|
+
return string.Intern(s);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public object?[] ReadArgs()
|
|
52
|
+
{
|
|
53
|
+
var count = ReadByte();
|
|
54
|
+
if (count == 0) return [];
|
|
55
|
+
var args = new object?[count];
|
|
56
|
+
for (int i = 0; i < count; i++)
|
|
57
|
+
{
|
|
58
|
+
var tag = ReadByte();
|
|
59
|
+
args[i] = tag switch
|
|
60
|
+
{
|
|
61
|
+
0x00 => null,
|
|
62
|
+
0x01 => (object)false,
|
|
63
|
+
0x02 => (object)true,
|
|
64
|
+
0x03 => (object)ReadI32(),
|
|
65
|
+
0x04 => (object)ReadF64(),
|
|
66
|
+
0x05 => (object)ReadString16(),
|
|
67
|
+
0x06 => (object)new HandleRef(ReadI32()),
|
|
68
|
+
_ => null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return args;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
internal ref struct BinWriter(ArrayBufferWriter<byte> buf)
|
|
76
|
+
{
|
|
77
|
+
private readonly ArrayBufferWriter<byte> _buf = buf;
|
|
78
|
+
|
|
79
|
+
public void WriteByte(byte b)
|
|
80
|
+
{
|
|
81
|
+
_buf.GetSpan(1)[0] = b;
|
|
82
|
+
_buf.Advance(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public void WriteI32(int v)
|
|
86
|
+
{
|
|
87
|
+
BinaryPrimitives.WriteInt32LittleEndian(_buf.GetSpan(4), v);
|
|
88
|
+
_buf.Advance(4);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public void WriteU16(ushort v)
|
|
92
|
+
{
|
|
93
|
+
BinaryPrimitives.WriteUInt16LittleEndian(_buf.GetSpan(2), v);
|
|
94
|
+
_buf.Advance(2);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public void WriteU32(uint v)
|
|
98
|
+
{
|
|
99
|
+
BinaryPrimitives.WriteUInt32LittleEndian(_buf.GetSpan(4), v);
|
|
100
|
+
_buf.Advance(4);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public void WriteF64(double v)
|
|
104
|
+
{
|
|
105
|
+
BinaryPrimitives.WriteDoubleLittleEndian(_buf.GetSpan(8), v);
|
|
106
|
+
_buf.Advance(8);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public void WriteString32(ReadOnlySpan<char> s)
|
|
110
|
+
{
|
|
111
|
+
var byteCount = Encoding.UTF8.GetByteCount(s);
|
|
112
|
+
WriteU32((uint)byteCount);
|
|
113
|
+
Encoding.UTF8.GetBytes(s, _buf.GetSpan(byteCount));
|
|
114
|
+
_buf.Advance(byteCount);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public void WriteString16(ReadOnlySpan<char> s)
|
|
118
|
+
{
|
|
119
|
+
var byteCount = Encoding.UTF8.GetByteCount(s);
|
|
120
|
+
WriteU16((ushort)byteCount);
|
|
121
|
+
Encoding.UTF8.GetBytes(s, _buf.GetSpan(byteCount));
|
|
122
|
+
_buf.Advance(byteCount);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Buffers;
|
|
3
|
+
using System.Collections.Generic;
|
|
4
|
+
using System.Reflection;
|
|
5
|
+
using System.Runtime.InteropServices;
|
|
6
|
+
|
|
7
|
+
namespace NativeScriptBridge;
|
|
8
|
+
|
|
9
|
+
public static partial class Bridge
|
|
10
|
+
{
|
|
11
|
+
[UnmanagedCallersOnly(EntryPoint = "InvokeBinary",
|
|
12
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
13
|
+
public static unsafe int InvokeBinary(
|
|
14
|
+
byte* requestPtr, int requestLen,
|
|
15
|
+
byte** responsePtr, int* responseLenPtr)
|
|
16
|
+
{
|
|
17
|
+
try
|
|
18
|
+
{
|
|
19
|
+
var r = new BinReader(new ReadOnlySpan<byte>(requestPtr, requestLen));
|
|
20
|
+
var res = DispatchBin(ref r);
|
|
21
|
+
var buf = new ArrayBufferWriter<byte>(128);
|
|
22
|
+
res.WriteAsBin(buf);
|
|
23
|
+
WriteUnmanaged(buf.WrittenSpan, responsePtr, responseLenPtr);
|
|
24
|
+
}
|
|
25
|
+
catch (Exception ex)
|
|
26
|
+
{
|
|
27
|
+
WriteBinError(Unwrap(ex).Message, responsePtr, responseLenPtr);
|
|
28
|
+
}
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
internal static DispatchResult DispatchBin(ref BinReader r)
|
|
33
|
+
{
|
|
34
|
+
var op = r.ReadByte();
|
|
35
|
+
|
|
36
|
+
if (op == 0x04) // release
|
|
37
|
+
{
|
|
38
|
+
s_handles.TryRemove(r.ReadI32(), out _);
|
|
39
|
+
return DispatchResult.Void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (op == 0x05) // members by handle
|
|
43
|
+
{
|
|
44
|
+
var h = r.ReadI32();
|
|
45
|
+
if (!s_handles.TryGetValue(h, out var obj))
|
|
46
|
+
throw new KeyNotFoundException($"Invalid handle {h}");
|
|
47
|
+
return BuildMembersResult(
|
|
48
|
+
obj?.GetType() ?? throw new InvalidOperationException("Handle is null"));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (op == 0x06) // members by type
|
|
52
|
+
{
|
|
53
|
+
var typeName = r.ReadString16();
|
|
54
|
+
var assembly = r.ReadString16();
|
|
55
|
+
var type = ResolveType(NullIfEmpty(assembly), typeName)
|
|
56
|
+
?? throw new TypeLoadException($"Type not found: {typeName}");
|
|
57
|
+
return BuildMembersResult(type);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (op == 0x01) // instance call
|
|
61
|
+
{
|
|
62
|
+
var handle = r.ReadI32();
|
|
63
|
+
if (!s_handles.TryGetValue(handle, out var target))
|
|
64
|
+
throw new KeyNotFoundException($"Invalid handle {handle}");
|
|
65
|
+
var type = target?.GetType() ?? throw new InvalidOperationException("Handle is null");
|
|
66
|
+
var method = r.ReadString16();
|
|
67
|
+
var args = r.ReadArgs();
|
|
68
|
+
return DispatchCallBin(target, type, method, args, isStatic: false);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (op == 0x09) // create JS delegate
|
|
72
|
+
{
|
|
73
|
+
var delTypeName = r.ReadString16(); // "" → System.Action
|
|
74
|
+
var callbackId = r.ReadI32();
|
|
75
|
+
return CreateJsDelegate(delTypeName, callbackId);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Static ops: 0x02 = call, 0x03 = constructor
|
|
79
|
+
var typeNameS = r.ReadString16();
|
|
80
|
+
var assemblyS = r.ReadString16();
|
|
81
|
+
var typeS = ResolveType(NullIfEmpty(assemblyS), typeNameS)
|
|
82
|
+
?? throw new TypeLoadException($"Type not found: {typeNameS}");
|
|
83
|
+
|
|
84
|
+
if (op == 0x03) // constructor
|
|
85
|
+
{
|
|
86
|
+
var args = r.ReadArgs();
|
|
87
|
+
var entry = GetCachedCtor(typeS, args.Length);
|
|
88
|
+
if (entry.Ctor is null)
|
|
89
|
+
throw new MissingMethodException(
|
|
90
|
+
$"No public ctor on {typeS.FullName} for {args.Length} args");
|
|
91
|
+
// ConstructorInfo.Invoke requires exact arg count — build a precise array.
|
|
92
|
+
return Box(entry.Ctor.Invoke(BuildArgsBinExact(args, entry.Parameters)));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// op == 0x02: static call
|
|
96
|
+
var methodS = r.ReadString16();
|
|
97
|
+
var argsS = r.ReadArgs();
|
|
98
|
+
return DispatchCallBin(null, typeS, methodS, argsS, isStatic: true);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private static DispatchResult DispatchCallBin(
|
|
102
|
+
object? target, Type type, string method, object?[] args, bool isStatic)
|
|
103
|
+
{
|
|
104
|
+
var flags = (isStatic ? BindingFlags.Static : BindingFlags.Instance) | BindingFlags.Public;
|
|
105
|
+
|
|
106
|
+
if (method.Length > 4
|
|
107
|
+
&& method[0] == 'g' && method[1] == 'e' && method[2] == 't' && method[3] == '_')
|
|
108
|
+
{
|
|
109
|
+
var prop = GetCachedProp(type, method, 4, flags);
|
|
110
|
+
if (prop is not null) return Box(prop.GetValue(target));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (method.Length > 4
|
|
114
|
+
&& method[0] == 's' && method[1] == 'e' && method[2] == 't' && method[3] == '_'
|
|
115
|
+
&& args.Length == 1)
|
|
116
|
+
{
|
|
117
|
+
var prop = GetCachedProp(type, method, 4, flags);
|
|
118
|
+
if (prop is not null)
|
|
119
|
+
{
|
|
120
|
+
prop.SetValue(target, CoerceBin(args[0], prop.PropertyType));
|
|
121
|
+
return DispatchResult.Void;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
var entry = GetCachedMethod(type, method, args.Length, flags);
|
|
126
|
+
if (entry.Invoke is null)
|
|
127
|
+
throw new MissingMethodException(
|
|
128
|
+
$"Method '{method}' ({args.Length} args) not found on {type.FullName}");
|
|
129
|
+
|
|
130
|
+
var builtArgs = BuildArgsBin(args, entry.Parameters);
|
|
131
|
+
try { return Box(AwaitIfTask(entry.Invoke(target, builtArgs))); }
|
|
132
|
+
finally { if (builtArgs.Length > 0) ReturnArgs(builtArgs); }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private static object?[] BuildArgsBin(object?[] binArgs, ParameterInfo[] parameters)
|
|
136
|
+
{
|
|
137
|
+
if (parameters.Length == 0) return [];
|
|
138
|
+
var result = ArrayPool<object?>.Shared.Rent(parameters.Length);
|
|
139
|
+
for (int i = 0; i < parameters.Length && i < binArgs.Length; i++)
|
|
140
|
+
result[i] = CoerceBin(binArgs[i], parameters[i].ParameterType);
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private static object?[] BuildArgsBinExact(object?[] binArgs, ParameterInfo[] parameters)
|
|
145
|
+
{
|
|
146
|
+
if (parameters.Length == 0) return [];
|
|
147
|
+
var result = new object?[parameters.Length];
|
|
148
|
+
for (int i = 0; i < parameters.Length && i < binArgs.Length; i++)
|
|
149
|
+
result[i] = CoerceBin(binArgs[i], parameters[i].ParameterType);
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private static object? CoerceBin(object? value, Type targetType)
|
|
154
|
+
{
|
|
155
|
+
if (value is null) return null;
|
|
156
|
+
if (value is HandleRef hr)
|
|
157
|
+
{
|
|
158
|
+
s_handles.TryGetValue(hr.Id, out var obj);
|
|
159
|
+
return obj;
|
|
160
|
+
}
|
|
161
|
+
if (value.GetType() == targetType) return value;
|
|
162
|
+
try { return Convert.ChangeType(value, targetType); }
|
|
163
|
+
catch { return value; }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private static string? NullIfEmpty(string s) => s.Length == 0 ? null : s;
|
|
167
|
+
|
|
168
|
+
private static unsafe void WriteBinError(string msg, byte** outPtr, int* outLen)
|
|
169
|
+
{
|
|
170
|
+
var msgBytes = System.Text.Encoding.UTF8.GetByteCount(msg);
|
|
171
|
+
var buf = new ArrayBufferWriter<byte>(5 + msgBytes);
|
|
172
|
+
var w = new BinWriter(buf);
|
|
173
|
+
w.WriteByte(0xFF);
|
|
174
|
+
w.WriteString32(msg);
|
|
175
|
+
WriteUnmanaged(buf.WrittenSpan, outPtr, outLen);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Buffers;
|
|
3
|
+
using System.Collections;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.Linq;
|
|
6
|
+
using System.Linq.Expressions;
|
|
7
|
+
using System.Reflection;
|
|
8
|
+
using System.Runtime.InteropServices;
|
|
9
|
+
using System.Text.Json;
|
|
10
|
+
using System.Threading;
|
|
11
|
+
using System.Threading.Tasks;
|
|
12
|
+
|
|
13
|
+
namespace NativeScriptBridge;
|
|
14
|
+
|
|
15
|
+
public static partial class Bridge
|
|
16
|
+
{
|
|
17
|
+
internal static DispatchResult Dispatch(InvokeRequest req)
|
|
18
|
+
{
|
|
19
|
+
var method = req.Method ?? throw new ArgumentException("Method is required");
|
|
20
|
+
|
|
21
|
+
if (method == "__release" && req.Handle.HasValue)
|
|
22
|
+
{
|
|
23
|
+
s_handles.TryRemove(req.Handle.Value, out _);
|
|
24
|
+
return DispatchResult.Void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
object? target = null;
|
|
28
|
+
Type type;
|
|
29
|
+
|
|
30
|
+
if (req.Handle.HasValue)
|
|
31
|
+
{
|
|
32
|
+
if (!s_handles.TryGetValue(req.Handle.Value, out target))
|
|
33
|
+
throw new KeyNotFoundException($"Invalid handle {req.Handle.Value}");
|
|
34
|
+
type = target?.GetType() ?? throw new InvalidOperationException("Handle refers to null");
|
|
35
|
+
}
|
|
36
|
+
else
|
|
37
|
+
{
|
|
38
|
+
type = ResolveType(req.Assembly, req.TypeName)
|
|
39
|
+
?? throw new TypeLoadException($"Type not found: {req.TypeName}");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (method == "__members__")
|
|
43
|
+
return BuildMembersResult(type);
|
|
44
|
+
|
|
45
|
+
if (method == ".ctor")
|
|
46
|
+
{
|
|
47
|
+
var argElems = req.Args ?? [];
|
|
48
|
+
var entry = GetCachedCtor(type, argElems.Length);
|
|
49
|
+
if (entry.Ctor is null)
|
|
50
|
+
throw new MissingMethodException(
|
|
51
|
+
$"No public constructor on {type.FullName} for {argElems.Length} args");
|
|
52
|
+
// ConstructorInfo.Invoke validates args.Length == paramCount exactly — no pool.
|
|
53
|
+
return Box(entry.Ctor.Invoke(BuildArgsExact(argElems, entry.Parameters)));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
var isStatic = target is null;
|
|
57
|
+
var flags = (isStatic ? BindingFlags.Static : BindingFlags.Instance) | BindingFlags.Public;
|
|
58
|
+
|
|
59
|
+
if (method.Length > 4)
|
|
60
|
+
{
|
|
61
|
+
if (method[0] == 'g' && method[1] == 'e' && method[2] == 't' && method[3] == '_')
|
|
62
|
+
{
|
|
63
|
+
var prop = GetCachedProp(type, method, 4, flags);
|
|
64
|
+
if (prop is not null) return Box(prop.GetValue(target));
|
|
65
|
+
}
|
|
66
|
+
else if (method[0] == 's' && method[1] == 'e' && method[2] == 't' && method[3] == '_'
|
|
67
|
+
&& req.Args?.Length == 1)
|
|
68
|
+
{
|
|
69
|
+
var prop = GetCachedProp(type, method, 4, flags);
|
|
70
|
+
if (prop is not null)
|
|
71
|
+
{
|
|
72
|
+
prop.SetValue(target, Coerce(req.Args[0], prop.PropertyType));
|
|
73
|
+
return DispatchResult.Void;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
var args = req.Args ?? [];
|
|
79
|
+
var dispEntry = GetCachedMethod(type, method, args.Length, flags);
|
|
80
|
+
if (dispEntry.Invoke is null)
|
|
81
|
+
throw new MissingMethodException(
|
|
82
|
+
$"Method '{method}' ({args.Length} args) not found on {type.FullName}");
|
|
83
|
+
|
|
84
|
+
var builtArgs = BuildArgs(args, dispEntry.Parameters);
|
|
85
|
+
try { return Box(AwaitIfTask(dispEntry.Invoke(target, builtArgs))); }
|
|
86
|
+
finally { if (builtArgs.Length > 0) ReturnArgs(builtArgs); }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── compiled dispatch cache ───────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
private static DispatchEntry GetCachedMethod(Type type, string name, int argCount, BindingFlags flags)
|
|
92
|
+
=> s_methodCache.GetOrAdd(
|
|
93
|
+
new MethodKey(type, name, argCount, flags),
|
|
94
|
+
static k => BuildDispatchEntry(k.Type, k.Name, k.ArgCount, k.Flags));
|
|
95
|
+
|
|
96
|
+
private static DispatchEntry BuildDispatchEntry(Type type, string name, int argCount, BindingFlags flags)
|
|
97
|
+
{
|
|
98
|
+
var mi = FindMethodCore(type, name, argCount, flags);
|
|
99
|
+
if (mi is null) return DispatchEntry.Empty;
|
|
100
|
+
|
|
101
|
+
var parameters = mi.GetParameters();
|
|
102
|
+
|
|
103
|
+
try
|
|
104
|
+
{
|
|
105
|
+
var targetParam = Expression.Parameter(typeof(object), "t");
|
|
106
|
+
var argsParam = Expression.Parameter(typeof(object?[]), "a");
|
|
107
|
+
|
|
108
|
+
var typedArgs = parameters.Select((p, i) =>
|
|
109
|
+
(Expression)Expression.Convert(
|
|
110
|
+
Expression.ArrayIndex(argsParam, Expression.Constant(i)),
|
|
111
|
+
p.ParameterType)).ToArray();
|
|
112
|
+
|
|
113
|
+
Expression body = mi.IsStatic
|
|
114
|
+
? Expression.Call(mi, typedArgs)
|
|
115
|
+
: Expression.Call(Expression.Convert(targetParam, type), mi, typedArgs);
|
|
116
|
+
|
|
117
|
+
if (body.Type == typeof(void))
|
|
118
|
+
body = Expression.Block(typeof(object), body, Expression.Constant(null, typeof(object)));
|
|
119
|
+
else if (body.Type != typeof(object))
|
|
120
|
+
body = Expression.Convert(body, typeof(object));
|
|
121
|
+
|
|
122
|
+
var fn = Expression.Lambda<Func<object?, object?[], object?>>(
|
|
123
|
+
body, targetParam, argsParam).Compile();
|
|
124
|
+
|
|
125
|
+
return new DispatchEntry(fn, parameters);
|
|
126
|
+
}
|
|
127
|
+
catch
|
|
128
|
+
{
|
|
129
|
+
// mi.Invoke validates args.Length == paramCount; slice the rented buffer if needed.
|
|
130
|
+
int pc = parameters.Length;
|
|
131
|
+
return new DispatchEntry(
|
|
132
|
+
(t, a) => mi.Invoke(t, a.Length == pc ? a : a[..pc]),
|
|
133
|
+
parameters);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private static CtorEntry GetCachedCtor(Type type, int argCount)
|
|
138
|
+
=> s_ctorCache.GetOrAdd(new CtorKey(type, argCount), static k =>
|
|
139
|
+
{
|
|
140
|
+
var ctors = k.Type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
|
|
141
|
+
var ctor = Array.Find(ctors, c => c.GetParameters().Length == k.ArgCount)
|
|
142
|
+
?? ctors.FirstOrDefault();
|
|
143
|
+
return new CtorEntry(ctor, ctor?.GetParameters() ?? []);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
private static PropertyInfo? GetCachedProp(Type type, string method, int prefixLen, BindingFlags flags)
|
|
147
|
+
=> s_propCache.GetOrAdd(
|
|
148
|
+
new PropKey(type, method[prefixLen..], flags),
|
|
149
|
+
static k => k.Type.GetProperty(k.Name, k.Flags));
|
|
150
|
+
|
|
151
|
+
// ── argument coercion ─────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
// Pooled: rented array passed to the compiled delegate (which accesses by index,
|
|
154
|
+
// not by Length). Caller must return via ReturnArgs immediately after invoke.
|
|
155
|
+
private static object?[] BuildArgs(JsonElement[] elements, ParameterInfo[] parameters)
|
|
156
|
+
{
|
|
157
|
+
if (parameters.Length == 0) return [];
|
|
158
|
+
var args = ArrayPool<object?>.Shared.Rent(parameters.Length);
|
|
159
|
+
for (int i = 0; i < parameters.Length && i < elements.Length; i++)
|
|
160
|
+
args[i] = Coerce(elements[i], parameters[i].ParameterType);
|
|
161
|
+
return args;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Exact: required by ConstructorInfo.Invoke / MethodInfo.Invoke which validate
|
|
165
|
+
// args.Length == paramCount and would throw on an oversized rented buffer.
|
|
166
|
+
private static object?[] BuildArgsExact(JsonElement[] elements, ParameterInfo[] parameters)
|
|
167
|
+
{
|
|
168
|
+
if (parameters.Length == 0) return [];
|
|
169
|
+
var args = new object?[parameters.Length];
|
|
170
|
+
for (int i = 0; i < parameters.Length && i < elements.Length; i++)
|
|
171
|
+
args[i] = Coerce(elements[i], parameters[i].ParameterType);
|
|
172
|
+
return args;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private static void ReturnArgs(object?[] args) =>
|
|
176
|
+
ArrayPool<object?>.Shared.Return(args, clearArray: true);
|
|
177
|
+
|
|
178
|
+
private static object? Coerce(JsonElement el, Type targetType)
|
|
179
|
+
{
|
|
180
|
+
if (el.ValueKind == JsonValueKind.Null) return null;
|
|
181
|
+
if (el.ValueKind == JsonValueKind.Object && el.TryGetProperty("__handle", out var h))
|
|
182
|
+
{
|
|
183
|
+
s_handles.TryGetValue(h.GetInt32(), out var obj);
|
|
184
|
+
return obj;
|
|
185
|
+
}
|
|
186
|
+
return el.Deserialize(targetType, s_coerceOpts);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── async unwrapping ──────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
private static object? AwaitIfTask(object? value)
|
|
192
|
+
{
|
|
193
|
+
if (value is null) return null;
|
|
194
|
+
|
|
195
|
+
if (value is Task task)
|
|
196
|
+
{
|
|
197
|
+
task.GetAwaiter().GetResult();
|
|
198
|
+
return TaskResultCache.GetResult(task);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (value is ValueTask vt)
|
|
202
|
+
{
|
|
203
|
+
vt.AsTask().GetAwaiter().GetResult();
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
var type = value.GetType();
|
|
208
|
+
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ValueTask<>))
|
|
209
|
+
{
|
|
210
|
+
var innerTask = ValueTaskAsTaskCache.AsTask(type, value);
|
|
211
|
+
innerTask.GetAwaiter().GetResult();
|
|
212
|
+
if (innerTask.IsFaulted) throw innerTask.Exception!.InnerException ?? innerTask.Exception;
|
|
213
|
+
return TaskResultCache.GetResult(innerTask);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── result boxing ─────────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
private static DispatchResult Box(object? value)
|
|
222
|
+
{
|
|
223
|
+
if (value is null) return DispatchResult.Void;
|
|
224
|
+
var t = value.GetType();
|
|
225
|
+
|
|
226
|
+
if (t.IsPrimitive || t == typeof(string) || t == typeof(decimal)
|
|
227
|
+
|| t == typeof(DateTime) || t == typeof(DateTimeOffset)
|
|
228
|
+
|| t == typeof(TimeSpan) || t == typeof(Guid))
|
|
229
|
+
return DispatchResult.Primitive(value, t);
|
|
230
|
+
|
|
231
|
+
if (t.IsArray || (t != typeof(string) && value is IEnumerable))
|
|
232
|
+
{
|
|
233
|
+
try { return DispatchResult.Collection((IEnumerable)value); }
|
|
234
|
+
catch { }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
var id = Interlocked.Increment(ref s_nextHandle);
|
|
238
|
+
s_handles[id] = value;
|
|
239
|
+
return DispatchResult.Handle(id, t.FullName ?? t.Name);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ── type/member resolution ────────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
internal static DispatchResult BuildMembersResult(Type t)
|
|
245
|
+
{
|
|
246
|
+
const BindingFlags inst = BindingFlags.Public | BindingFlags.Instance;
|
|
247
|
+
const BindingFlags stat = BindingFlags.Public | BindingFlags.Static;
|
|
248
|
+
return DispatchResult.Members(
|
|
249
|
+
t.GetMethods(inst).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
|
|
250
|
+
t.GetProperties(inst).Select(p => p.Name).Distinct().ToArray(),
|
|
251
|
+
t.GetMethods(stat).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
|
|
252
|
+
t.GetProperties(stat).Select(p => p.Name).Distinct().ToArray());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
internal static Type? ResolveType(string? assemblyName, string? typeName)
|
|
256
|
+
{
|
|
257
|
+
if (string.IsNullOrEmpty(typeName)) return null;
|
|
258
|
+
var key = string.IsNullOrEmpty(assemblyName) ? typeName : $"{assemblyName}|{typeName}";
|
|
259
|
+
return s_typeCache.GetOrAdd(key, _ => ResolveTypeCore(assemblyName, typeName));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private static Type? ResolveTypeCore(string? assemblyName, string? typeName)
|
|
263
|
+
{
|
|
264
|
+
var fqn = string.IsNullOrEmpty(assemblyName) ? typeName! : $"{typeName}, {assemblyName}";
|
|
265
|
+
var t = Type.GetType(fqn);
|
|
266
|
+
if (t is not null) return t;
|
|
267
|
+
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
268
|
+
{
|
|
269
|
+
t = asm.GetType(typeName!);
|
|
270
|
+
if (t is not null) return t;
|
|
271
|
+
}
|
|
272
|
+
if (!string.IsNullOrEmpty(assemblyName))
|
|
273
|
+
{
|
|
274
|
+
try { t = Assembly.Load(assemblyName).GetType(typeName!); } catch { }
|
|
275
|
+
}
|
|
276
|
+
return t;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private static MethodInfo? FindMethodCore(Type type, string name, int argCount, BindingFlags flags)
|
|
280
|
+
{
|
|
281
|
+
var match = Array.Find(type.GetMethods(flags),
|
|
282
|
+
m => m.Name == name && !m.IsGenericMethod && m.GetParameters().Length == argCount);
|
|
283
|
+
return match ?? type.GetMethod(name, flags);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
internal static Exception Unwrap(Exception ex) =>
|
|
287
|
+
ex is TargetInvocationException { InnerException: { } inner } ? Unwrap(inner) : ex;
|
|
288
|
+
|
|
289
|
+
// ── full-pipeline helpers (parse + dispatch + serialise) ──────────────────
|
|
290
|
+
// Used by benchmarks to give a fair end-to-end comparison of protocols.
|
|
291
|
+
|
|
292
|
+
internal static byte[] PipelineJson(ReadOnlySpan<byte> jsonRequest)
|
|
293
|
+
{
|
|
294
|
+
var req = JsonSerializer.Deserialize(jsonRequest, BridgeJsonContext.Default.InvokeRequest)!;
|
|
295
|
+
var result = Dispatch(req);
|
|
296
|
+
var buf = new ArrayBufferWriter<byte>(128);
|
|
297
|
+
using var w = new Utf8JsonWriter(buf);
|
|
298
|
+
w.WriteStartObject();
|
|
299
|
+
w.WritePropertyName("result"u8);
|
|
300
|
+
result.WriteTo(w, s_coerceOpts);
|
|
301
|
+
w.WriteEndObject();
|
|
302
|
+
w.Flush();
|
|
303
|
+
return buf.WrittenSpan.ToArray();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
internal static byte[] PipelineBinary(ReadOnlySpan<byte> binRequest)
|
|
307
|
+
{
|
|
308
|
+
var r = new BinReader(binRequest);
|
|
309
|
+
var result = DispatchBin(ref r);
|
|
310
|
+
var buf = new ArrayBufferWriter<byte>(128);
|
|
311
|
+
result.WriteAsBin(buf);
|
|
312
|
+
return buf.WrittenSpan.ToArray();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ── JSON response writers ─────────────────────────────────────────────────
|
|
316
|
+
|
|
317
|
+
private static unsafe void WriteResult(DispatchResult res, byte** outPtr, int* outLen)
|
|
318
|
+
{
|
|
319
|
+
var buf = new ArrayBufferWriter<byte>(256);
|
|
320
|
+
using var w = new Utf8JsonWriter(buf);
|
|
321
|
+
w.WriteStartObject();
|
|
322
|
+
w.WritePropertyName("result"u8);
|
|
323
|
+
res.WriteTo(w, s_coerceOpts);
|
|
324
|
+
w.WriteEndObject();
|
|
325
|
+
w.Flush();
|
|
326
|
+
WriteUnmanaged(buf.WrittenSpan, outPtr, outLen);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private static unsafe void WriteError(string msg, byte** outPtr, int* outLen)
|
|
330
|
+
{
|
|
331
|
+
var buf = new ArrayBufferWriter<byte>(128);
|
|
332
|
+
using var w = new Utf8JsonWriter(buf);
|
|
333
|
+
w.WriteStartObject();
|
|
334
|
+
w.WriteString("error"u8, msg);
|
|
335
|
+
w.WriteEndObject();
|
|
336
|
+
w.Flush();
|
|
337
|
+
WriteUnmanaged(buf.WrittenSpan, outPtr, outLen);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
internal static unsafe void WriteUnmanaged(ReadOnlySpan<byte> bytes, byte** outPtr, int* outLen)
|
|
341
|
+
{
|
|
342
|
+
var p = (byte*)Marshal.AllocHGlobal(bytes.Length + 1);
|
|
343
|
+
bytes.CopyTo(new Span<byte>(p, bytes.Length));
|
|
344
|
+
p[bytes.Length] = 0;
|
|
345
|
+
*outPtr = p;
|
|
346
|
+
*outLen = bytes.Length;
|
|
347
|
+
}
|
|
348
|
+
}
|