Description
C# Source Generators have been announced. They aren't a C# language feature; they're not LISP-style macros. Instead, a class implements the Microsoft.CodeAnalysis.ISourceGenerator
interface, and the ISourceGenerator.Execute(SourceGeneratorContext)
method is executed as part of the compiler "pipeline". SourceGeneratorContext
provides access the the C# source code parse trees that exist at the time of ISourceGenerator.Execute()
invocation, and can emit new C# source code to be included into the final executable.
(@jonpryor has no idea if ISourceGenerator.Execute(SourceGeneratorContext)
can be invoked multiple times at different times of the parse tree, so let's assume that it's only executed once.)
Is this useful for Java binding?
Replace generator
?
One can imagine a world in which generator.exe
becomes a C# source generator, and emits bindings as part of the C# compiler invocation process.
Other than being "cool", @jonpryor isn't sure if this actually buys us anything else useful. (It would still be cool!)
@jonpryor can't, offhand, see any ways to significantly alter the existing binding process; there would still be a Metadata file, an api.xml
, etc.
Replace jnimarshalmethod-gen
?
The C# source generator announcement suggests that C# source generators may be useful for Ahead Of Time (AOT) compilation purposes, which is a similar area as what jnimarshalmethod-gen.exe
targets. One can imagine all Xamarin.Android projects as having a "post-processing" source generator which looks at all Java.Lang.Object
and Java.Lang.Throwable
subclasses, then emits a __<$>_jni_marshal_methods
-style nested class which contains marshal methods for all method overrides, and a __<$>_jni_marshal_methods.__RegisterNativeMembers()
which performs the actual runtime registration. This would allow everything to happen at compile time.
Another upside is that, at present, jnimarshalmethod-gen.exe
cannot run under .NET 5, as it uses AppDomain
s; see Issue #616. This would circumvent that limitation.
The downside to using source generators in this manner is twofold:
- All "Java callable" types which override Java methods would need to be declared as
partial
classes. - It hardcodes the marshal method ABI, and
- Interaction with custom marshalers is unknown.
(2) is a somewhat "silly" complaint: the marshal method ABI is currently hardcoded, by nature of the interaction of the RegisterAttribute.Connector
property, which (eventually at runtime) invokes a corresponding n_*()
method -- also contained in the binding assembly -- which performs the Java-to-managed parameter & return type marshaling.
@jonpryor still wants to remove the need for RegisterAttribute.Connector
; see the Proposed Java.Interop Architecture section.
Then there's the idea of C# Function Pointers (see also 8e5310b).
In an architectural view in which jnimarshalmethod-gen.exe
runs at app build time, and has access to all of the assemblies that will be present in the final app, RegisterAttribute.Connector
becomes unnecessary, and jnimarshalmethod-gen.exe
could migrate to a C# Function Pointer-compatible IL implementation in a way that doesn't require rebuilding all existing binding assemblies. This is a cool feature!
Custom marshalers (3) (da5d1b8) are another "not quit working" idea that would be impacted by embracing source generators. The current architecture is that Java.Interop custom marshalers work by using [JniValueMarshalerAttribute]
and a JniValueMarshaler<T>
subclass: the JniValueMarshaler
is created at jnimarshalmethod-gen.exe
execution time to create System.Linq.Expressions.Expression
s to deal with marshaling Java parameters to managed parameters, and managed parameters to Java parameters.
(3.a): Is there a way to get C# source generators to "play nicely" with Expressions? Probably not, though this hasn't been explored. If there's no way to make this work, then it wouldn't be possible to support custom marshalers alongside source generators.
(3.b): if there is a way to get C# source generators to play nicely with expressions, we then get a "versioning" problem: the marshal method generated would be based on the custom marshaler available at compilation time, which may be a different assembly version than is bundled with the resulting app. This is kinda/sorta like the difference between C# const
and readonly
: const
values are "baked into" the call-site, while readonly
values are actually looked at runtime. Custom marshalers would be in a similar position: the emitted marshaling code would be "baked into" the referencing assembly at assembly compilation time, which is not app build time. If (when) the assembly containing custom marshalers changes, those changes won't be "percolated" ..
At present, C# source generators don't appear to be a good idea for fully replacing jnimarshalmethod-gen.exe
.