Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: dotnet/java-interop
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 8b85462e5f304d9aa2d91c02966d7dc9751516c7
Choose a base ref
...
head repository: dotnet/java-interop
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 07c73009571d3b5d36ae79d0d4d69a02062dd6e8
Choose a head ref
  • 7 commits
  • 60 files changed
  • 4 contributors

Commits on Dec 1, 2023

  1. [Java.Interop] Use PublicApiAnalyzers to ensure we do not break API (#…

    …1170)
    
    Fixes: #1169
    
    Context: dotnet/android@76ab8b2
    
    dotnet/android@76ab8b2c mentions:
    
    > Now that we are in the .NET `TargetFramework` world we also need
    > to ensure we do not *add* any new API to a Target Framework once it
    > has shipped.  A `TargetFramework` is essentially a contract that we
    > cannot change.  (Imagine if you had different minor versions of .NET
    > on your local machine and CI machine, what works on one should work
    > on the other.)
    
    This prevents an issue where a user on `.NET 8.0.300` uses a method
    that isn't available to their coworker or CI on `.NET 8.0.100`.
    
    This logical argument also applies to `Java.Interop.dll`.
    
    Enable Microsoft's [PublicApiAnalyzers][0] for `Java.Interop.dll`.
    ([PublicApiAnalyzers documentation][1].)  This ensures that we don't
    add new API once we've shipped `Java.Interop.dll` for a given
    .NET version.
    
    Update `build-tools/jnienv-gen` so that `JniEnvironment.g.cs` enables
    nullable reference types.  This allows us to *avoid* disabling RS0041.
    
    Co-authored-by: Jonathan Pryor <jonpryor@vt.edu>
    
    [0]: https://github.com/dotnet/roslyn-analyzers/tree/ace28a1039d09626a94d3b0f8ce69e547ca2bcbf/src/PublicApiAnalyzers
    [1]: https://github.com/dotnet/roslyn-analyzers/blob/ace28a1039d09626a94d3b0f8ce69e547ca2bcbf/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md
    jpobst authored Dec 1, 2023
    Configuration menu
    Copy the full SHA
    0f1efeb View commit details
    Browse the repository at this point in the history

