Skip to content

Commit 529cf7d

Browse files
[TrimmableTypeMap] Emit direct RegisterNatives with UTF-8 RVA data (#10988)
* Emit direct RegisterNatives with UTF-8 RVA data Replace per-method TrimmableTypeMap.RegisterMethod() calls with a single JNI RegisterNatives call using stackalloc'd JniNativeMethod structs and compile-time UTF-8 byte data stored in RVA static fields. Changes: - PEAssemblyBuilder: Add AddRvaField() for static fields backed by raw byte data in the PE mapped field data section. Pass mappedFieldData to ManagedPEBuilder. - TypeMapAssemblyEmitter: Add type/member refs for JniNativeMethod, JniEnvironment.Types.RegisterNatives, JniType.PeerReference, and ReadOnlySpan<JniNativeMethod>. Rewrite EmitRegisterNatives to pack all method names/signatures into one UTF-8 RVA blob per proxy type, stackalloc JniNativeMethod[N], and call RegisterNatives once. - TrimmableTypeMap: Remove RegisterMethod (no longer called by generated code). Result: zero delegate allocations, zero string allocations, zero array allocations, one JNI call per class during native registration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix IL emission to match Roslyn patterns for RegisterNatives - Use newobj + stobj (instead of call .ctor) for constructing JniNativeMethod on stackalloc'd memory. Roslyn always constructs value types on the eval stack then stores to the destination address. - Use ldarg + callvirt (instead of ldarga + call) for calling JniType.get_PeerReference(). JniType is a sealed reference type, so the this-pointer must be the object reference, not a pointer to the argument slot. - Fix doc comment on GetOrAddUtf8Field to accurately describe that fields live on nested sized types, not directly on <PrivateImplementationDetails>. These patterns were verified by compiling equivalent C# code with Roslyn and disassembling the output with ikdasm. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review: pre-allocate list, extract shared blob encoding - Pre-allocate validRegs list with registrations.Count capacity. - Extract EncodeGenericValueTypeInst() to share the ECMA-335 generic instantiation blob encoding between MakeGenericTypeSpec and EncodeReadOnlySpanOfJniNativeMethod. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use EncodeGenericValueTypeInst in encodeLocals callback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Restore using System.Runtime.InteropServices for TypeMapping The using directive was removed along with the RegisterMethod helper, but TypeMapping.GetOrCreateExternalTypeMapping still needs it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent aa0f6d9 commit 529cf7d

4 files changed

Lines changed: 372 additions & 40 deletions

File tree

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,20 @@ sealed class PEAssemblyBuilder
2828
readonly BlobBuilder _codeBlob = new BlobBuilder (256);
2929
readonly BlobBuilder _attrBlob = new BlobBuilder (64);
3030

31+
// Holds raw byte data for fields with FieldAttributes.HasFieldRVA (e.g., UTF-8 string literals).
32+
// Passed to ManagedPEBuilder as the mappedFieldData section.
33+
readonly BlobBuilder _mappedFieldData = new BlobBuilder ();
34+
35+
// Cache of sized value types for RVA fields, keyed by byte length.
36+
// Avoids creating duplicate __utf8_N types when multiple fields share the same size.
37+
readonly Dictionary<int, TypeDefinitionHandle> _sizedTypeCache = new ();
38+
39+
// Deduplication cache for UTF-8 string RVA fields. Strings like "()V" that repeat across
40+
// many proxy types are stored once and shared via the same FieldDefinitionHandle.
41+
readonly Dictionary<string, FieldDefinitionHandle> _utf8FieldCache = new (StringComparer.Ordinal);
42+
TypeDefinitionHandle _privateImplDetailsType;
43+
int _utf8FieldCounter;
44+
3145
readonly Version _systemRuntimeVersion;
3246

3347
public MetadataBuilder Metadata { get; } = new MetadataBuilder ();
@@ -102,7 +116,8 @@ public void WritePE (Stream stream)
102116
var peBuilder = new ManagedPEBuilder (
103117
new PEHeaderBuilder (imageCharacteristics: Characteristics.Dll),
104118
new MetadataRootBuilder (Metadata),
105-
ILBuilder);
119+
ILBuilder,
120+
mappedFieldData: _mappedFieldData.Count > 0 ? _mappedFieldData : null);
106121
var peBlob = new BlobBuilder ();
107122
peBuilder.Serialize (peBlob);
108123
peBlob.WriteContentTo (stream);
@@ -167,6 +182,92 @@ TypeReferenceHandle MakeTypeRefForManagedName (EntityHandle scope, string manage
167182
return Metadata.AddTypeReference (scope, Metadata.GetOrAddString (ns), Metadata.GetOrAddString (name));
168183
}
169184

