Bug 19740 - Add support for binding generic Java interfaces to generic C# interfaces?
Summary: Add support for binding generic Java interfaces to generic C# interfaces?
Status: ASSIGNED
Alias: None
Product: Android
Classification: Xamarin
Component: Bindings ()
Version: 4.12.4
Hardware: PC Mac OS
: Low enhancement
Target Milestone: master
Assignee: Atsushi Eno
URL:
Depends on:
Blocks:
 
Reported: 2014-05-13 16:57 UTC by Brendan Zagaeski (Xamarin Team, assistant)
Modified: 2016-09-21 12:05 UTC (History)
4 users (show)

Tags: XATriaged
Is this bug a regression?: ---
Last known good build:


Attachments
Working example of hand-edited generic bindings (63.02 KB, application/zip)
2014-05-13 16:57 UTC, Brendan Zagaeski (Xamarin Team, assistant)
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report for Bug 19740 on Developer Community or GitHub if you have new information to add and do not yet see a matching new report.

If the latest results still closely match this report, you can use the original description:

  • Export the original title and description: Developer Community HTML or GitHub Markdown
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments

Related Links:
Status:
ASSIGNED

Description Brendan Zagaeski (Xamarin Team, assistant) 2014-05-13 16:57:39 UTC
Created attachment 6797 [details]
Working example of hand-edited generic bindings

Perhaps generic Java interfaces and abstract classes could be bound automatically to corresponding generic C# types? Or if not, maybe an additional metadata attribute could be added to allow the user to explicitly request that the generator bind a specific Java type to a generic C# type?

The ACW generator would also need some small adjustments to handle this properly.


## Example use-case

Create a bindings project for a Java library that includes the following types:

> public interface TopInterface<T> {
> 	abstract public void onRun(T object);
> }

> public interface SecondInterface extends TopInterface<StringContainer> {
> }

> public class StringContainer extends java.lang.Object {
> 	public String contents;
> }



## Problem: the generated `ITopInterface` binding is not generic

Try to use the `ISecondInterface` binding from a Xamarin.Android app:

> public class ImplementsSecondInterface : Java.Lang.Object, ISecondInterface
> {
>     public void OnRun(StringContainer container)
>     {
>         container.Contents = "Hello, World!";
>     }
> }


## Result

### Error from `javac`

Error:  testapp.ImplementsSecondInterface is not abstract and does not override abstract method onRun(com.example.testandroidlib.StringContainer) in com.example.testandroidlib.TopInterface
public class ImplementsSecondInterface


### Reason for the error

This error happens because the ACW generator doesn't know that `OnRun()` needs to have an argument type of `StringContainer` if it's implementing `SecondInterface` (rather than `TopInterface`). The generator just uses the type-erased `Java.Lang.Object`.

Hand-editing _just_ the ACW won't work because the C# binding for `SecondInterface` doesn't know about `StringContainer` either. `IStringContainer` also needs to specify that it implements `ITopInterface<StringContainer>` instead of the non-generic `ITopInterface`.


## Possible improvement

Bind generic Java interfaces to generic C# interfaces by default. For example, you can get the small example to work if you:

- Hand-edit the bindings for `TopInterface`, `SecondInterface`, and any other types that use `TopInterface` to make `ITopInterface` generic.

- Hand-edit the generated Android Callable Wrapper for `ImplementsSecondInterface`. The automatic ACW generator doesn't yet handle the special case of generic C# bindings for Java interfaces.


### Working example

The attached sample demonstrates this for the `TopInterface` `SecondInterface` example. Admittedly, this suggested fix could easily overlook a detail that would cause failures for some particular invocations of `OnRun()`.


## Additional information: generic abstract classes

There's a similar problem with abstract methods in abstract generic classes. Those are a little easier to fix-up by hand because you can use a trick of creating a thin "wrapper" `TopAbstractClass<T>` that inherits from `TopAbstractClass`, and then changing `SecondAbstractClass` to inherit from the wrapper. The ACW generation then works correctly with no further adjustments.



Many thanks!
Comment 2 lewix 2016-04-29 08:07:51 UTC
Hi Brendan,
i wrote to you an email but i think useful for others posting this issue here too, if you agre.

