Xamarin Android Bindings fails? Lets hack into JNI World

Standard

Hey.

I am writing Android application which requires some kind of “Emoji” selector (emoji picker).

The first thing I did – I’ve looked for some ready-to-use implementations and I found brilliant java library by Niklas Baudy Vanniktech called Emoji

This is well developed, modular library – you install main package “Emoji” and then Emoji Providers – so you can use multiple emoji types (Google-like/iOS/Twitter and so on).

Creating Xamarin Android Bindings Project

Library did not have Xamarin Android bindings so I had to generate it manually. I have put output main library and Google Compat Emoji Provider (.aar files) into Android Bindings projects (binding generator).

Adding Android library dependencies to Bindings Project

After I have added .aar files to bindings project I added dependencies required by Emoji library. This is usually a step where less advanced developers fails while creating Xamarin Bindings.
If library A takes dependency to library B and C, the bindings project of library A has to reference bindings or reference jars of library B and C.

To check we need to look at binded library build.gradle or pom file (this is something similar to Nuget packages.xml/project.json we use in .NET).
I have looked at build.gradle of Emoji and Emoji Google Compat Provider:

App build.gradle -> https://github.com/vanniktech/Emoji/blob/master/build.gradle

Emoji build.gradle:

emojibuildgradlegooglecompatbuildgradle

As we can see, Emoji library needs reference to Android Support AppCompat and CardView libraries. The Emoji-Google-Compat (Google Emojis Provider) requires reference to original Emoji library and Android Support Emoji Library.

Those are pretty common libraries in Android world so I can just add Nuget packages of those libraries to the Emoji and Emoji Google Compat Bindings Project.

Screen Shot 2018-03-24 at 15.12.53.png\

Fist error on board

We start building our projects and we get…

Screen Shot 2018-03-24 at 15.21.50.png

GoogleCompatEmoji: cannot derive from sealed type “Emoji”.

Well, we need to check how generated Com.Vanniktech.Emoji.Emoji.Emoji class look like.

Lets look at generated code

To see how generated code look like toggle “Display all files” in Visual Studio for Mac option at solution level.

Screen Shot 2018-03-24 at 15.17.42.png

Then expand “obj”/”generated/src” folders and open “Emoji” class.

Screen Shot 2018-03-24 at 15.26.24.png

As we can see –  class has sealed modifier which means we can not inherit from that. Those kind of a problems arise because of C#/Java languages differences – not all of them are correctly resolved by Xamarin Bindings generator so we have to fix that manually.

Each bindings project has Transforms folder with “metadata.xml” file – which can be used to modify generator output code.

We have path to the generated class – we just need to remove “sealed” attribute.
This can be done by using final metadata attribute.

Screen Shot 2018-03-24 at 15.29.13.png

To read more about available transforms check: https://developer.xamarin.com/guides/android/advanced_topics/binding-a-java-library/customizing-bindings/java-bindings-metadata/ 

Use bindings library in Android application

Both projects builds without any error now – seems as everything works. This is the time to use documentation of original library and plug it into my app.

Screen Shot 2018-03-24 at 15.15.18.png

Unfortunately – bindings generator did not generate GoogleCompatEmojiProvider class which is required to setup library. The other classes suggested by documentation like EmojiPopup  are available though.

Resolve errors by analysing original code

First – lets check what classes are generated in Emoji and Emoji Google Compat bindings project.

Screen Shot 2018-03-24 at 15.40.12.png

Screen Shot 2018-03-24 at 15.35.05.png

Right, we do not have GoogleCompatEmojiProvider class. Weird.
We need to find reason why this class has not been generated in Bindings Project.
The only reliable way to do that is by checking original java code – fortunately this is open-source project so we can do that – https://github.com/vanniktech/Emoji/blob/master/emoji-google-compat/src/main/java/com/vanniktech/emoji/googlecompat/GoogleCompatEmojiProvider.java.

(btw. what to do when we do not have access to source code? easy, just look at decompiled original library ^_^  how? look at my MultiDex post)

If class is not generated – the problem almost always lie in fact that it uses type that is not available either in bindings project or referenced project.

Lets look at class definition

public final class GoogleCompatEmojiProvider implements EmojiProvider, EmojiReplacer

This class implements *EmojiReplacer* interface but it is not available in *Emoji library bindings* (see screens above).

Resolve errors by analysing original code part 2

So we now know that in order to get GoogleCompatEmojiProvider we need to get EmojiReplacer interface in Bindings.

Lets look at original code once again – this time at *EmojiReplacer* interface – https://github.com/vanniktech/Emoji/blob/ebe1cb05c38999dc4233655b8d754e33cacd4c71/emoji/src/main/java/com/vanniktech/emoji/EmojiReplacer.java

Screen Shot 2018-03-24 at 15.43.37.png

Honestly, I don’t know if it is possible to make Bindings Generator generate automatically this interface for us. I suspect there is a bug there which prevents to generate classes which have strong cycles – replaceWithImages method takes EmojiReplacer as method  parameter.

Can we bind this library anyway?

Yes, we can 🙂

Although the only way I have found to restore EmojiReplacer class back is by writing it manually 🙂

I have not found a lot documentation about manually putting Java classes into Xamarin world so I have analysed and reverse-engineered Bindings Generator output code.

To use Java types in C#, Xamarin uses bunch of [Register] attributes and pointers (IntPtr).
In fact – it is not really intended to be used in “manual code”.

The mechanism is briefly described here: https://developer.xamarin.com/guides/android/advanced_topics/binding-a-java-library/customizing-bindings/java-bindings-metadata/

Register Attribute

Lets start with analysing [Register] attribute.
To use Java code in C# – we have to use this attribute. Shortly speaking – it tells compiler how to control C#-Java proxy. Screen Shot 2018-03-24 at 15.59.44.png

First parameter is name – this is the path to the type – in case of interface –EmojiReplacer that would be com/vanniktech/emoji/EmojiReplacer

The second parameter is signature – this is used to determine method signature (what kind of parameter it takes, what is the return type) – for interface we should leave it empty -> “”

The third parameter is connector – that is managed class name which connects native code with managed code. This is why bindings output contains bunch of Invoker classes – they are called whenever the original interface method is called.

EmojiReplacer interface bound to C#

Screen Shot 2018-03-24 at 16.05.16.png

1. We have to add IEmojiReplacer interface in Bindings project.
2. We have to register this interface – EmojiReplacer sits in com.vanniktech.emoji package so “name” parameter is equal to “com/vanniktech/emoji/EmojiReplacer“.
3. EmojiReplacer is interface so we leave signature parameter empty.
4. We have to implement connector class – the managed code which will be called when the interface is used – we will create class IEmojiReplacerInvoker in namespace Com.Vanniktech.Emoji so we put “Com.Vanniktech.Emoji.IEmojiReplacerInvoker” in “connector” parameter.

5. Looking at EmojiReplacer we can observer that it has just one method – replaceWithImages and it takes five parameters.
We have to add appropriate [Register] attribute.

6. First parameter – name should be equal to java method name – “replaceWithImages“.

7. Second parameter – should describe method signature.
Primitive types are described with one letter, ex. “F” stands for float, “V” for void
Complex types like objects are prefixed with Lnamespace. There is “;” character after each complex type.

The parameter format is:
(parameters description)ReturnType

We have five parameters:

a) Android.Content.Context – object so – “Landroid/content/Context;“.

b) Android.Text.Spannable – object so – “Landroid/text/Spannable;“.

c) Float – primitive type float so – “F“.

d) Float – primitive type float so – “F“.

e) IEmojiReplacer  – object which we have just registered so – “L/com/vanniktech/emoji/EmojiReplacer”.

The return type is void – so “V“.

Putting that together we get:
(Landroid/content/Context;Landroid/text/Spannable;;FFL/com/vanniktech/emoji/EmojiReplacer;)V

8. Third parameter – should contain path to the managed method of previously described connector class.

The format for this parameter is:
“registeredMethodName:namespace.ConnectorClassName, AssemblyWhereConnectorClassLies”.

We will add this connector class in next step.

EmojiReplacer interface bound to C# – managed connector class

The final connector class is presented below. It had been written partially manually thus I will explain it in a moment.

[ [global::Android.Runtime.Register (“com/vanniktech/emoji/EmojiReplacer”, DoNotGenerateAcw=true)]
    internal class IEmojiReplacerInvoker : global::Java.Lang.Object, IEmojiReplacer {

        static IntPtr java_class_ref = JNIEnv.FindClass (“com/vanniktech/emoji/EmojiReplacer”);

        protected override IntPtr ThresholdClass {
            get { return class_ref; }
        }

        protected override global::System.Type ThresholdType {
            get { return typeof (IEmojiReplacerInvoker); }
        }

        IntPtr class_ref;
 

        static IntPtr Validate (IntPtr handle)
        {
            if (!JNIEnv.IsInstanceOf (handle, java_class_ref))
                throw new InvalidCastException (string.Format (“Unable to convert instance of type ‘{0}’ to type ‘{1}’.”,
                            JNIEnv.GetClassNameFromInstance (handle), “com.vanniktech.emoji.EmojiReplacer”));
            return handle;
        }

        protected override void Dispose (bool disposing)
        {
            if (this.class_ref != IntPtr.Zero)
                JNIEnv.DeleteGlobalRef (this.class_ref);
            this.class_ref = IntPtr.Zero;
            base.Dispose (disposing);
        }

        public IEmojiReplacerInvoker(IntPtr handle, JniHandleOwnership transfer) : base (Validate (handle), transfer)
        {
            IntPtr local_ref = JNIEnv.GetObjectClass (((global::Java.Lang.Object) this).Handle);
            this.class_ref = JNIEnv.NewGlobalRef (local_ref);
            JNIEnv.DeleteLocalRef (local_ref);
        }
        
        static IntPtr _replaceWithImagesMethodPointer;
        
        
        [Register (“replaceWithImages”, “(Landroid/content/Context;Landroid/text/Spannable;FFLcom/vanniktech/emoji/EmojiReplacer;)V”, “”)]
        public virtual unsafe void ReplaceWithImages(Context context, ISpannable text, float emojiSize, float defaultEmojiSize,
            IEmojiReplacer fallback)
        {
            if (_replaceWithImagesMethodPointer == IntPtr.Zero)
                _replaceWithImagesMethodPointer = JNIEnv.GetStaticMethodID (class_ref, “replaceWithImages”, “(Lcom/vanniktech/emoji/EmojiReplacer;)V”);
            try
            {
                JValue* __args = stackalloc JValue[5];
                __args[0] = new JValue(context);
                __args[1] = new JValue(text);
                __args[2] = new JValue(emojiSize);
                __args[3] = new JValue(defaultEmojiSize);
                __args[4] = new JValue(fallback);
                JNIEnv.CallVoidMethod((this).Handle, _replaceWithImagesMethodPointer, __args);
            } finally 
            {
            }        
        }
    }

 

Lets start with class definition – it has to inherit from Java.Lang.Object and implement interface (for which it has been defined as connector).

Diving through binding generator code it turns out that we have to override two members – ThresholdClass and ThresholdType.

ThresholdClass should be a pointer to Java.Lang.Class for which it was registered as connector – use JNIEnv.FindClass(“path”) for that.
TheresholdType should return Invorker C# Type.

The Validate, Dispose and Constructor has been copied from other autogenerated binding class. This is responsible for reference counting of the object – you can just copy-paste that in your Invoker class.

The implementation of IEmojiReplacer is the thing what we really need here.
It finds pointer to the Java Method, create method parameters call stack and finally calls Java method using JNIEnv.CallVoidMethod(..)

We allocate JValue array on stack using C# stackalloc (so it won’t create garbage collector pressure – as far as I know that was one of the optimizations made by Xamarin team to the Bindings Generator – which had some positive performance impact on Xamarin.Java code).

Make sure you set JValue array members in original Java method parameters order – so the method is called properly with proper parameters.

Build and have a fun!

After rebuild – it works! The missing classes suddenly popped out.

Library works but has some annoying issues (for ex. does not work inside fragment, had to hack a bit).

Screenshot_1521931925.png

I have showed you few advanced techniques dealing with Xamarin Android Bindings issues.

Hope it help you someday!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s