185+
/// <summary>
186+
/// Returns a deduplicated RVA field containing the null-terminated UTF-8 encoding of
187+
/// <paramref name="value"/>. Strings like <c>"()V"</c> that appear across many proxy
188+
/// types are stored once and share the same <see cref="FieldDefinitionHandle"/>.
189+
/// The field is declared on an internal sized helper type (e.g. <c>__utf8_10</c>)
190+
/// nested under <c>&lt;PrivateImplementationDetails&gt;</c>.
191+
/// </summary>
192+
public FieldDefinitionHandle GetOrAddUtf8Field (string value)
193+
{
194+
if (_utf8FieldCache.TryGetValue (value, out var existing)) {
195+
return existing;
196+
}
197+
198+
EnsurePrivateImplDetailsType ();
199+
200+
// Encode to null-terminated UTF-8 (all JNI names/signatures are ASCII).
201+
int byteCount = System.Text.Encoding.UTF8.GetByteCount (value);
202+
var bytes = new byte [byteCount + 1];
203+
System.Text.Encoding.UTF8.GetBytes (value, 0, value.Length, bytes, 0);
204+
// bytes[byteCount] is already 0 (null terminator)
205+
206+
var sizedType = GetOrCreateSizedType (bytes.Length);
207+
208+
_sigBlob.Clear ();
209+
new BlobEncoder (_sigBlob).FieldSignature ().Type (sizedType, true);
210+
211+
int rva = _mappedFieldData.Count;
212+
_mappedFieldData.WriteBytes (bytes);
213+
214+
var fieldHandle = Metadata.AddFieldDefinition (
215+
FieldAttributes.Static | FieldAttributes.Assembly | FieldAttributes.HasFieldRVA | FieldAttributes.InitOnly,
216+
Metadata.GetOrAddString ($"__utf8_{_utf8FieldCounter++}"),
217+
Metadata.GetOrAddBlob (_sigBlob));
218+
219+
Metadata.AddFieldRelativeVirtualAddress (fieldHandle, rva);
220+
221+
_utf8FieldCache [value] = fieldHandle;
222+
return fieldHandle;
223+
}
224+
225+
void EnsurePrivateImplDetailsType ()
226+
{
227+
if (!_privateImplDetailsType.IsNil) {
228+
return;
229+
}
230+
231+
int typeFieldStart = Metadata.GetRowCount (TableIndex.Field) + 1;
232+
int typeMethodStart = Metadata.GetRowCount (TableIndex.MethodDef) + 1;
233+
234+
_privateImplDetailsType = Metadata.AddTypeDefinition (
235+
TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.Abstract | TypeAttributes.BeforeFieldInit,
236+
default,
237+
Metadata.GetOrAddString ("<PrivateImplementationDetails>"),
238+
Metadata.AddTypeReference (SystemRuntimeRef,
239+
Metadata.GetOrAddString ("System"), Metadata.GetOrAddString ("Object")),
240+
MetadataTokens.FieldDefinitionHandle (typeFieldStart),
241+
MetadataTokens.MethodDefinitionHandle (typeMethodStart));
242+
}
243+
244+
TypeDefinitionHandle GetOrCreateSizedType (int size)
245+
{
246+
if (_sizedTypeCache.TryGetValue (size, out var existing)) {
247+
return existing;
248+
}
249+
250+
EnsurePrivateImplDetailsType ();
251+
252+
int typeFieldStart = Metadata.GetRowCount (TableIndex.Field) + 1;
253+
int typeMethodStart = Metadata.GetRowCount (TableIndex.MethodDef) + 1;
254+
255+
var handle = Metadata.AddTypeDefinition (
256+
TypeAttributes.NestedPrivate | TypeAttributes.ExplicitLayout | TypeAttributes.Sealed | TypeAttributes.AnsiClass,
257+
default,
258+
Metadata.GetOrAddString ($"__utf8_{size}"),
259+
Metadata.AddTypeReference (SystemRuntimeRef,
260+
Metadata.GetOrAddString ("System"), Metadata.GetOrAddString ("ValueType")),
261+
MetadataTokens.FieldDefinitionHandle (typeFieldStart),
262+
MetadataTokens.MethodDefinitionHandle (typeMethodStart));
263+
264+
Metadata.AddTypeLayout (handle, packingSize: 1, size: (uint) size);
265+
Metadata.AddNestedType (handle, _privateImplDetailsType);
266+
267+
_sizedTypeCache [size] = handle;
268+
return handle;
269+
}
270+
170271
/// <summary>
171272
/// Emits a method body and definition in one call.
172273
/// </summary>

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs

Lines changed: 190 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
4848
/// // Registers JNI native methods (ACWs only):
4949
/// public void RegisterNatives(JniType jniType)
5050
/// {
51-
/// TrimmableNativeRegistration.RegisterMethod(jniType, "n_OnCreate", "(Landroid/os/Bundle;)V", &amp;n_OnCreate_uco_0);
52-
/// TrimmableNativeRegistration.RegisterMethod(jniType, "nctor_0", "()V", &amp;nctor_0_uco);
51+
/// JniNativeMethod* methods = stackalloc JniNativeMethod[2];
52+
/// methods[0] = new JniNativeMethod(&amp;__utf8_0, &amp;__utf8_1, &amp;n_OnCreate_uco_0);
53+
/// methods[1] = new JniNativeMethod(&amp;__utf8_2, &amp;__utf8_3, &amp;nctor_0_uco);
54+
/// JniEnvironment.Types.RegisterNatives(jniType.PeerReference, new ReadOnlySpan&lt;JniNativeMethod&gt;(methods, 2));
5355
/// }
5456
/// }
5557
///
@@ -86,13 +88,23 @@ sealed class TypeMapAssemblyEmitter
8688
MemberReferenceHandle _jniObjectReferenceCtorRef;
8789
MemberReferenceHandle _jniEnvDeleteRefRef;
8890
MemberReferenceHandle _activateInstanceRef;
89-
MemberReferenceHandle _registerMethodRef;
9091
MemberReferenceHandle _ucoAttrCtorRef;
9192
BlobHandle _ucoAttrBlobHandle;
9293
MemberReferenceHandle _typeMapAttrCtorRef2Arg;
9394
MemberReferenceHandle _typeMapAttrCtorRef3Arg;
9495
MemberReferenceHandle _typeMapAssociationAttrCtorRef;
9596

97+
// RegisterNatives with JniNativeMethod
98+
TypeReferenceHandle _jniNativeMethodRef;
99+
TypeReferenceHandle _jniEnvironmentRef;
100+
TypeReferenceHandle _jniEnvironmentTypesRef;
101+
TypeReferenceHandle _readOnlySpanOpenRef;
102+
TypeSpecificationHandle _readOnlySpanOfJniNativeMethodSpec;
103+
MemberReferenceHandle _jniNativeMethodCtorRef;
104+
MemberReferenceHandle _jniTypePeerReferenceRef;
105+
MemberReferenceHandle _jniEnvTypesRegisterNativesRef;
106+
MemberReferenceHandle _readOnlySpanOfJniNativeMethodCtorRef;
107+
96108
/// <summary>
97109
/// Creates a new emitter.
98110
/// </summary>
@@ -194,6 +206,18 @@ void EmitTypeReferences ()
194206
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("NotSupportedException"));
195207
_runtimeHelpersRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
196208
metadata.GetOrAddString ("System.Runtime.CompilerServices"), metadata.GetOrAddString ("RuntimeHelpers"));
209+
210+
_jniNativeMethodRef = metadata.AddTypeReference (_javaInteropRef,
211+
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniNativeMethod"));
212+
_jniEnvironmentRef = metadata.AddTypeReference (_javaInteropRef,
213+
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniEnvironment"));
214+
_jniEnvironmentTypesRef = metadata.AddTypeReference (_jniEnvironmentRef,
215+
default, metadata.GetOrAddString ("Types"));
216+
217+
// ReadOnlySpan<JniNativeMethod> — TypeSpec for generic instantiation
218+
_readOnlySpanOpenRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
219+
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("ReadOnlySpan`1"));
220+
_readOnlySpanOfJniNativeMethodSpec = MakeGenericTypeSpec_ValueType (_readOnlySpanOpenRef, _jniNativeMethodRef);
197221
}
198222

