TouchDraw Code Reuse - Updated

As we just seeded the third beta release of TouchDraw for Android to our testers, I thought it would be a good time to publish some updated statistics on the code re-use in TouchDraw since we now have near final statistics for the Android version.

For those of you that didn't read the first post (and the associated disclaimers related to how these statistics were calculated), you can find it here.

As always, here are the pretty pictures first:

Next, here is the raw output from the script that calculated those statistics:

app      t       u      s       u%      s%
Mac 121114 28945 92169 23.90% 76.10%
Android 128036 35867 92169 28.01% 71.99%
iOS 141949 55363 86586 39.00% 61.00%

t is Total lines of code
u is Unique (platform specific) lines of code
s in Shared lines of code.

At a first glance, it seems like the Android version requires quite a bit less code than the iPad version; however the statistics don't tell the whole story.  What's not accounted for in the numbers is all of the things (layouts, menus, etc...) that is managed in code in the iPad version vs managed via resource files in Android.  

If we take into consideration all of the resource files, then platform specific "code" (term used loosely in this case) jumps up to around 35% for the Android version.  If our expectations hold true then the platform specific code in TouchDraw for iPad should drop to around 30 to 35% once we get version 2.0 finished.

I'm sure we would not have been able to build and deliver the first Android beta version in less than 3.5 months if we wouldn't have been able to re-use so much of our code base, and if we didn't have great tooling (MonoTouch, MonoMac and Mono for Android) at our disposal to enable this.  

Calling a dynamic library from MonoMac - Part 1

