Step by Step java jar to C# Binding

03:16 Unknown 3 Comments

         In this walkthrough, we will use the following steps to bind a Java .JAR to create a new .NET assembly that we can use in a Xamarin.Android project:



  1. Create a new Java Bindings Library project.
  2. Add .JAR files to the project.
  3. Set the build action for your .JAR files using the EmbeddedJar or InputJar build actions.
  4. Choose the target framework.
  5. Build the bindings library.
After we have successfully created a .JAR Bindings Library, we'll incorporate it into a Xamarin.Android project. To prepare for this walkthrough, download the OsmDroidBindingExample and extract the following .JAR files from OsmDroidBindingExample/OsmDroidBinding/Jars:
  • slf4j-android-1.6.1-RC1.jar
  • osmdroid-android-4.2.jar


Creating the Project

The first thing we need to do is create a new project for the binding.

  1. Select File > New > Project.
  2. Select the Java Bindings Library project template and name it OsmDroid.


Adding The OsmDroid Java Libraries

Creating the project gives us an empty bindings library. But, we need to add the actual Java Library .JAR file to the binding project so that it can be bound.
  1. OsmDroid requires two .JAR files which must be added to the project. Copy the osmdroid-android-4.2.jar and slf4j-android-1.6.1-RC1.jar files (extracted earlier) and copy them into the Jars folder of the project using Windows Explorer.

  2. Switch back to Visual Studio. Right click on the Jars folder and select Add > Existing Item:

  3. Add the files osmdroid-android-3.0.8.jar and slf4j-android-1.5.8.jar to the project:

  4. Next, we need to tell the project which .JAR file that we want to create the bindings for. We do this by setting the Build Action of the file osmdroidandroid-3.0.8.jar to EmbeddedJar as shown below:

  5. The file step is to tell the project that the file slf4j-android-1.5.8.jar is a reference jar that is required by OsmDroid. A reference .JAR is a .JAR file that is required by another .JAR (such as the input .JAR), but we do not want to generate bindings for it. We do this by setting the Build Action of the file slf4j-android-1.5.8.jar to EmbeddedReferenceJar as shown below:

At this point, we have a basic setup for creating the binding. However, for most configurations, including this case, to successfully build the binding we'll need to fine-tune it with some additional manual steps.


Resolving API Differences

When creating a binding, the binding project will:
  • Read all the information from the .JAR.
  • Generate a .NET assembly containing the Managed Callable Wrapper.

Transform Files

The binding project contains a Transforms folder that has three files that are used to control how the binding is generated.
  • EnumFields.xml – Maps Java int constants to C# enums.
  • EnumMethods.xml – Supports changing method parameters and return types from Java int constants to C# enums .
  • MetaData.xml – Supports changes to be made to the final API, such as changing the namespace of the generated binding.
After the bindings project reads out all the information from the .JAR, we can use the files listed above to modify the API before it becomes an assembly. There are two reasons to do this:
  • To fix issues with the binding.
  • To customize the API design by changing names or types, by removing unused pieces, etc.


Fixing Build Issues

Sometimes a binding will build without modification when we create it as we did in the previous section. In many cases, though, some modification will be needed before it will build correctly.
To resolve possible build issues, we need to modify the mappings used to generate the binding. We can then account for any differences between the Java API and the bindings that ship with Xamarin.Android, such as in the case described earlier, where Java int constants are replaced by C# enums in the Xamarin.Android binding.
For example, in the case of the osmdroid binding, if we try to compile the binding project as-is, we'll get the errors shown below, (Xamarin Studio is shown here but Visual Studio will look similar to this):

These errors are occurring for a variety of reasons:
  • Some classes are less accessible than their sub-classes.
  • Some classes provide overrides that hide methods in base classes.
Double-clicking on an error will take us to the generated file. The first error reports a problem in the generated code for the class Org.Osmdroid.Tileprovider.Modules.TileLoader, a snippet of which is shown below:
[global::Android.Runtime.Register ("org/osmdroid/tileprovider/modules/MapTileDownloader$TileLoader", DoNotGenerateAcw=true)]
    public new partial class TileLoader : global::Org.Osmdroid.Tileprovider.Modules.MapTileModuleProviderBase.TileLoader {
The problem is that the base class, Org.OsmDroid.TileProvider.Modules.MapTileModuleProviderBase.TileLoader, has an accessibility of protected, and the subclass is public. We can correct this by editing the file Metadata.xml, and changing the visibility of the generated C# class with the following:
<attr path="/api/package[@name='org.osmdroid.tileprovider.modules']/class[@name='MapTileModuleProviderBase.TileLoader']" name="visibility">public</attr>
For the osmdroid .JAR, it is necessary to do this several more times in order to adjust these visibility discrepancies. For example, if you examine Transforms\Metadata.xml, you will notice this mapping:
<attr path="/api/package[@name='org.osmdroid.views.overlay']/class[@name='Overlay']/method[@name='draw']" name="visibility">public</attr>
This mapping will ensure that the visibility of the method Overlay.Draw is also public.


Correcting Enums

In order to get osmdroid to build, we need to change the two methods to use .NET enums instead of int constants of the Java code. To do this, open up the EnumMethods.xml file under the Transforms directory in the project. The following mapping can be seen:
<mapping jni-interface="org/osmdroid/api/IMapView">
    <method jni-name="setBackgroundColor" parameter="p0" clr-enum-type="Android.Graphics.Color" />
</mapping>
In the above example, we need the binding generator to map the parameter p0 to be the enum Android.Graphics.Color in the setBackgroundColor method of the Java interface org.osmdroid.api.IMapView.
If we rebuild again, everything should now succeed and we'll have a MyGoogleMaps.dll that is ready to use.


Obfuscated Types

Beginning in Xamarin.Android 4.4, the binding generator treats any classes and interfaces whose name only consists of lower case letters, numbers, or the $ as obfuscated. The obfuscated types will be filtered out by the by the binding generator and not included in the Java Binding library.
It is possible to modify Metadata.xml to force an obfuscated class to be included in Java Binding Library as follows:
<metadata>
 <attr path="/api/package[@name='{package_name}']/class[@name='{name}']" name="obfuscated">false</attr>
</metadata>


Normalizing the API

Changing intenum is just one way to modify an API to make it more familiar looking for .NET users. Some other things we might want to do to ensure that the API follows .NET patterns are:
  • Rename namespaces, classes, methods, fields, or parameter names to follow .NET conventions.
  • Remove namespaces, classes, methods, or fields that aren't needed.
  • Move classes to different namespaces.
  • Add additional support classes to make the design of the binding look more like .NET framework classes.


API Metadata

Changes to the final API can be specified in the Transforms\Metadata.xml file. For example, the namespace of our Open Street Maps library is generated as Org.Osmdroid. This type characterization reflects the capitalized Java namespace, but looks awkward to a .NET developer.
Let's add some XML to Metadata.xml that changes the namespace to OsmDroid:
<attr path="/api/package[@name='org.osmdroid']" name="managedName">OsmDroid</attr>
This causes the generator to find the Java package org.osmdroid and to change its .NET name to OsmDroid. It will not adjust any nested namespaces – so for each namespace that we wish to change we must have one mapping.
The Metadata.xml file gives us the power to modify an API in any we want. For more information about the supported metadata operations, see Java Bindings Metadata.


Fixing Up Parameter Names

When generating the managed callable wrapper, the generated C# code does not retain the names of parameters on Java methods. For example, the Java method org.osmdroid.views.MapView() takes only one parameter. However the generated C# code will name this parameter as p0. This is not a very meaningful name. To fix this, we can add the following mapping to Metadata.xml:
<attr path="/api/package[@name='org.osmdroid.views']/class[@name='MapView']/method[@name='setTileSource']/parameter[@name='p0']" name="name">tileSource</attr>


Limitations

When creating a binding, we may add additional supporting classes to design the binding. However, if we add members to any partial classes that we generated in the binding, we must not declare any instance fields that can reference a Java.Lang.Object subclass at runtime. This includes types such as:
  • Java.Lang.Object (and subclasses).
  • System.Object
  • Interface fields
This restriction prevents the garbage collector from prematurely collecting objects. If a partial class must declare an instance field that can refer to a Java.Lang.Object subclass at runtime, it must use either WeakReference or GCHandle to refer to this subclass.
However, if we were working with a partial class that exposes a Java interface through .NET events, we would need to include a reference to a Java.Lang.Object.
For example, consider the ViewPager class, which is part of the Android v4 support package. This class has a ViewPager.OnPageChangeListener interface that receives callbacks. A portion of the generated binding is shown below, (with RegsiterAttributes omitted for brevity):
public partial class ViewPager : global::Android.Views.ViewGroup {
        public partial interface IOnPageChangeListener : IJavaObject {
                        void OnPageScrollStateChanged (int p0);
                        void OnPageScrolled (int p0, float p1, int p2);
                        void OnPageSelected (int p0);
        }
        public virtual void SetOnPageChangeListener (
        global::Android.Support.V4.View.ViewPager.IOnPageChangeListener p0)
}
In order to expose the callback methods in the interface as .NET events, we need to create a helper class that implements the interface and maps the methods to the events, which we can do by implementing the methods in terms of EventHandler<T> delegates, as shown below for the OnPageScrollStateChanged method.
internal partial class OnPageChangeEventDispatcher : Java.Lang.Object,
        ViewPager.IOnPageChangeListener
{
        ViewPager sender;

        public OnPageChangeEventDispatcher(ViewPager sender)
        {
                this.sender = sender;
        }

        internal EventHandler<PageScrollStateChangedEventArgs>
                PageScrollStateChanged;

        public void OnPageScrollStateChanged(int p0)
        {
                var h = PageScrollStateChanged;
                if (h != null)
                        h(sender, new PageScrollStateChangedEventArgs()
                                { State = p0 });
        }

        // OnPageScrolled and OnPageSelected omitted for brevity
}

public class PageScrollStateChangedEventArgs : EventArgs
{
        public int State { get; internal set; }
}
Now, we need to add our implementation to the ViewPager partial class that contains the actual events. This class needs to keep an instance of the OnPageEventDispatcher that is dispatching the events from the callback methods. However, we must use a WeakReference (as mentioned earlier), so that the garbage collector does not collect the object prematurely, as shown below:
partial class ViewPager
{
        WeakReference dispatcher;
        OnPageChangeEventDispatcher EventDispatcher
        {
                get
                {
                        if (dispatcher == null || !dispatcher.IsAlive)
                        {
                                var d = new
                                        OnPageChangeEventDispatcher(this);
                                SetOnPageChangeListener(d);
                                dispatcher = new WeakReference(d);
                        }
                        return(OnPageChangeEventDispatcher)
                                dispatcher.Target;
                }
        }

        public event EventHandler<PageScrollStateChangedEventArgs>
                PageScrollStateChanged
        {
                add
                {
                        EventDispatcher.PageScrollStateChanged += value;
                }

                remove
                {
                        EventDispatcher.PageScrollStateChanged -= value;
                }
        }
}
The OnPageScrolled and OnPageSelected methods can be implemented in a similar fashion.


Using the Bound Library

Once the Java library has been bound, we can use the binding in a Xamarin.Android project just like any other .NET assembly.


Add a Reference to the Binding Assembly

For example, to use the Open Street Maps binding from the previous section in another Xamarin.Android application, first add a reference to the dll for the binding:
  1. Right-click on the solution, and then select Add > New Project.
  2. Select Mono for Android > Android Application, and then give it a name such as OsmDroidTest, as shown below:

  3. Right-click on the new project in the Solution Explorer, and select Add Reference.
  4. Select the OsmDroid binding project, and then click OK to add the reference, as shown below:



Summary

In this article, we walked through how to use the Java Bindings Library project template to create a binding to a Java library. Using the Open Street Maps library as an example, we showed how to add a .JAR to the project and how to generate that binding. We then examined how to resolve any errors that resulted from incompatibilities between the Java library and Xamarin.Android. Finally, we discussed techniques that showed us how to shape an API exposed by a binding to more closely follow .NET design guidelines.

You Might Also Like

3 comments: