The Weekly Source Code 16 - Duct Tape Edition
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 IEnumerableSelect (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, IEnumerableexposedTypes) { 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!
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Best regards...
I don't see what is C# 3.0 about that code. It just looks like a static class with static helper methods.
I don't see what is C# 3.0 about that code. It just looks like a static class with static helper methods."
I'm guessing that what Scott meant is that my static methods duplicate functionality that was introduced alongside C# 3.0 (though technically the methods are part of .NET 3.5).
But you're right, there's nothing C# 3.0 about the methods. My understanding is that Mono has some support for Linq and C# 3.0, but I haven't upgraded my copy of it recently enough to see any of those features.
The big advantage that IDisposable gets you over deterministic destructors is that C# has language features that help you ensure that the Dispose() method gets called--even in the presence of exceptions and whatnot.
Also, unlike deterministic destructors (in any language with which I'm familiar), IDisposable is not required on all objects that reference other objects; you only need it when you're dealing with native resources (or other things that need to be released in a deterministic fashion).
Personally, I vastly prefer explicitly specifying which types need special cleanup over being required to write standard cleanup code for (nearly) all of my types.
That's ironic, since you now work on the ASP.NET team. The IIS Rearchitecture project in the late '90s which eventually became ASP.NET, http.sys, and IIS 6 was known as DuctTape. Mainly because several people threatened to tape Dmitry Robsman's mouth shut with duct tape, because he kept piping up in team meetings and asking questions that threw people for a loop.
Quite true. However, you still have to wrap each of those heap-allocated members in another object, which is quite a bit of extra typing for something that is a pretty common practice. Again, it comes back to the question of how much housekeeping code you have to/want to write.
All it really does is indicate that I'll no longer be using autoReleasePool. Instances of Objective C classes are reference counted, and sending an instance the message "release" decreases the reference count. Since I'm no longer supposed to be holding a reference to the auto release pool, I set autoReleasePool to "null".
Technically, it's not necessary, but its an old habit of mine to always set pointers to null after releasing/freeing/deleting them, and old habits die hard.
Comments are closed.