Skip to content

Add ikvm.runtime.Util.getDelegateFromMethodHandle/getDelegateFromMethod - #738

Merged
wasabii merged 3 commits into
mainfrom
feature/delegate-from-methodhandle
Aug 16, 2026
Merged

Add ikvm.runtime.Util.getDelegateFromMethodHandle/getDelegateFromMethod#738
wasabii merged 3 commits into
mainfrom
feature/delegate-from-methodhandle

Conversation

@wasabii

@wasabii wasabii commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Adds a supported way to obtain a .NET delegate for a Java method, so callers don't have to reach into IKVM.Runtime.ByteCodeHelper or the internal MH/MHV delegate family. Neither of those is API — ByteCodeHelper is public only because emitted IL calls it — but between them they are currently the only route.

Why not expose a MethodInfo

The obvious shape, MethodMethodInfo, has no total answer. RuntimeJavaMethod.GetMethod() is one-to-many for remapped types (mb / mbHelper / mbNonvirtualHelper, see RemappedJavaMethod), is null for artificial methods such as enum wrap(), and the correct choice depends on the call-site kind rather than the method. What it returns is also a compiler-internal entry point: a trailing synthetic CallerID parameter when HasCallerID, ghost-typed parameters as the ghost struct, and no null check on the receiver, so calling it directly yields NullReferenceException in place of NullPointerException.

A delegate does have a total answer, and MethodHandle already resolves every one of those cases.

API

public static native cli.System.Delegate getDelegateFromMethodHandle(cli.System.Type delegateType, MethodHandle methodHandle);
public static cli.System.Delegate getDelegateFromMethod(cli.System.Type delegateType, Executable executable) throws IllegalAccessException;

getDelegateFromMethodHandle validates the type is a delegate with no by-ref or pointer parameters, adapts the handle to that delegate's own signature with asType — which performs the boxing, primitive widening, ghost wrapping and receiver binding — then materialises the canonical invoke-exact delegate and binds its Invoke as the requested type. An unadaptable handle surfaces as WrongMethodTypeException.

Taking the handle rather than only the Executable means bindTo, insertArguments, unreflectSpecial (the super-call case GetMethod() structurally cannot express), unreflectConstructor and unreflectGetter/Setter all compose through the same entry point.

getDelegateFromMethod is a thin wrapper over unreflect/unreflectConstructor. Access is checked as it is there, so an inaccessible member needs setAccessible first; this does not escalate on the caller's behalf.

Also

Factors the non-generic core out of MethodHandleUtil.GetDelegateForInvokeExact<T>. The generic form previously threw unconditionally when the handle already had a cached delegate, because the as T test sat inside the null check. It now returns the cached delegate when the type matches. Net behaviour is unchanged, since the sole caller in ByteCodeHelper performs that same test before calling in.

Verification status — please do not merge on my say-so

Both managed builds compile clean (IKVM.Runtime and IKVM.Java, 0 errors). The 10 added tests have never executed. The local native build fails with pre-existing clang errors in stock OpenJDK C (libiava, libkrb5, libfontmanager, libawt_lwawt-Wincompatible-pointer-types under ikvm.clang.sdk 2.0.4), and a standalone harness needs the full IKVM home that same build produces. This branch touches four files, none under ext/ or src/lib*.

Left unproven, and what CI should be read for:

  • whether the native binds to the extern at all — a mismatch shows up only as UnsatisfiedLinkError at runtime, not at ikvmc time
  • the remapped-type case (java.lang.Object.hashCode), which is the case the design exists to handle
  • the GetDelegateMethodTypeCreateMethodHandleDelegateType round-trip producing a signature Delegate.CreateDelegate accepts. Reasoning says identity for reference and primitive types, mismatching only for ghost parameters — which would be unusual in a user-authored delegate and fails loudly — but that is argument, not evidence.

Draft until CI is green.

@wasabii
wasabii marked this pull request as ready for review August 11, 2026 12:25
Gives callers a supported way to obtain a .NET delegate for a Java method
without reaching into IKVM.Runtime.ByteCodeHelper or the internal MH/MHV
delegate family, neither of which is API.

getDelegateFromMethodHandle adapts the handle to the requested delegate's
signature with asType, which performs the boxing, primitive widening, ghost
wrapping and receiver binding that a raw MethodBase from
RuntimeJavaMethod.GetMethod() cannot express -- that method is one-to-many for
remapped types and null for artificial ones. It then materializes the canonical
invoke-exact delegate and binds its Invoke as the requested type.

getDelegateFromMethod is a thin wrapper over Lookup.unreflect and
unreflectConstructor. Access is checked as it is there, so callers must
setAccessible an otherwise inaccessible member.

Also factors the non-generic core out of MethodHandleUtil.GetDelegateForInvokeExact<T>.
The generic form previously threw unconditionally when the handle already had a
cached delegate, because the type test sat inside the null check; it now returns
the cached delegate when the type matches. Net behaviour is unchanged, since the
sole caller in ByteCodeHelper performs that same test before calling in.
@wasabii
wasabii force-pushed the feature/delegate-from-methodhandle branch from c254878 to e6d6aa2 Compare August 11, 2026 12:59
@wasabii
wasabii merged commit 2a50cd7 into main Aug 16, 2026
193 of 195 checks passed
@wasabii
wasabii deleted the feature/delegate-from-methodhandle branch August 16, 2026 18:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant