Scott Hanselman

The Weekly Source Code 16 - Duct Tape Edition

February 21, '08 Comments [18] Posted in Source Code
Sponsored By

duct-tape-roll 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!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Thursday, February 21, 2008 7:07:46 AM UTC
My RSS feed had the code all mangled and unindented so had to click through - weird...
Thursday, February 21, 2008 8:16:48 AM UTC
FYI this came across RSS truncated/mangled...using BlogLines
Thursday, February 21, 2008 9:00:16 AM UTC
Wow, I don't know why. It's just pre tags, do a view source. Any ideas>
Thursday, February 21, 2008 9:02:11 AM UTC
Looks like Feedburner started messing with whitespace. I'm on it.
Thursday, February 21, 2008 1:24:37 PM UTC
Okay good, I was thinking there was something wrong w/ my reader :) Its much better actually on the page. Crazy stuff.
Thursday, February 21, 2008 2:55:57 PM UTC
Thanks for the plug! I'm glad you enjoyed reading through the code.
Thursday, February 21, 2008 3:36:04 PM UTC
Scott, the mangledness in your feed is likely caused by the use of tabs in teh code. When inserting source code, you should try to replace tabs with 4 spaces and see if that works.

Best regards...
Thursday, February 21, 2008 5:19:51 PM UTC
This is actually WSC #16.
Thursday, February 21, 2008 10:59:01 PM UTC
"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 don't see what is C# 3.0 about that code. It just looks like a static class with static helper methods.
Wade C
Thursday, February 21, 2008 11:25:16 PM UTC
I don't care for IDisposable. It seems like a cheap hack around the lack of deterministic destructors.
Friday, February 22, 2008 12:06:01 AM UTC
"'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 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.
Friday, February 22, 2008 12:12:50 AM UTC
"I don't care for IDisposable. It seems like a cheap hack around the lack of deterministic destructors."

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.
Friday, February 22, 2008 2:48:39 AM UTC
> First, how can you not like a project named after Duct Tape.

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.
Friday, February 22, 2008 3:09:09 AM UTC
But you can use the power of deterministic destructors to make standard cleanup code almost unnecessary in languages that support it. For example, using a C++ std::auto_ptr to wrap heap-allocated members of an object makes an explicit destructor for the object unnecessary.
Friday, February 22, 2008 3:53:03 AM UTC
"For example, using a C++ std::auto_ptr to wrap heap-allocated members of an object makes an explicit destructor for the object unnecessary."

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.
Friday, February 22, 2008 4:08:01 AM UTC
Depending on the application, it might take more housekeeping to implement IDisposable and set up Using blocks for a bunch of resource-controlling objects. But I won't press the point after reading xkcd tonight :)
Friday, February 22, 2008 2:41:45 PM UTC
What is that last line for?

autoReleasePool = IntPtr.Zero;
Friday, February 22, 2008 4:26:41 PM UTC
"What is that last line for?"

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.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.