A few weeks ago I interviewed Steven Frank (blog), co-owner of Panic and a Mac Developer (who I went to college with). After that interview I stumbled upon the very recently release NSDuctTape project. First, how can you not like a project named after Duct Tape. Second, whenever I hear that some code will bridge to completely incongruent and unbridgeable things, I gotta check it out. What can I say, if there's a freak somewhere that promises to tape two things together, I want to see it! ;) (And I mean freak in the most positive way!)
NSDuctTape is niche, to be clear, but if you want to write .NET code using Mono on the Mac and you want access to the Objective C Cocoa libraries, this is your one-stop shop. (NOTE: If you do download his source, you'll likely have to pull the files out one at a time because there's Mac files in the zip with the same names as folders and Windows doesn't like it.)
And so, Dear Reader, I present to you sixteenth in a infinite number of posts of "The Weekly Source Code." Here's some source I was reading this week.
Dave, the author, hasn't check on Mono's support for Linq, but he uses C# 3.0 features to create his own LINQ-lite helper methods. I found this to be a clever "punt."
internal static class Enumerable
{
public static IEnumerable Select(IEnumerable list, Converter convert)
{
foreach (TInput value in list)
yield return convert(value);
}
public static IEnumerable SelectMany(IEnumerable list, Converter> convert)
{
foreach (TInput value in list)
foreach (TOutput converted in convert(value))
yield return converted;
}
public static IEnumerable Where(IEnumerable list, Predicate predicate)
{
foreach (T value in list)
if (predicate(value))
yield return value;
}
public static List ToList(IEnumerable list)
{
List result = list as List;
return result ?? new List(list);
}
public static T[] ToArray(IEnumerable list)
{
return ToList(list).ToArray();
}
}
Because he's "thunking" (not the technically accurate word, but I like saying it) down into unmanaged code that needs to have handles allocated and deallocated, he creates an HGlobal wrapper class using my most favorite .NET BCL pattern, IDisposable. Classic stuff, simple and works great.
...snip...
public void Dispose()
{
if (_hGlobal != IntPtr.Zero)
Marshal.FreeHGlobal(_hGlobal);
_hGlobal = IntPtr.Zero;
}
private DisposableHGlobal(IntPtr hGlobal)
{
_hGlobal = hGlobal;
}
public static DisposableHGlobal StructureToHGlobal(T value)
where T : struct
{
DisposableHGlobal result = new DisposableHGlobal(Marshal.SizeOf(value));
Marshal.StructureToPtr(value, result.ToIntPtr(), false);
return result;
}
...snip...
Finally, in his application managed "wrapper" he spins through his chosen System.Types and registers each of them with ObjectiveC. This interop is one way, meaning that he's choosing to expose his .NET types as Objective C classes.
public static void Run(string nibFile, IEnumerable exposedTypes)
{
ObjectiveCClass nsAutoReleasePoolClass = ObjectiveCClass.GetClass("NSAutoreleasePool");
IntPtr autoReleasePool = nsAutoReleasePoolClass.Instantiate();
ObjectiveCMethods.SendMessage(autoReleasePool, ObjectiveCMethods.SelectorFromString("init"));
try
{
IntPtr process = IntPtr.Zero;
GetCurrentProcess(ref process);
TransformProcessType(ref process, ProcessType.ForegroundApplication);
SetFrontProcess(ref process);
Registrar.Initialize();
foreach (Type type in exposedTypes)
{
ObjectiveCNameAttribute attribute = MemberInfoUtility.GetCustomAttribute(type);
Registrar.RegisterClass(attribute != null ? attribute.Name : type.Name, type);
}
ObjectiveCClass nsBundleClass = ObjectiveCClass.GetClass("NSBundle");
IntPtr name = NativeString.StringToNativeString(nibFile);
ObjectiveCClass nsDictionaryClass = ObjectiveCClass.GetClass("NSDictionary");
IntPtr key = NativeString.StringToNativeString("NSOwner");
ObjectiveCClass nsApplicationClass = ObjectiveCClass.GetClass("NSApplication");
IntPtr sharedApplication = ObjectiveCMethods.SendMessage(nsApplicationClass.ToIntPtr(), ObjectiveCMethods.SelectorFromString("sharedApplication"));
IntPtr nsDictionary = ObjectiveCMethods.SendMessage(nsDictionaryClass.ToIntPtr(), ObjectiveCMethods.SelectorFromString("dictionaryWithObject:forKey:"), sharedApplication, key);
IntPtr zone = ObjectiveCMethods.SendMessage(sharedApplication, ObjectiveCMethods.SelectorFromString("zone"));
ObjectiveCMethods.SendMessage(nsBundleClass.ToIntPtr(), ObjectiveCMethods.SelectorFromString("loadNibFile:externalNameTable:withZone:"), name, nsDictionary, zone);
ObjectiveCMethods.SendMessage(sharedApplication, ObjectiveCMethods.SelectorFromString("run"));
}
finally
{
ObjectiveCMethods.SendMessage(autoReleasePool, ObjectiveCMethods.SelectorFromString("release"));
autoReleasePool = IntPtr.Zero;
}
}
It's inside the RegisterClass where he creates Objective C classes for each .NET class, lazily making class definitions, and poking values into them. He's using "reflection" on both sides...reflecting over the .NET types, methods, etc and dynamically creating the same types, methods, etc on the ObjectiveC side.
Freaky and fun!
Hosting By