Commits on Dec 2, 2023

  1. [Java.Interop] Avoid Type.GetType() in ManagedPeer (#1168)

    Fixes: #1165
    
    Context: #1153
    Context: #1157
    Context: f60906c
    
    When building for NativeAOT (#1153) or when building .NET Android
    apps with `-p:IsAotcompatible=true` (#1157), we get [IL2057][0]
    warnings from `ManagedPeer.cs`:
    
    	ManagedPeer.cs(93,19,93,112): warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String, Boolean)'. It's not possible to guarantee the availability of the target type.
    	ManagedPeer.cs(156,18,156,65): warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String, Boolean)'. It's not possible to guarantee the availability of the target type.
    	ManagedPeer.cs(198,35,198,92): warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String, Boolean)'. It's not possible to guarantee the availability of the target type.
    
    These warnings are because `ManagedPeer.Construct()` and
    `ManagedPeer.RegisterNativeMembers()` use `Type.GetType()` on string
    values provided *from Java code*, and thus the IL trimmer does not
    have visibility into those strings, and thus cannot reliably
    determine which types need to be preserved:
    
    	// Java Callable Wrapper
    	/* partial */ class ManagedType
    	{
    	  public static final String __md_methods;
    	  static {
    	    __md_methods =
    	      "n_GetString:()Ljava/lang/String;:__export__\n" +
    	      "";
    	    net.dot.jni.ManagedPeer.registerNativeMembers (
    	        /* nativeClass */             ManagedType.class,
    	        /* assemblyQualifiedName */   "Example.ManagedType, Hello-NativeAOTFromJNI",
    	        /* methods */                 __md_methods);
    	  }
    
    	  public ManagedType (int p0)
    	  {
    	    super ();
    	    if (getClass () == ManagedType.class) {
    	      net.dot.jni.ManagedPeer.construct (
    	          /* self */                  this,
    	          /* assemblyQualifiedName */ "Example.ManagedType, Hello-NativeAOTFromJNI",
    	          /* constructorSignature */  "System.Int32, System.Runtime",
    	          /* arguments */             new java.lang.Object[] { p0 });
    	    }
    	  }
    	}
    
    `ManagedPeer.construct()` passes *two* sets of assembly-qualified
    type names: `assemblyQualifiedName` contains the type to construct,
    while `constructorSignature` contains a `:`-separated list of
    assembly-qualified type names for the constructor parameters.
    Each of these are passed to `Type.GetType()`.
    
    `ManagedPeer.registerNativeMembers()` passes an assembly-qualified
    type name to `ManagedPeer.RegisterNativeMembers()`, which passes the
    assembly-qualified type name to `Type.GetType()` to find the type
    to register native methods for.
    
    If we more strongly rely on JNI signatures, we can remove the need
    for Java Callable Wrappers to contain assembly-qualified type names
    entirely, thus removing the need for `ManagedPeer` to use
    `Type.GetType()`, removing the IL2057 warnings.
    
    For `ManagedPeer.construct()`, `assemblyQualifiedName` can be
    replaced with getting the JNI type signature from `self.getClass()`,
    and `constructorSignature` can be replaced with a
    *JNI method signature* of the calling constructor.
    
    For `ManagedPeer.registerNativeMembers()`, `assemblyQualifiedName`
    can be replaced with getting the JNI type signature from `nativeClass`.
    `jcw-gen --codegen-target=JavaInterop1` output becomes:
    
    	// New JavaInterop1 Java Callable Wrapper
    	/* partial */ class ManagedType
    	{
    	  public static final String __md_methods;
    	  static {
    	    __md_methods =
    	      "n_GetString:()Ljava/lang/String;:__export__\n" +
    	      "";
    	    net.dot.jni.ManagedPeer.registerNativeMembers (
    	        /* nativeClass */             ManagedType.class,
    	        /* methods */                 __md_methods);
    	  }
    
    	  public ManagedType (int p0)
    	  {
    	    super ();
    	    if (getClass () == ManagedType.class) {
    	      net.dot.jni.ManagedPeer.construct (
    	          /* self */                  this,
    	          /* constructorSignature */  "(I)V",
    	          /* arguments */             new java.lang.Object[] { p0 });
    	    }
    	  }
    	}
    
    This does not alter `jcw-gen --codegen-target=XAJavaInterop1` output;
    .NET Android will continue to require `Type.GetType()` calls within
    xamarin/xamarin-android, e.g.
    [`AndroidTypeManager.RegisterNativeMembers()`][2].
    
    Furthermore, if we add `[DynamicallyAccessedMembers]` to
    `JniRuntime.JniTypeManager.GetType()`, we can fix some [IL2075][1]
    warnings which appeared after fixing the IL2057 warnings.
    
    Aside: Excising assembly-qualified type names from Java Callable
    Wrappers had some "interesting" knock-on effects in the unit tests,
    requiring that more typemap information be explicitly provided.
    (This same information was *implicitly* provided before, via the
    provision of assembly-qualified type names everywhere…)
    
    One problem with the approach of using JNI signatures instead of
    using assembly-qualified names is *ambiguity*: there can be multiple
    managed types which correspond to a given JNI signature.  Consider
    the JNI signature `[I`, which is a Java `int[]`.  This is bound as:
    
      * C# `int[]`
      * `JavaArray<int>`
      * `JavaPrimitiveArray<int>`
      * `JavaInt32Array`
    
    How do we know which to use?  Using assembly-qualified type names
    for constructor parameters nicely solved this issue, but if we're not
    using them anymore…
    
    Update `JavaCallableExample` to demonstrate this:
    
    	partial class JavaCallableExample {
    	    [JavaCallableConstructor(SuperConstructorExpression="")]
    	    public JavaCallableExample (int[] a, JavaInt32Array b);
    	}
    
    The intention is twofold:
    
     1. This should result in a Java Callable Wrapper constructor with
        signature `JavaCallableExample(int[] p0, int[] p1)`, and
    
     2. Java code should be able to invoke this constructor.
    
    Turns out, neither of these worked when `Type.GetType()` is not used
    for constructor argument lookup: `JavaCallableWrapperGenerator`
    didn't fully support e.g. `[JniTypeSignature("I", ArrayRank=1)]`
    (present on `JavaInt32Array`), so it didn't know what to do with
    the `JavaInt32Array` parameter.
    
    Once (1) was fixed, (2) would fail because
    `JniRuntime.JniTypeManager.GetType(JniTypeSignature.Parse("[I"))`
    would return `JavaPrimitiveArray<int>`, which wasn't used in
    `JavaCallableExample`, resulting in:
    
    	System.NotSupportedException : Unable to find constructor
    	  Java.InteropTests.JavaCallableExample(Java.Interop.JavaPrimitiveArray`1[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], Java.Interop.JavaPrimitiveArray`1[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]).
    	  Please provide the missing constructor.
    	  ----> Java.Interop.JniLocationException : Exception of type 'Java.Interop.JniLocationException' was thrown.
    	  Stack Trace:
    	     at Java.Interop.ManagedPeer.GetConstructor(JniTypeManager typeManager, Type type, String signature, Type[]& parameterTypes)
    	   at Java.Interop.ManagedPeer.Construct(IntPtr jnienv, IntPtr klass, IntPtr n_self, IntPtr n_constructorSignature, IntPtr n_constructorArguments)
    	…
    	  --- End of managed Java.Interop.JavaException stack trace ---
    	java.lang.Throwable
    		at net.dot.jni.ManagedPeer.construct(Native Method)
    		at net.dot.jni.test.JavaCallableExample.<init>(JavaCallableExample.java:32)
    		at net.dot.jni.test.UseJavaCallableExample.test(UseJavaCallableExample.java:8)
    
    The constructor couldn't be found because
    `JniRuntime.JniTypeManager.GetTypes()` was incomplete, which is
    a longstanding limitation from f60906c: for `[I`, it would only
    return `JavaPrimitiveArray<int>` and `int[]`, in that order.
    
    Fix both of these.
    `JniRuntime.JniTypeManager.GetTypes(JniTypeSignature.Parse("[I"))`
    will now include:
    
      * `JavaArray<int>`
      * `JavaPrimitiveArray<int>`
      * `JavaInt32Array`
      * `int[]`
    
    This now allows the `JavaCallableExample` constructor to be invoked
    from Java.
    
    Because `ManagedPeer.Construct()` is now doing so much extra work
    in order to find the `ConstructorInfo` to invoke, cache the lookups.
    (Technically this is a "memory leak," as cache entries are never
    removed.)
    
    Finally, update `CecilCompilerExpressionVisitor` to emit `newobj`
    in certain `VisitNew()` invocations.  This was needed while trying:
    
    	partial class JavaCallableExample {
    	    [JavaCallable ("getA")]
    	    public int[] GetA() => this.a;
    	}
    
    in order to fix the IL error:
    
    	% $HOME/.dotnet/tools/ilverify bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll \
    	    --tokens --system-module System.Private.CoreLib \
    	    -r 'bin/TestDebug-net7.0/*.dll' \
    	    -r '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.10/*.dll'
    	[IL]: Error [StackUnderflow]: […/bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll : .__<$>_jni_marshal_methods::n_GetA(native int, native int)][offset 0x0000002F] Stack underflow.
    
    Unfortunately, even after the above fix invalid IL was generated during
    `jnimarshalmethod-gen` processing, which will be investigated later.
    
    [0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-warnings/IL2057
    [1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-warnings/il2075
    [2]: https://github.com/xamarin/xamarin-android/blob/main/src/Mono.Android/Android.Runtime/AndroidRuntime.cs#L481-L577
    jonpryor authored Dec 2, 2023
    Configuration menu
    Copy the full SHA
    005c914 View commit details
    Browse the repository at this point in the history
  2. Bump to xamarin/xamarin-android-tools/main@4889bf0 (#1172)

    Changes: dotnet/android-tools@8d38281...4889bf0
    
      * dotnet/android-tools@4889bf0: [MSBuildReferences.projitems] Require opt-in to use `Microsoft.Build` (dotnet/android-tools#220)
      * dotnet/android-tools@21de3d7: [build] update $(MSBuildPackageReferenceVersion) to 17.6.3 (dotnet/android-tools#221)
      * dotnet/android-tools@08a6990: Bump android-sdk NDK version to 26.1.10909125
      * dotnet/android-tools@6ae1f2a: Bump android-sdk build-tool version to 34.0.0
      * dotnet/android-tools@184b6b3: Bump android-sdk cmdline-tools to version 11.0
      * dotnet/android-tools@1365e33: Bump android-sdk platforms-tools to version 34.0.5
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Dec 2, 2023
    Configuration menu
    Copy the full SHA
    473ef74 View commit details
    Browse the repository at this point in the history

Commits on Dec 11, 2023

  1. [invocation-overhead] Add generated source files (#1175)

    Building the `Java.Interop.sln` solution always produces some generated
    files that `git` wants to try to commit:
    
    	Changes not staged for commit:
    	  (use "git add <file>..." to update what will be committed)
    	  (use "git restore <file>..." to discard changes in working directory)
    	        modified:   tests/invocation-overhead/jni.cs
    
    	Untracked files:
    	  (use "git add <file>..." to include in what will be committed)
    	        tests/invocation-overhead/jni-api.h
    	        tests/invocation-overhead/jni.c
    
    Commit these generated files, and update the generator for `jni.cs` to
    always use native platform new lines so it does not show up as modified
    on Windows.
    jpobst authored Dec 11, 2023
    Configuration menu
    Copy the full SHA
    d5afa0a View commit details
    Browse the repository at this point in the history

Commits on Jan 10, 2024

  1. [ci] Add API Scan job (#1178)

    Context: https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/25351/APIScan-step-by-step-guide-to-setting-up-a-Pipeline
    
    The ApiScan task has been added to pipeline runs against `main`.  This
    task should help us identify related issues earlier, rather than having
    to wait for a full scan of VS.
    pjcollins authored Jan 10, 2024
    Configuration menu
    Copy the full SHA
    def5bc0 View commit details
    Browse the repository at this point in the history

Commits on Jan 26, 2024

  1. Bump to xamarin/xamarin-android-tools/main@ed102fc (#1182)

    Changes: dotnet/android-tools@4889bf0...ed102fc
    
      * dotnet/android-tools@ed102fc: [Xamarin.Android.Tools.Versions] Add JavaSdkVersion (#226)
      * dotnet/android-tools@b175674: [ci] Only enable CodeQL on Windows build job (#224)
      * dotnet/android-tools@2a2e64b: [ci] Add API Scan job (#225)
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Jan 26, 2024
    Configuration menu
    Copy the full SHA
    d529f3b View commit details
    Browse the repository at this point in the history

Commits on Feb 2, 2024

  1. [Java.Interop] Typemap support for JavaObject & [JniTypeSignature] (#…

    …1181)
    
    Context: dotnet/android#8543
    Context: dotnet/android#8625
    Context: dotnet/android#8681
    Context: #1168
    Context: def5bc0
    Context: 005c914
    
    dotnet/android#8543 tested PR #1168, was Totally Green™ --
    finding no issues -- and so we merged PR #1168 into 005c914.
    
    Enter dotnet/android#8625, which bumps xamarin-android to
    use def5bc0, which includes 005c914.  dotnet/android#8625
    contains *failing unit tests* (?!), including
    `Java.InteropTests.InvokeVirtualFromConstructorTests()`:
    
    	Java.Lang.LinkageError : net.dot.jni.test.CallVirtualFromConstructorDerived
    	----> System.NotSupportedException : Could not find System.Type corresponding to Java type JniTypeSignature(TypeName=net/dot/jni/test/CallVirtualFromConstructorDerived ArrayRank=0 Keyword=False) .
    	   at Java.Interop.JniEnvironment.StaticMethods.GetStaticMethodID(JniObjectReference , String , String )
    	   at Java.Interop.JniType.GetStaticMethod(String , String )
    	   at Java.Interop.JniPeerMembers.JniStaticMethods.GetMethodInfo(String , String )
    	   at Java.Interop.JniPeerMembers.JniStaticMethods.GetMethodInfo(String )
    	   at Java.Interop.JniPeerMembers.JniStaticMethods.InvokeObjectMethod(String , JniArgumentValue* )
    	   at Java.InteropTests.CallVirtualFromConstructorDerived.NewInstance(Int32 value)
    	   at Java.InteropTests.InvokeVirtualFromConstructorTests.ActivationConstructor()
    	   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
    	   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object , BindingFlags )
    	  --- End of managed Java.Lang.LinkageError stack trace ---
    	java.lang.NoClassDefFoundError: net.dot.jni.test.CallVirtualFromConstructorDerived
    		at crc643df67da7b13bb6b1.TestInstrumentation_1.n_onStart(Native Method)
    		at crc643df67da7b13bb6b1.TestInstrumentation_1.onStart(TestInstrumentation_1.java:35)
    		at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2189)
    	Caused by: android.runtime.JavaProxyThrowable: [System.NotSupportedException]: Could not find System.Type corresponding to Java type JniTypeSignature(TypeName=net/dot/jni/test/CallVirtualFromConstructorDerived ArrayRank=0 Keyword=False) .
    		at Java.Interop.ManagedPeer.GetTypeFromSignature(Unknown Source:0)
    		at Java.Interop.ManagedPeer.RegisterNativeMembers(Unknown Source:0)
    		at net.dot.jni.ManagedPeer.registerNativeMembers(Native Method)
    		at net.dot.jni.test.CallVirtualFromConstructorDerived.<clinit>(CallVirtualFromConstructorDerived.java:12)
    		... 3 more
    
    	--NotSupportedException
    	   at Java.Interop.ManagedPeer.GetTypeFromSignature(JniTypeManager , JniTypeSignature , String )
    	   at Java.Interop.ManagedPeer.RegisterNativeMembers(IntPtr jnienv, IntPtr klass, IntPtr n_nativeClass, IntPtr n_methods)
    
    :shocked-pikachu-face: (But dotnet/android#8543 was green!)
    
    The problem is twofold:
    
     1. 005c914 now requires the presence of typemap entries from e.g.
        `net.dot.jni.test.CallVirtualFromConstructorDerived` to
        `Java.InteropTests.CallVirtualFromConstructorDerived`.
    
     2. `Java.Interop.Tools.JavaCallableWrappers` et al doesn't create
        typemap entries for `Java.Interop.JavaObject` subclasses which
        have `[JniTypeSignature]`.
    
    Consequently, our units tests fail (and apparently weren't *run* on
    dotnet/android#8543?!  Still not sure what happened.)
    
    Update typemap generation by adding a new `TypeDefinition.HasJavaPeer()`
    extension method to replace all the `.IsSubclassOf("Java.Lang.Object")`
    and similar checks, extending it to also check for
    `Java.Interop.JavaObject` and `Java.Interop.JavaException` base types.
    (Continuing to use base type checks is done instead of just relying
    on implementation of `Java.Interop.IJavaPeerable` as a performance
    optimization, as there could be *lots* of interface types to check.)
    
    Additionally, @jonathanpeppers -- while trying to investigate all
    this -- ran across a build failure:
    
    	obj\Debug\net9.0-android\android\src\java\lang\Object.java(7,15): javac.exe error JAVAC0000:  error: cyclic inheritance involving Object
    
    This suggests that `Java.Interop.Tools.JavaCallableWrappers` was
    encountering `Java.Interop.JavaObject` -- or some other type which
    has `[JniTypeSignature("java/lang/Object")]` -- which is why
    `java/lang/Object.java` was being generated.
    
    Audit all `[JniTypeSignature]` attributes, and add
    `GenerateJavaPeer=false` to all types which should *not* hava a
    Java Callable Wrapper generated for them.  This includes nearly
    everything within `Java.Interop-Tests.dll`.  (We want the typemaps!
    We *don't* want generated Java source, as we have hand-written Java
    peer types for those tests.)
    
    Add `[JniTypeSignature]` to `GenericHolder<T>`.  This type mapping
    isn't *actually* required, but it *is* used in `JavaVMFixture`, and
    it confuses people (me!) if things are inconsistent.  Additionally,
    remove `tests/` from the Java-side name, for consistency.
    
    ~~ Avoid multiple java/lang/Object bindings ~~
    
    A funny thing happened when in dotnet/android#8681 -- which
    tested this commit -- when using an intermediate version of this
    commit: unit tests started crashing!
    
    	E monodroid-assembly: typemap: unable to load assembly 'Java.Interop-Tests' when looking up managed type corresponding to Java type 'java/lang/Object'
    
    What appears to be happening is an Unfortunate Interaction™:
    
     1. `Java.Interop-Tests.dll` contained *multiple bindings* for
        `java/lang/Object`. e.g.
    
            [JniTypeSignature ("java/lang/Object", GenerateJavaPeer=false)]
            partial class JavaDisposedObject : JavaObject {
            }
    
     2. The typemap generator has no functionality to "prioritize" one
        binding vs. another; it's random.  As such, there is nothing to
        cause `Java.Lang.Object, Mono.Android` to be used as the
        preferred binding for `java/lang/Object`.
    
    This meant that when we hit the typemap codepath in .NET Android,
    we looked for the C# type that corresponded to `java/lang/Object`,
    found *some random type* from `Java.Interop-Tests`, and…
    
    …and then we hit another oddity: that codepath only supported looking
    for C# types in assemblies which had already been loaded.  This was
    occurring during startup, so `Java.Interop-Tests` had not yet been
    loaded yet, so it errored out, returned `nullptr`, and later Android
    just aborts things:
    
    	F droid.NET_Test: runtime.cc:638] JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0x79
    
    Just…eep!
    
    This didn't happen before because `Java.Interop.JavaObject` subclasses
    *didn't* participate in typemap generation.  This commit *adds* that
    support, introducing this unforeseen interaction.
    
    Fix this by *removing* most "alternate bindings" for `java/lang/Object`:
    
    	- [JniTypeSignature ("java/lang/Object", GenerateJavaPeer=false)]
    	+ [JniTypeSignature (JniTypeName)]
    	  partial class JavaDisposedObject : JavaObject {
    	+     internal const string JniTypeName = "net/dot/jni/test/JavaDisposedObject";
    	  }
    
    This implicitly requires that we now have a Java Callable Wrapper
    for this type, so update `Java.Interop-Tests.csproj` to run `jcw-gen`
    as part of the build process.  This ensures that we create the
    JCW for e.g. `JavaDisposedObject`.
    
    Update `JavaVMFixture` to add the required typemap entries.
    
    ---
    
    Aside: this project includes [T4 Text Templates][0].  To regenerate
    the output files *without involving Visual Studio*, you can install
    the [`dotnet-t4`][1] tool:
    
    	$ dotnet tool install --global dotnet-t4
    
    then run it separately for each `.tt` file:
    
    	$HOME/.dotnet/tools/t4 -o src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs \
    	  src/Java.Interop/Java.Interop/JavaPrimitiveArrays.tt
    
    [0]: https://learn.microsoft.com/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2022
    [1]: https://www.nuget.org/packages/dotnet-t4/
    jonpryor authored Feb 2, 2024
    Configuration menu
    Copy the full SHA
    07c7300 View commit details
    Browse the repository at this point in the history
Loading