199223
void EmitMemberReferences ()
@@ -240,16 +264,41 @@ void EmitMemberReferences ()
240264
p.AddParameter ().Type ().Type (_systemTypeRef, false);
241265
}));
242266

243-
_registerMethodRef = _pe.AddMemberRef (_trimmableNativeRegistrationRef, "RegisterMethod",
244-
sig => sig.MethodSignature ().Parameters (4,
267+
// JniNativeMethod..ctor(byte*, byte*, IntPtr)
268+
_jniNativeMethodCtorRef = _pe.AddMemberRef (_jniNativeMethodRef, ".ctor",
269+
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (3,
245270
rt => rt.Void (),
246271
p => {
247-
p.AddParameter ().Type ().Type (_jniTypeRef, false);
248-
p.AddParameter ().Type ().String ();
249-
p.AddParameter ().Type ().String ();
272+
p.AddParameter ().Type ().Pointer ().Byte ();
273+
p.AddParameter ().Type ().Pointer ().Byte ();
250274
p.AddParameter ().Type ().IntPtr ();
251275
}));
252276

277+
// JniType.get_PeerReference() -> JniObjectReference
278+
_jniTypePeerReferenceRef = _pe.AddMemberRef (_jniTypeRef, "get_PeerReference",
279+
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (0,
280+
rt => rt.Type ().Type (_jniObjectReferenceRef, true),
281+
p => { }));
282+
283+
// JniEnvironment.Types.RegisterNatives(JniObjectReference, ReadOnlySpan<JniNativeMethod>)
284+
_jniEnvTypesRegisterNativesRef = _pe.AddMemberRef (_jniEnvironmentTypesRef, "RegisterNatives",
285+
sig => sig.MethodSignature ().Parameters (2,
286+
rt => rt.Void (),
287+
p => {
288+
p.AddParameter ().Type ().Type (_jniObjectReferenceRef, true);
289+
// ReadOnlySpan<JniNativeMethod> — must encode as GENERICINST manually
290+
EncodeReadOnlySpanOfJniNativeMethod (p.AddParameter ().Type ());
291+
}));
292+
293+
// ReadOnlySpan<JniNativeMethod>..ctor(void*, int)
294+
_readOnlySpanOfJniNativeMethodCtorRef = _pe.AddMemberRef (_readOnlySpanOfJniNativeMethodSpec, ".ctor",
295+
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2,
296+
rt => rt.Void (),
297+
p => {
298+
p.AddParameter ().Type ().VoidPointer ();
299+
p.AddParameter ().Type ().Int32 ();
300+
}));
301+
253302
var ucoAttrTypeRef = _pe.Metadata.AddTypeReference (_pe.SystemRuntimeInteropServicesRef,
254303
_pe.Metadata.GetOrAddString ("System.Runtime.InteropServices"),
255304
_pe.Metadata.GetOrAddString ("UnmanagedCallersOnlyAttribute"));
@@ -702,25 +751,116 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco)
702751
void EmitRegisterNatives (List<NativeRegistrationData> registrations,
703752
Dictionary<string, MethodDefinitionHandle> wrapperHandles)
704753
{
754+
// Filter to only registrations that have corresponding wrapper methods
755+
var validRegs = new List<(NativeRegistrationData Reg, MethodDefinitionHandle Wrapper)> (registrations.Count);
756+
foreach (var reg in registrations) {
757+
if (wrapperHandles.TryGetValue (reg.WrapperMethodName, out var wrapperHandle)) {
758+
validRegs.Add ((reg, wrapperHandle));
759+
}
760+
}
761+
762+
if (validRegs.Count == 0) {
763+
_pe.EmitBody ("RegisterNatives",
764+
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
765+
MethodAttributes.NewSlot | MethodAttributes.Final,
766+
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (1,
767+
rt => rt.Void (),
768+
p => p.AddParameter ().Type ().Type (_jniTypeRef, false)),
769+
encoder => encoder.OpCode (ILOpCode.Ret));
770+
return;
771+
}
772+
773+
// Get or create deduplicated RVA fields for each unique name/signature string.
774+
var nameFields = new FieldDefinitionHandle [validRegs.Count];
775+
var sigFields = new FieldDefinitionHandle [validRegs.Count];
776+
for (int i = 0; i < validRegs.Count; i++) {
777+
nameFields [i] = _pe.GetOrAddUtf8Field (validRegs [i].Reg.JniMethodName);
778+
sigFields [i] = _pe.GetOrAddUtf8Field (validRegs [i].Reg.JniSignature);
779+
}
780+
781+
int methodCount = validRegs.Count;
782+
705783
_pe.EmitBody ("RegisterNatives",
706784
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
707785
MethodAttributes.NewSlot | MethodAttributes.Final,
708786
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (1,
709787
rt => rt.Void (),
710788
p => p.AddParameter ().Type ().Type (_jniTypeRef, false)),
711789
encoder => {
712-
foreach (var reg in registrations) {
713-
if (!wrapperHandles.TryGetValue (reg.WrapperMethodName, out var wrapperHandle)) {
714-
continue;
790+
// stackalloc JniNativeMethod[N]
791+
encoder.LoadConstantI4 (methodCount);
792+
encoder.OpCode (ILOpCode.Sizeof);
793+
encoder.Token (_jniNativeMethodRef);
794+
encoder.OpCode (ILOpCode.Mul);
795+
encoder.OpCode (ILOpCode.Localloc);
796+
encoder.StoreLocal (0);
797+
798+
for (int i = 0; i < methodCount; i++) {
799+
// &methods[i] — destination address for stobj
800+
encoder.LoadLocal (0);
801+
if (i > 0) {
802+
encoder.LoadConstantI4 (i);
803+
encoder.OpCode (ILOpCode.Sizeof);
804+
encoder.Token (_jniNativeMethodRef);
805+
encoder.OpCode (ILOpCode.Mul);
806+
encoder.OpCode (ILOpCode.Add);
715807
}
716-
encoder.LoadArgument (1);
717-
encoder.LoadString (_pe.Metadata.GetOrAddUserString (reg.JniMethodName));
718-
encoder.LoadString (_pe.Metadata.GetOrAddUserString (reg.JniSignature));
808+
809+
// byte* name — ldsflda of deduplicated field
810+
encoder.OpCode (ILOpCode.Ldsflda);
811+
encoder.Token (nameFields [i]);
812+
813+
// byte* signature
814+
encoder.OpCode (ILOpCode.Ldsflda);
815+
encoder.Token (sigFields [i]);
816+
817+
// IntPtr functionPointer
719818
encoder.OpCode (ILOpCode.Ldftn);
720-
encoder.Token (wrapperHandle);
721-
encoder.Call (_registerMethodRef);
819+
encoder.Token (validRegs [i].Wrapper);
820+
821+
// Construct the struct on the evaluation stack and store it
822+
// at the destination address. This matches the Roslyn pattern:
823+
// newobj JniNativeMethod::.ctor(byte*, byte*, IntPtr)
824+
// stobj JniNativeMethod
825+
encoder.OpCode (ILOpCode.Newobj);
826+
encoder.Token (_jniNativeMethodCtorRef);
827+
encoder.OpCode (ILOpCode.Stobj);
828+
encoder.Token (_jniNativeMethodRef);
722829
}
830+
831+
// JniObjectReference peerRef = jniType.PeerReference
832+
// JniType is a sealed reference type, so use ldarg + callvirt
833+
encoder.LoadArgument (1);
834+
encoder.OpCode (ILOpCode.Callvirt);
835+
encoder.Token (_jniTypePeerReferenceRef);
836+
encoder.StoreLocal (1);
837+
838+
// new ReadOnlySpan<JniNativeMethod>(methods, count)
839+
encoder.LoadLocalAddress (2);
840+
encoder.LoadLocal (0);
841+
encoder.LoadConstantI4 (methodCount);
842+
encoder.Call (_readOnlySpanOfJniNativeMethodCtorRef);
843+
844+
// JniEnvironment.Types.RegisterNatives(peerRef, span)
845+
encoder.LoadLocal (1);
846+
encoder.LoadLocal (2);
847+
encoder.Call (_jniEnvTypesRegisterNativesRef);
848+
723849
encoder.OpCode (ILOpCode.Ret);
850+
},
851+
encodeLocals: localSig => {
852+
localSig.WriteByte (0x07); // IMAGE_CEE_CS_CALLCONV_LOCAL_SIG
853+
localSig.WriteCompressedInteger (3);
854+
855+
// local 0: native int (stackalloc pointer)
856+
localSig.WriteByte (0x18); // ELEMENT_TYPE_I
857+
858+
// local 1: JniObjectReference
859+
localSig.WriteByte (0x11); // ELEMENT_TYPE_VALUETYPE
860+
localSig.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniObjectReferenceRef));
861+
862+
// local 2: ReadOnlySpan<JniNativeMethod>
863+
EncodeGenericValueTypeInst (localSig, _readOnlySpanOpenRef, _jniNativeMethodRef);
724864
});
725865
}
726866

