Showing posts with label Android Mono. Show all posts
Showing posts with label Android Mono. Show all posts

More Progress on Writing Android Applications in Mono

Mono on Android: State of the Union

Having mostly finished up my surprisingly successful ROM Manager and ClockworkMod Recovery, I finally turned my attentions back to androidmono. Though Miguel recently announced the MonoDroid project, there's still room for a open source/hobbyist version. :) (And I need something to work on when I am bored)
When I put the project down a few months ago, I had very rough bindings to the Java API. I recently dumped the approach I took (albeit very cool), in favor of using jni4net. jni4net is a similar project that allows embedding a JVM into Microsoft's CLR, or vice versa. It only took a little bit of tweaking to make it work on Dalvik with Mono (I also hacked off a lot of unnecessary stuff that jni4net provides). So the following file:



Gives you this activity:

(Note: the code above is being displayed from my github repo and may change and leave the image out of date.)
I hacked up a Java/C# project to reflect on the Android SDK jar and create a C# android project that binds to the Java APIs via JNI. So, how it sort of works:
  • Create a class library and start coding up your services and activities.
  • Create a matching Java project (using the Android template as found on github) that uses the same namespace as your C# project.
  • Create "native" method stubs for all the overridden methods on your C#/Java doppleganger types. See here for an example.
  • The native methods get bound to the C# implementations at runtime automagically at runtime.

  • There's still a lot of work left to do: MonoDevelop template and plugin, a tool to create Java dopplegangers for each C# object via reflection, soft debugger support, translating Java's "OnSomeEvent" interfaces into true C# events/delegates, convenence extension methods, support for constructors (a Java constructor can not be "native"), and more. I wouldn't even say this is in Alpha stages yet, but it *works*.

    Android.dll

    androidmono

    Picture above represents the results of 3 sleepless nights. No, I didn’t rewrite the Android framework. I generated a bunch of proxy classes to access them from Mono though. Rough idea of how it works:

    1. Java application reflects through Android framework SDK jar and outputs an XML file that represents the object model.
    2. C# application generates thousands of code files of said object model. (This is similar to jni4net, but that doesn’t work on anything but Windows).
    3. Tag all the methods with the [MethodImpl(MethodImplOptions.InternalCall)] attribute.
    4. Implement the method calls at runtime with a combination of dynamically created expression tree delegates and mono_add_internal_call.

    The class contents actually have no code, so the total dll size is pretty small (800k). Here’s what the LinearLayout “implementation” looks like:

    namespace android.widget 
    { 
        public class LinearLayout : android.view.ViewGroup
        { 
            public class LayoutParams : android.view.ViewGroup.MarginLayoutParams
            { 
                [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
                public virtual extern java.lang.String debug(java.lang.String arg0); 
                [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
                public extern LayoutParams(android.content.Context arg0, android.util.AttributeSet arg1); 
                [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
                public extern LayoutParams(int arg0, int arg1); 
                [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
                public extern LayoutParams(int arg0, int arg1, float arg2); 
                [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
                public extern LayoutParams(android.view.ViewGroup.LayoutParams arg0); 
                [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
                public extern LayoutParams(android.view.ViewGroup.MarginLayoutParams arg0); 
                [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
                internal extern LayoutParams(); 
            } 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public virtual extern void setGravity(int arg0); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            protected override extern void onLayout(bool arg0, int arg1, int arg2, int arg3, int arg4); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public override extern int getBaseline(); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            protected override extern void onMeasure(int arg0, int arg1); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            protected override extern bool checkLayoutParams(android.view.ViewGroup.LayoutParams arg0); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public virtual extern new android.widget.LinearLayout.LayoutParams generateLayoutParams(android.util.AttributeSet arg0); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            protected virtual extern new android.widget.LinearLayout.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams arg0); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            protected virtual extern new android.widget.LinearLayout.LayoutParams generateDefaultLayoutParams(); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public virtual extern bool isBaselineAligned(); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public virtual extern void setBaselineAligned(bool arg0); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public virtual extern int getBaselineAlignedChildIndex(); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public virtual extern void setBaselineAlignedChildIndex(int arg0); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public virtual extern float getWeightSum(); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public virtual extern void setWeightSum(float arg0); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public virtual extern void setOrientation(int arg0); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public virtual extern int getOrientation(); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public virtual extern void setHorizontalGravity(int arg0); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public virtual extern void setVerticalGravity(int arg0); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public extern LinearLayout(android.content.Context arg0, android.util.AttributeSet arg1); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            public extern LinearLayout(android.content.Context arg0); 
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.InternalCall)] 
            internal extern LinearLayout(); 
        } 
    } 

    A year of on and off hacking coming to fruition soon…

    Building Mono using the Android NDK

    I finally got around to looking at the Android NDK yesterday. It is basically a subset of the Android Source build environment, but it has all the bits necessary to compile Mono. It only took a bit of finagling with the Android.mk to make everything work right. Building for Android is pretty easy now; the longest part is actually downloading and building the mcs assemblies. There’s a script to handle that though.

    The androidmono repository has updated instructions.

    Mono 2.5 for Android Released

    On and off over the last few weeks, I’ve been working on getting the Mono 2.6 branch, and its super awesome soft debugger, working on Android. I’ve also spent some time documenting and cleaning up the currently horrendous build process involved (I was really the only one that knew how to do it). Anyways, the Android Market now has the current 2.6 branch of Mono available for download (2.6 is not complete yet, hence the reason I am referring to it at 2.5). This release supports Cupcake, Donut, and Eclair!

    The project is hosted on Github in the androidmono repository.

    For those interested in building/hacking this themselves, here are the instructions:

      

    Developing and Debugging .NET applications on Android with Mono(Develop)

    Lots of cool stuff has been happening in Mono recently! First MonoTouch was released, which allows the development of iPhone applications in C#/MonoDevelop. The catch was that there was no debugger support. To address this glaring problem, Mono released created and released a Soft-Mode Debugger, which is basically an in-process debugger that lives in the runtime. After peeking at the code, I realized that porting this to make it work on Android would be a cinch! The result: you can now create a console application in MonoDevelop, deploy it to an Android emulator or device, and debug:

    md-android

     

    I blogged a while ago that I was working on an interop layer for Mono to allow it to utilize the Android SDK. This is mostly complete. Once I write up a tool to generate convenient wrapper classes, writing full blown applications for Android will become a reality!

    Android’s Linker makes Baby Jesus Cry

    release-1.0 branch:

    static int open_library(const char *name)
    {
    int fd;
    char buf[512];
    const char **path;

    TRACE("[ %5d opening %s ]\n", pid, name);

    if(strlen(name) > 256) return -1;
    if(name == 0) return -1;

    fd = open(name, O_RDONLY);
    if(fd != -1) return fd;

    for(path = sopaths; *path; path++){
    sprintf(buf,"%s/%s", *path, name);
    fd = open(buf, O_RDONLY);
    if(fd != -1) return fd;
    }

    return -1;
    }

    cupcake branch:

    /* TODO: Need to add support for initializing the so search path with
    * LD_LIBRARY_PATH env variable for non-setuid programs. */
    static int open_library(const char *name)
    {
    int fd;
    char buf[512];
    const char **path;

    TRACE("[ %5d opening %s ]\n", pid, name);

    if(name == 0) return -1;
    if(strlen(name) > 256) return -1;

    if ((name[0] == '/') && ((fd = _open_lib(name)) >= 0))
    return fd;

    for (path = sopaths; *path; path++) {
    snprintf(buf, sizeof(buf), "%s/%s", *path, name);
    if ((fd = _open_lib(buf)) >= 0)
    return fd;
    }

    return -1;
    }

    Summary: Android’s linker used to look in the current working directory to resolve library references. Now it doesn’t. (And it never used LD_LIBRARY_PATH at all) This is really only annoying for 3rd party command line applications which have references to libraries that aren’t part of the standard Android build (and contained in /system/lib). I need to figure out how to make gcc, et al, create binaries that store the full path to the libraries they reference, rather than just the base file name...

    Edit: I tried patching it to use LD_LIBRARY_PATH, but calling getenv from within the linker would always return NULL. Not sure why. So I did the next best thing and added “.” to the list of search paths. I think that is default behavior anyways on other Linux based systems (will verify later). Submitted the change, hope it gets accepted.

    Mono/Dalvik Interop

    I've been working away at getting Mono into a usable state on Android, and finally have something to show for it. I (mostly) completed the interop layer that allows code invocation of Mono from Dalvik, and vice versa. As a proof of concept test, I have my test verify reentrancy (i.e., you can invoke Mono method from Dalvik, which in turn invokes a Dalvik method from Mono).

    Java code that calls into Mono:

    String ret = (String)MonoBridge.invoke("MonoDalvikBridge.DalvikBridge, MonoDalvikBridge", String.class, "Echo", "hello");
    Log.i("MonoActivity", ret);

    And here's the C# method that was called. In turn, it calls back into Java:

    public static string Echo(string echo)
    {
    DebugLog(echo);
    string ret = Invoke<string>("com.koushikdutta.monodalvikbridge.MonoBridge", "echo", echo);
    DebugLog(ret);
    return echo;
    }

    And finally, this is the Java method that was called from C#:

    public static String echo(String str)
    {
    DebugLog(str);
    return str;
    }

    As shown, passing and returning "primitive" types between both runtimes works seamlessly. Passing complex types and calling methods on those objects works as well, by way of a runtime wrapper is created for it (MonoObject/DalvikObject).

    The basic syntax for invoking a method from one runtime into another is:

    Invoke(className | runtimeObject, methodName, returnType, params object[] arguments)

    That said, I'm pretty excited for when Mono supports the upcoming C# "dynamic" keyword. That will make interop (at least from Mono to Dalvik) quite trivial.

    There's still a bit of clean up and convenience functions I need to implement, but the majority of this is complete. The code is all open source, for those that are curious.