I ran into some difficulty today calling a native C library from MonoMac today and would have killed to have a working example to play with and build off of.  Since I finally was able to get everything working, I thought I'd share the sample here so that if someone else finds themselves in the same position I was they'll hopefully find this post.  (Besides, I'll probably need this as a refresher the next time I try to do this.)

This post will simply cover having a C# application call a native library with Mono from the command line.  I'll follow up with a second post later that explains how to integrate the library into a packaged MonoMac application.

First, in order to call a native library, we'll need a native library to call.  For the purposes of this sample, we'll create a simple function that echo's an integer value back to us.  Here's the C source code for that simple function:

//
// echo.c
//

#include <stdio.h>

int EchoInteger(int value)
{
    return value;
}

Second, we'll need to compile the source code:

gcc -arch i386 -c echo.c -o bin/echo.o

Third, we'll need to create dynamic library:

gcc -arch i386 \
 -dynamiclib \
 -o bin/libEchoTest.dylib \
 bin/echo.o \
 -install_name $CURRENT_DIR/libEchoTest.dylib

Next, one of the problems that I ran into originally was that my library wasn't built properly and the  function I was trying to call wasn't actually exposed externally in the library.  If you want to verify which symbols are exposed externally, you can use the "nm" command.  Here's how it would be invoked for this library, along with the results of execution.

/Applications/Xcode.app/Contents/Developer/usr/bin/nm -g bin/libEchoTest.dylib

00000f90 T _EchoInteger
         U dyld_stub_binder

The key thing to notice is the "T _EchoInteger" entry, which tells us the function we wrote is exposed externally.

Now that we know that our library is built correctly, we can move onto the C# call to invoke it.:

//
// Main.cs
//

using System;
using System.Runtime.InteropServices;

namespace MonoMacLoadLib
{
	class MainClass
	{
		[DllImport ("EchoTest")]
 		public static extern int EchoInteger (int arg);
 			
		static void Main (string[] args)
		{
			int echoResults = EchoInteger(11);
			System.Console.WriteLine("Echo Results:{0}",echoResults);
		}
	}
}

The key things is the code are the [DllImport("EchoTest")] public static extern int EchoInteger(int arg); lines.  These will bind the C# method to the C function via P/Invoke.  By default (as shown above), it will bind the C# method to a function of the same name (which in our case is "EchoInteger" in both the C and the C# code).  If you want to C# name to be different than in the C library, you can add an EntryPoint to the annotation: [DllImport ("EchoTest", EntryPoint="SomeOtherName")]

Now that we have the C# code written, we can move onto compiling and trying to run this.  Here is the command necessary to compile that class with Mono:

dmcs /noconfig "/out:bin/Test.exe" \
 "/r:/Library/Frameworks/Mono.framework/Versions/2.10.9/lib/mono/4.0/System.dll" \
 "/r:/Applications/MonoDevelop.app/Contents/MacOS/lib/monodevelop/AddIns/MonoDevelop.MonoMac/MonoMac.dll" \
 /nologo /warn:4 /debug:full /optimize- /codepage:utf8 /platform:x86 /define:DEBUG  /t:exe "Main.cs"

And here is the command necessary to run this:

mono bin/Test.exe

If everything went successfully you should see the following output:

Echo Results:11

Troubleshooting

If you run into problems (as I did originally) you may see a couple different errors/exceptions.  Here are a few things to look for and possible solutions to them:

The first error that you may see is the System.EntryPointNotFoundException exception.  If this happens, then you know that Mono was able to find your library; however it wasn't able to find the function that you referenced with your [DllImport] annotation.  You can use the "nm" command referenced above to make sure the expected function is externally available in the library, and you can also verify that your EntryPoint entry or C# method name are spelled correctly to match the C function.

The second error that you may see a System.DllNotFoundException exception.  This will occur if Mono can't find and load the dynamic library.  To resolve this, make sure that the name of the library is correct in your [DllImport] annotation, and make sure that it's in a location that it can be found by Mono.

Lastly, if you want to verify that Mono can even load your dynamic library, you can manually load the library using "MonoMac.ObjCRuntime.Dlfcn.dlopen()" method.  If that method is able to load the library, then it will return an IntPtr referencing the loaded library, otherwise it will return a 0 (IntPtr.Zero).  This was useful to me diagnose that I had errors in my library that prevented it from being loaded even though it was named and placed in the correct location.  Here is the C# sample from above that has been modified to also call the "dlopen" method as a first step.

using System;
using System.Runtime.InteropServices;
using MonoMac.ObjCRuntime;

namespace MonoMacLoadLib
{
	class MainClass
	{
		[DllImport ("EchoTest", EntryPoint="EchoInteger")]
 		public static extern int EchoInteger (int arg);
 			
		static void Main (string[] args)
		{
			// The current directly is expected to be passed in as arg[0] for this sample to work
			string path = args[0] + "/bin/libEchoTest.dylib";
			System.Console.WriteLine("Dylib Path:{0}", path);	
					
			IntPtr libPtr = Dlfcn.dlopen(path, 1);
			System.Console.WriteLine("Pointer:{0}",libPtr);
			
			int echoResults = EchoInteger(11);
			System.Console.WriteLine("Echo Results:{0}",echoResults);
		}
	}
}	

The source code for this sample is available on GitHub, along with a shell script that will compile everything and run the sample.

TouchDraw Code Reuse

At the beginning of the year Frank Krueger posted a blog entry on the code reuse in iCircuit, which is built using MonoTouch and MonoMac.

As we're 1 or 2 days away from submitting TouchDraw for Mac to Apple, I thought it would be an interesting exercise to generate some statistics on the code reuse for TouchDraw.  Like iCircuit, TouchDraw for iPad is developed using MonoTouch, and TouchDraw for Mac is developed using MonoMac.

I used the same script that Frank created to calculate the stats, and I'll mention the same description/disclaimer that he did:  All the compiled code for each project is put into two buckets: unique code and shared code. Unique code is code used only for that project while shared code is code that was used in more than 1 project.

First, some pretty pictures:

Code reuse between TouchDraw for iPad and TouchDraw for Mac

Second, the raw output from the script is as follows:

app     t       u       s       u%      s%
iOS     139809	55685	84124	39.83 %	60.17 %
Mac     115221	29198	86023	25.34 %	74.66 %

t is Total lines of code
u is Unique (platform specific) lines of code
s in Shared lines of code.

Lines of code include comments and white space.

Note: Like with iCircuit, we have a consistent coding style, so this simple metric is good enough for me to understand the relationship of our shared code to platform specific code.

As you can see, more platform specific code was required for the iPad version than for the Mac version.  There are many reasons for this; however our belief is that by the time we finish TouchDraw for iPad 2.0 and TouchDraw for Android (which is of course being built with Mono for Android) that the code reuse in the iPad version will increase to 65% to 70%.

Final Note: If you look really closely, you'll see that the "shared" lines of code above do not  match up. That is because when we calculate the statistics we are also measuring the  development version of TouchDraw for Android.  Once we get closer to the actual release of that, I'll share those numbers here as well.