@@ -753,4 +893,38 @@ void EmitTypeMapAssociationAttribute (TypeMapAssociationData assoc)
753893
});
754894
_pe.Metadata.AddCustomAttribute (EntityHandle.AssemblyDefinition, _typeMapAssociationAttrCtorRef, blob);
755895
}
896+
897+
/// <summary>
898+
/// Writes the ECMA-335 blob for a closed generic value type with a single value-type argument.
899+
/// E.g., <c>ReadOnlySpan&lt;JniNativeMethod&gt;</c>.
900+
/// </summary>
901+
static void EncodeGenericValueTypeInst (BlobBuilder builder, EntityHandle openType, EntityHandle valueTypeArg)
902+
{
903+
builder.WriteByte (0x15); // ELEMENT_TYPE_GENERICINST
904+
builder.WriteByte (0x11); // ELEMENT_TYPE_VALUETYPE
905+
builder.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (openType));
906+
builder.WriteCompressedInteger (1); // generic arity = 1
907+
builder.WriteByte (0x11); // ELEMENT_TYPE_VALUETYPE
908+
builder.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (valueTypeArg));
909+
}
910+
911+
/// <summary>
912+
/// Builds a <c>TypeSpec</c> for a closed generic type with a single value-type argument.
913+
/// E.g., <c>ReadOnlySpan&lt;JniNativeMethod&gt;</c>.
914+
/// </summary>
915+
TypeSpecificationHandle MakeGenericTypeSpec_ValueType (EntityHandle openType, EntityHandle valueTypeArg)
916+
{
917+
var sigBlob = new BlobBuilder (32);
918+
EncodeGenericValueTypeInst (sigBlob, openType, valueTypeArg);
919+
return _pe.Metadata.AddTypeSpecification (_pe.Metadata.GetOrAddBlob (sigBlob));
920+
}
921+
922+
/// <summary>
923+
/// Encodes <c>ReadOnlySpan&lt;JniNativeMethod&gt;</c> directly into a signature type encoder.
924+
/// Required because <see cref="SignatureTypeEncoder.Type"/> doesn't accept TypeSpec handles.
925+
/// </summary>
926+
void EncodeReadOnlySpanOfJniNativeMethod (SignatureTypeEncoder encoder)
927+
{
928+
EncodeGenericValueTypeInst (encoder.Builder, _readOnlySpanOpenRef, _jniNativeMethodRef);
929+
}
756930
}

0 commit comments

Comments
 (0)