I encountered the same situation with my android binding project.
Following your suggestions i was able to compile and build correctly, but when i use my custom class which implements abstract insterface, i get this runtime error:

04-29 09:35:17.510 W/monodroid(23147): JNIEnv.FindClass(Type) caught unexpected exception: Java.Lang.ClassNotFoundException: Didn't find class "testandroidbinding.PCLEventsImplementation" on path: DexPathList[[zip file "/data/app/net.aliaslab.testandroidbinding-1/base.apk"],nativeLibraryDirectories=[/data/app/net.aliaslab.testandroidbinding-1/lib/arm, /vendor/lib, /system/lib]]
04-29 09:35:17.510 W/monodroid(23147):   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Users/builder/data/lanes/3053/a94a03b5/source/mono/external/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:143 
04-29 09:35:17.510 W/monodroid(23147):   at Android.Runtime.JNIEnv.CallObjectMethod (IntPtr jobject, IntPtr jmethod, Android.Runtime.JValue* parms) [0x00064] in /Users/builder/data/lanes/3053/a94a03b5/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.g.cs:195 
04-29 09:35:17.510 W/monodroid(23147):   at Android.Runtime.JNIEnv.CallObjectMethod (IntPtr jobject, IntPtr jmethod, Android.Runtime.JValue[] parms) [0x0001d] in /Users/builder/data/lanes/3053/a94a03b5/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.g.cs:203 
04-29 09:35:17.510 W/monodroid(23147):   at Android.Runtime.JNIEnv.FindClass (System.String classname) [0x00007] in /Users/builder/data/lanes/3053/a94a03b5/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.cs:494 
04-29 09:35:17.510 W/monodroid(23147):   at Android.Runtime.JNIEnv.FindClass (System.Type type) [0x00009] in /Users/builder/data/lanes/3053/a94a03b5/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.cs:434 
04-29 09:35:17.510 W/monodroid(23147):   --- End of managed exception stack trace ---
04-29 09:35:17.510 W/monodroid(23147): java.lang.ClassNotFoundException: Didn't find class "testandroidbinding.PCLEventsImplementation" on path: DexPathList[[zip file "/data/app/net.aliaslab.testandroidbinding-1/base.apk"],nativeLibraryDirectories=[/data/app/net.aliaslab.testandroidbinding-1/lib/arm, /vendor/lib, /system/lib]]
04-29 09:35:17.510 W/monodroid(23147): at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
04-29 09:35:17.510 W/monodroid(23147): at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
04-29 09:35:17.510 W/monodroid(23147): at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
04-29 09:35:17.510 W/monodroid(23147): at md5c37b5090c7953962553837bb65efec6f.MainActivity.n_onCreate(Native Method)
04-29 09:35:17.510 W/monodroid(23147): at md5c37b5090c7953962553837bb65efec6f.MainActivity.onCreate(MainActivity.java:29)
04-29 09:35:17.510 W/monodroid(23147): at android.app.Activity.performCreate(Activity.java:6288)
04-29 09:35:17.510 W/monodroid(23147): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119)
04-29 09:35:17.510 W/monodroid(23147): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2642)
04-29 09:35:17.510 W/monodroid(23147): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2754)
04-29 09:35:17.510 W/monodroid(23147): at android.app.ActivityThread.access$900(ActivityThread.java:177)
04-29 09:35:17.510 W/monodroid(23147): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1448)
04-29 09:35:17.510 W/monodroid(23147): at android.os.Handler.dispatchMessage(Handler.java:102)
04-29 09:35:17.520 W/monodroid(23147): at android.os.Looper.loop(Looper.java:145)
04-29 09:35:17.520 W/monodroid(23147): at android.app.ActivityThread.main(ActivityThread.java:5938)
04-29 09:35:17.520 W/monodroid(23147): at java.lang.reflect.Method.invoke(Native Method)
04-29 09:35:17.520 W/monodroid(23147): at java.lang.reflect.Method.invoke(Method.java:372)
04-29 09:35:17.520 W/monodroid(23147): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1400)
04-29 09:35:17.520 W/monodroid(23147): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1195)
04-29 09:35:17.520 W/monodroid(23147): at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:115)
04-29 09:35:17.520 W/monodroid(23147): Suppressed: java.lang.ClassNotFoundException: testandroidbinding.PCLEventsImplementation
04-29 09:35:17.520 W/monodroid(23147):                 at java.lang.Class.classForName(Native Method)
04-29 09:35:17.520 W/monodroid(23147):                 at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
04-29 09:35:17.520 W/monodroid(23147):                 at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
04-29 09:35:17.520 W/monodroid(23147):                 at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
04-29 09:35:17.520 W/monodroid(23147):                 ... 17 more
04-29 09:35:17.520 W/monodroid(23147): Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack available

My class s implemented this way (IPCLIAE87Events is the interface declared in additions seciont of my android binding project):
    [Register("testandroidbinding/PCLEventsImplementation", DoNotGenerateAcw = true)]
    public class PCLEventsImplementation : Java.Lang.Object, IPCLIAE87Events
    {
        public void OnLibraryStarted(PCLIAE87 p0)
        {
            throw new NotImplementedException();
        }

        public void OnPOSConnected()
        {
            throw new NotImplementedException();
        }

        public void OnPOSDisconnected()
        {
            throw new NotImplementedException();
        }
    }
Demo project can be downladed at link
http://cloud.aliaslab.net/public.php?service=files&t=715fe3a47b2b5c365058de7d44f7741a

Thank you for any suggestions!
Lewix
Comment 3 lewix 2016-04-30 09:38:24 UTC
UPDATE
I noticed in your working example there is a java source file added to main project.
This java file refers to the "ISecondInterface".

This file was manually written by you or has been automatically generated in some folder?

Thank you for any suggestions.

Lewix
Comment 4 Brendan Zagaeski (Xamarin Team, assistant) 2016-04-30 19:23:31 UTC
@lewix,

See Comment 0:

> - Hand-edit the generated Android Callable Wrapper for
> `ImplementsSecondInterface`. The automatic ACW generator doesn't yet
> handle the special case of generic C# bindings for Java interfaces.

See also: https://developer.xamarin.com/guides/android/advanced_topics/java_integration_overview/android_callable_wrappers/

I got the original ACW for `ImplementsSecondInterface` by using the "normal" `Register` attribute for the `ImplementsSecondInterface` C# type. In particular, in the attached example from Comment 0 you'd need to temporarily change the `Register` attribute so that `DoNotGenerateAcw` is _false_.

> [Register("testapp/ImplementsSecondInterface", DoNotGenerateAcw = false)]


Then the project will generate the "default" ACW during the build process:

> TestApp/obj/Debug/android/src/testapp/ImplementsSecondInterface.java


Note that this means that any developer who is consuming your bindings library would also need to understand how to hand-edit the ACWs, so this approach is not suitable for publicly published bindings libraries.




## Stack Overflow for further follow-up

In general Bugzilla is not the best place to discuss techniques because it doesn't have much visibility to get answers from the broader Xamarin developer community. I would recommend posting any further questions on this topic on Stack Overflow, being sure to carefully follow the guidelines from [1] and [2].

[1] "How do I ask a good question?" http://stackoverflow.com/help/how-to-ask

[2] "How to create a Minimal, Complete, and Verifiable example" http://stackoverflow.com/help/mcve
Comment 5 lewix 2016-05-02 07:39:41 UTC
Hi Brendan,
thank you for your reply.

Sorry bud your first 2 comments seem not visible.
Ok i'll try what you suggested me and i'll let you know.

Thanks!

Lewix
Comment 6 lewix 2016-05-02 11:22:18 UTC
Hi Brendan,
i did as your suggestions, but i still get this runtime error when ai call my class ImplementsSecondInterface:
"Could not load type 'BindingProject.ISecondInterfaceEventsInvoker, BindingProject' from assembly ''."

Do i miss some settings to my Binding Library Assemblies?
Here this my demo project
http://cloud.aliaslab.net/public.php?service=files&t=dfffe4f7a6357e975fce8ef752e78ba4

My coworker has a Tamarin Business plan, if you tell me you can't help me here i'll try to contact them.

Thank you.

Lewix
Comment 7 lewix 2016-05-03 15:07:58 UTC
Ok issue fixed, my android binding project namespace and original jar library had the same name "com.ingenico.export", so project didn't know where to refer "ISecondInterface" implementation class.

Thank you for your help Brendan!

Lewix