2010-02-23

Crazy Saturday

My MCPD logo has been updated to include my new ASP.NET and Enterprise certifications. They were the result of a Crazy Saturday.

This last Saturday, February 20th, there was a fundraising dinner in Grand Rapids for the Constitution Party. Since we were going to be in town anyway, we figured I should go ahead and take a couple of certification exams. (Grand Rapids is ~3.5 hours away from our house, and that's where the closest testing center is). Due to various difficulties, I had almost no time to prepare for the exams.

Due to a scheduling bug on Prometric's website, I was able to schedule overlapping exams. I originally planned to take three exams (just getting the Enterprise certification), but trimmed it down to two because I didn't want the stress. Leaving the house at 5:30am, we got to the testing center right when they opened at 9:30.

After taking 70-561 (TS: ADO.NET) and 70-562 (TS: ASP.NET), I still had a bit of time. The "expected time" for those exams was 3:45 and 3:30, respectively, but it was only 11:45 when I finished (including a break for breakfast).

The test administrators were really nice and suggested that I "quick-test" another one... if I could help them figure out how to do it using their Prometric control panel application. I decided to try to take both remaining tests, which would save me a trip down in March. The only catch was that I had to finish the next test in a very short time, because the last test had to start by 1pm (it was 3 hours long and the testing center closed at 4:00).

Fortunately, I finished 70-565 (PRO: Enterprise) in about 45 minutes, with just enough time to schedule 70-564 (PRO: ASP.NET). I had a full three hours for that exam, but ended up leaving at around 1:45pm. By that time, I was hungry. :)

My wife was pleased when I told her that I had taken four tests instead of two that day, meaning that March 13th would now be spent at home instead of in Grand Rapids. I was a little wiped out during the fundraising dinner, though.

Four exams; total "expected time" of 13:15; completed in a little over four hours; earning two MCPD certifications. Crazy Saturday!

Even if my previous experience hadn't taught me, this last weekend sure would have: Jesus Christ is an awesome God to serve! He assisted me in each step of this process, and I encourage any of my readers to get to know Him! If you're interested in knowing Him, the first step is getting saved. Feel free to contact me if you have any questions!

2010-02-17

Q&A: Should I Set Variables to Null to Assist Garbage Collection?

This is a common question with rather complex reasoning behind the answer.

First off, setting a variable to null to assist garbage collection is different than setting a variable to null to indicate state. It's always proper to use "null" as a state indicator (e.g., the CallbackContext class has a field which is set to null to indicate the context is invalid).

Secondly, the "variable" being set to null may be either a field (possibly via a property) or a local variable. Local variables include method parameters. A field may be a static field or an instance field.

This blog entry is only concerned with the question "Should I set variables to null to assist garbage collection?" and will consider each type of "variable".

The Short Answer, for the Impatient

Yes, if the variable is a static field, or if you are writing an enumerable method (using yield return) or an asynchronous method (using async and await). Otherwise, no.

This means that in regular methods (non-enumerable and non-asynchronous), you do not set local variables, method parameters, or instance fields to null.

(Even if you're implementing IDisposable.Dispose, you still should not set variables to null).

The Longer Answer

  • Static fields should be set to null when they are no longer needed - unless the process is shutting down, in which case setting static fields to null is unnecessary.
  • Local variables hardly ever need to be set to null. There is only one exception:
    • It may be beneficial to set local variables to null if running on a non-Microsoft CLR.
  • Instance fields hardly ever need to be set to null. There is only one exception:
    • An instance field may be set to null if the referencing object is expected to outlive the referenced object.
    • [Note that the semi-common practice of setting instance fields to null in IDisposable.Dispose does not meet this test, and should not be encouraged].

There is a special consideration for enumerable and asynchronous methods. When compiling these methods, the compiler transforms the method into its own object. As a result, all local variables (including method parameters) are actually instance fields. If the "method object" is expected to outlive any of the objects referred to by those variables, then they should be set to null.

In conclusion: generally speaking, setting variables to null to help the garbage collector is not recommended. If it is deemed necessary, then an unusual condition exists and it should be carefully documented in the code.

The rest of this post deals with the reasoning behind this recommendation.

Required Reading

Most of this post relies heavily on Jeffrey Richter's awesome book CLR via C#. Unfortunately, even though the 3rd edition is out, I only have the 2nd; so all page numbers in this blog post are for the 2nd edition. The section "The Garbage Collection Algorithm" (pg 461) covers GC in general, and the section "Garbage Collections and Debugging" (pg 465) is particularly useful when considering this question.

Determining Root Objects

The garbage collector is based on a "mark and sweep" design, starting from a set of root objects and walking any nested references to determine which objects are still in use. Any objects not marked are declared unused and become eligible for garbage collection [this is a simplification, but it's the general idea]. Logically, all the marked objects form a "graph" of live objects.

The idea behind "setting variables to null" is that it would help the garbage collector to detect that the referenced object is no longer used. Before we can determine if this truly is helpful or not, we must first determine what constitutes a "root object".

First: any static field is a root object. That's the easy part (we'll handle static fields in more detail later).

Instance fields are used to build the graph of referenced objects, so it's possible that setting an instance field to null may "trim" objects from the graph (we'll handle instance fields in more detail later, too).

Method-local variables (including parameters and the implicit "this" reference) are much tricker: they are sometimes root objects.

As described in CLR via C#, the JIT compiler for a method will determine which native code blocks reference which variables by building a "root table" for the method. It's important to note that this table is quite accurate (though not 100% accurate - it may "hold onto" references slightly longer than necessary if it simplifies the table). Examine the simple code below:

static object CheckType(object a, Type b)
{
  Type t = a.GetType();
  // The object referenced by "a" may be eligible for GC here
  if (t == b)
  {
    Console.WriteLine("match!");
    return b;
  }
  else
  {
    Console.WriteLine("no match...");
    return null;
  }
}

The object referenced by "a" may be garbage collected as noted by the comments in this method (if it is not referenced elsewhere, of course). This is because the method's root table would declare that this method uses the "a" variable just for the code doing the "a.GetType()".

When the JIT Compiler Behaves Differently (Debug)

There are two situations where the JIT compiler will artificially extend the lifetime of local variables to the end of the method. The first is when the code is compiled without optimizations and running under the debugger. The second is when the code is compiled with full debug information.

If either of these situations is detected, the JIT compiler will change how it builds the root table so that in our example above, the object referenced by "a" cannot be eligible for garbage collection at least until the method returns.

By default, VS includes full debug information in "Debug" configuration builds but only includes pdb debug information in "Release" configuration builds. This means that the garbage collector does work differently when running "Debug" configuration code, even when run outside the debugger.

When the JIT Compiler Behaves Differently (Release)

An interesting behavior of the JIT compiler is that when optimizations are enabled (by default in "Release" configurations), one of the optimizations it performs is removing code that sets a local variable to null.

It is rather ironic that some people religiously scatter "a = null;" throughout their methods, only to have them completely removed by the runtime.

By this point, it should be obvious that setting local variables to null (with the goal of helping the GC) is not beneficial. This practice only complicates the code and provides no help to the GC since it is removed anyway.

Other CLRs and JIT Compilers

The above description of JIT compiler behavior is only applicable to the current Microsoft implementation. Mono, in particular, does not build a root table when JIT-compiling a method (it treats all local variables as referenced until the end of the method).

Because of this different implementation, it may be useful to set local variables to null if the code will be running on Mono.

Static Fields

Static fields are always root objects, so they are always considered "alive" by the garbage collector. If a static field references an object that is no longer needed, it should be set to null so that the garbage collector will treat it as eligible for collection.

Setting static fields to null is meaningless if the entire process is shutting down. The entire heap is about to be garbage collected at that point, including all the root objects.

Instance Fields

An instance field is how one object references another object. The garbage collector uses instance fields to build its graph of objects that are referenced (and thus uneligible for garbage collection).

Usually, when one object becomes eligible for garbage collection, it simultaneously makes all of its owned objects eligible for garbage collection as well. This happens perfectly naturally, without the need to set any instance fields to null.

Setting instance fields to null does not help the garbage collector in this case, since it marks the referenced objects. The fact that one unreferenced object no longer references another unreferenced object has absolutely no bearing on how the GC builds its graph.

However, there is one case where setting an instance field to null would help the garbage collector: if the owned (child) object is no longer necessary but the owning (parent) object will still be referenced for some time. In this case, setting the parent object's instance field to null would make the child object eligible for garbage collection. Note that this is a rare situation.

In particular, setting instance fields to null in an IDisposable.Dispose implementation is unnecessary. The parent object is being disposed; it cannot expect to be referenced much longer, and so it will not significantly outlive its child object(s).

Conclusion

Static fields; that's about it. Anything else is a waste of time.

2010-02-16

Interop: Multidimensional Arrays of Characters in a Structure

Yesterday an interesting problem was brought up on the MSDN forums. An unmanaged structure had a form like this:

struct MyStruct
{
  int id;
  char names[6][25];
};

Each structure has 6 strings of up to 25 characters each. Marshaling a single "flattened" string in a structure is not difficult (UnmanagedType.ByValTStr with SizeConst), and marshaling a "flattened" array of simple types in a structure is likewise not difficult (UnmanagedType.ByValArray with SizeConst and optionally ArraySubType). However, marshaling a flattened array of flattened strings is not exactly straightforward (there is no "ArraySubTypeSizeConst" option).

The answer is to split off the "25 character string" type into its own structure (containing a single flattened string), and define a flattened array of those structures in the parent structure, as such:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MyStruct
{
    public void Init()
    {
        this.names = new StringSizeConst25AsString[6];
    }

    public int id;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public StringSizeConst25AsString[] names;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct StringSizeConst25AsString
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 25)]
        private string Value;

        public static implicit operator string(StringSizeConst25AsString source)
        {
            return source.Value;
        }

        public static implicit operator StringSizeConst25AsString(string source)
        {
            // Note that longer strings would be silently truncated
            //  if we didn't explicitly check this.
            if (source.Length >= 25)
                throw new Exception("String too large for field: " + source);

            return new StringSizeConst25AsString { Value = source };
        }
    }
}

(The implicit conversions on the inner structure are for convenience; note that the default marshaling will silently truncate string values that are more than 24 characters).

The inner structure "StringSizeConst25AsString" marshals its string as a 25-character array, and the outer structure "MyStruct" marshals an array of the inner structures. Both of them end up getting flattened correctly into a single multidimensional unmanaged character array.

If we have an unmanaged function as such:

// ByValArrayOfStrings.h:
extern "C" __declspec(dllexport) void AddMultipleNames(const MyStruct* DSNames);

// ByValArrayOfStrings.cpp:
#include <string>
#include <sstream>

__declspec(dllexport) void AddMultipleNames(const MyStruct* DSNames)
{
 {
  std::ostringstream out;
  out << "DSNames->id: " << DSNames->id;
  OutputDebugStringA(out.str().c_str());
 }

 for (int i = 0; i != 6; ++i)
 {
  std::ostringstream out;
  out << "DSNames->names[" << i << "]: " << DSNames->names[i];
  OutputDebugStringA(out.str().c_str());
 }
}

Then we can use the managed interop definitions above like this:

[DllImport("ByValArrayOfStrings.dll", CharSet = CharSet.Ansi)]
static extern void AddMultipleNames(ref MyStruct DSNames);

private void button1_Click(object sender, EventArgs e)
{
    try
    {
        MyStruct tmp = new MyStruct();
        tmp.Init();
        tmp.id = 17;
        tmp.names[0] = "Hi";
        tmp.names[2] = "There";
        tmp.names[3] = "123456789012345678901234";

        // The following assignment would throw
        //tmp.names[4] = "1234567890123456789012345";
        tmp.names[5] = "x";

        AddMultipleNames(ref tmp);
    }
    catch (Exception ex)
    {
        MessageBox.Show("[" + ex.GetType().Name + "] " + ex.Message);
    }
}

And this would cause the unmanaged DLL to send to its debug output:

DSNames->id: 17
DSNames->names[0]: Hi
DSNames->names[1]: 
DSNames->names[2]: There
DSNames->names[3]: 123456789012345678901234
DSNames->names[4]: 
DSNames->names[5]: x

Non-Null-Terminated Strings

The above solution works well if each of the strings in the unmanaged structure are null-terminated. There are some APIs, however, which work with implicitly-terminated strings. It is possible that an unmanaged function may treat these strings as having an implicit length of 25 characters.

In this case, string marshaling cannot be used in the managed code. The above solution can be modified to marshal an array of characters instead:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MyStruct
{
    public void Init()
    {
        this.names = new StringSizeConst25AsCharArray[6];
    }

    public int id;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public StringSizeConst25AsCharArray[] names;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct StringSizeConst25AsCharArray
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 25)]
        private char[] Value;

        public static implicit operator string(StringSizeConst25AsCharArray source)
        {
            return new string(source.Value);
        }

        public static implicit operator StringSizeConst25AsCharArray(string source)
        {
            if (source.Length > 25)
                throw new Exception("String too large for field: " + source);

            var ret = new StringSizeConst25AsCharArray() { Value = new char[25] };
            Array.Copy(source.ToCharArray(), ret.Value, source.Length);
            return ret;
        }
    }
}

This solution allows sending a 25-character, non-null-terminated string as a member of the unmanaged string array:

private void button1_Click(object sender, EventArgs e)
{
    try
    {
        MyStruct tmp = new MyStruct();
        tmp.Init();
        tmp.id = 17;
        tmp.names[0] = "Hi";
        tmp.names[2] = "There";
        tmp.names[3] = "123456789012345678901234";

        // The following assignment would throw
        //tmp.names[4] = "12345678901234567890123456";
        tmp.names[4] = "1234567890123456789012345";
        tmp.names[5] = "x";

        AddMultipleNames(ref tmp);
    }
    catch (Exception ex)
    {
        MessageBox.Show("[" + ex.GetType().Name + "] " + ex.Message);
    }
}

Which produces this debug output:

DSNames->id: 17
DSNames->names[0]: Hi
DSNames->names[1]: 
DSNames->names[2]: There
DSNames->names[3]: 123456789012345678901234
DSNames->names[4]: 1234567890123456789012345x
DSNames->names[5]: x

Note that our unmanaged function is still interpreting the strings as null-terminated, and we're marshaling them as implicitly-terminated. This is why entry [4] above "spills over" and picks up entry [5]. If the unmanaged function actually interpreted the strings as having a length of 25 (or a maximum length of 25), then this "spill over" would not happen.

How to Implement IDisposable and Finalizers: Matrix and Flowchart

I've decided to split off the matrix and flowchart from my original How to Implement IDisposable and Finalizers: 3 Simple Rules post, and put them into their own post here.

Summary: A Decision Matrix for IDisposable and Finalize

The flowchart and decision matrix below use these terms:

  • Managed resource - an instance of any class implementing IDisposable.
  • Unmanaged resource - a handle of some type representing a resource that requires a p/Invoke function to "free" the handle; OR an IntPtr allocated by one of the "allocation" functions in the Marshal class; OR a GCHandle or its equivalent IntPtr.
  • Own - a resource is "owned" by a class if the lifetime of the resource is scoped to an instance of that class. Some classes share resources but do not own them.
Decision Matrix as a Table
Decision Matrix for IDisposable and Finalize
Class does not own managed resourcesClass owns at least one managed resource
Class does not own unmanaged resourcesApply Rule 1: no IDisposable or FinalizerApply Rule 2: IDisposable but no Finalizer
Class owns one unmanaged resourceApply Rule 3: both IDisposable and FinalizerRefactor
Class owns more than one unmanaged resourceRefactorRefactor
Decision Matrix as a Flowchart

Step 1 - Does the class own a managed resource? If not, go to Step 2.

Step 1.1 - Does the class own an unmanaged resource? If it does, refactor the class into two classes, so that any class owns either unmanaged or managed resources, but not both. Then apply this flowchart to each resulting class.

Step 1.2 - The class owns at least one managed resource and no unmanaged resources. Apply Rule 2 to the class, and you're done.

Step 2 - Does the class own an unmanaged resource? If not, then apply Rule 1 to the class, and you're done.

Step 2.1 - Does the class own more than one unmanaged resource? If it does, refactor the class so that any class only owns at most one unmanaged resource, and then apply this flowchart to each of the resulting classes.

Step 2.2 - The class owns exactly one unmanaged resource and no managed resources. Apply Rule 3 to the class.

2010-02-15

Evolution of Framework Design Guidelines?

Joe Duffy recently blogged about one of his comments in the book Framework Design Guidelines. His comment is rather brief in the book, but he expounds on it nicely in his blog: essentially, he suggests defining only the minimum set of operations in an interface, and using extension methods to provide "default" implementations of other "interface methods". This is the approach that I've been calling extension-based types. He also calls out the problems with extension properties and the non-virtual nature of overriding extension functions (these problems are explored on my earlier blog post as well).

I've hesitated mentioning the book in the past, because any review coming from me would be a bit harsh... To be blunt, I think a good amount of the book is obviously correct (in some cases, painfully obvious), and a good amount is flat-out bad advice. If you want an explaination of why the first version of the BCL was designed the way it was, then this is a decent reference. However, the book suggests that its guidelines should be accepted for other general .NET libraries, and there I must disagree. Microsoft themselves have evolved far from many of the guidelines in this book.

That said, there are a few gems. I would say that it's worth owning; just keep in mind while reading that it is not the gospel. I'd like to see a third edition, updated with the current Microsoft practices and spending more than a couple sentences on newer concepts such as extension-based types.

ASP.NET Slides Available

Last week I gave my first ASP.NET-oriented presentation, "ASP.NET MVC, Silverlight, and jQuery as Friends" (slides available here). There's a lot of setup tips to get those technologies working together (using VS2008 SP1).

I used my church website as an example of a static site I recently transitioned to ASP.NET MVC. The most interesting element of this website is the Silverlight menu. Just like CSS/JavaScript menus, it builds itself dynamically from <ul>'s containing <a>'s. In the talk I also covered less common aspects in this site: using ASP.NET routing to ensure the old links to *.html pages stay functional, and how to build your own HTML helpers (and why they aren't as useful as one might think).

All in all, it was much better than my first talk. :)

2010-02-06

Nito.Assimilation - the Assembly Manipulator

There's a growing need for a nicer "cross-platform" story. The Nito.Linq library has temporarily stalled, due to the complexity of the project with regards to multiple platforms. Currently, we support:

  • .NET 3.5 SP1 (Client profile compatible) with Rx
  • .NET 3.5 SP1 (Client profile compatible) without Rx
  • Compact Framework 3.5
  • Silverlight 3 with Rx
  • Silverlight 3 without Rx

However, we're going to have to add .NET 4.0 Beta 2 to the mix (with and without Rx), and other targets will only continue to add to what is already a mess. The current source code situation is not difficult, but Visual Studio is having an absolute fit.

Microsoft's Rx team had a similar problem; they have a single code base to run on a number of different platforms. Their solution was ingenious: they developed some in-house tools to manage the definitions of these various platforms and retarget an already-existing assembly.

Nito.Assimilation is the open-source equivalent. It's intended to be a tool for .NET library writers needing to target multiple versions/editions of the framework. The current roadmap is to provide a few primary tasks:

  1. Creating metadata assemblies from reference assemblies.
  2. Combining multiple metadata assemblies into a multitarget metadata assembly.
  3. Providing assembly targeting (converting a multitargeted library assembly into a targeted assembly). A "multitargeted library assembly" is one that has multitarget metadata assemblies as its assembly references; and a "targeted assembly" is an assembly that has been bound to a specific platform.
  4. (Possibly) Defining a standard means to define "profiles", "targets", and "multitargets".
  5. (Possibly) Providing assembly retargeting (converting a targeted assembly into a multitargeted library assembly).

The terminology can certainly get confusing! I'm still brainstorming for better words.

Anyway, Nito.Assimilation has reached its first milestone: it is capable of creating metadata assemblies from regular assemblies (along with XML documentation, of course). It is currently included in the source code of the Nito.Linq library (though eventually it will probably become its own project). It's been successfully used to create "metadata profiles" of the .NET 3.5 SP1 Client profile and the .NET 3.5 Compact Framework. Applications built against these metadata assemblies have working IntelliSense and execute without problems (binding to the real assemblies at runtime).

Enjoy!

2010-02-01

jQuery hosted on Google's CDN with IntelliSense

ASP.NET MVC (which I'm using for my church's web site) comes packaged with jQuery; the appropriate JavaScript files are placed into the Scripts folder of the default MVC project. It is a good idea, though, to let Google (or Microsoft) host jQuery for you over their CDN (content delivery network).

However, you lose out on the cool jQuery IntelliSense! There are various workarounds to fix this, but most of them only succeed on VS2010 (which works much better with JavaScript IntelliSense). I'm still using VS2008; if you find yourself in the same boat, be sure to install KB958502 first. Then you can do this:

<%= "<script type='text/jscript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'></script>" %>
<% if (false) { %><script type="text/javascript" src="../../App_Data/jquery-1.3.2.js"></script><% } %>

It's a nice little trick to make the Visual Studio editor ignore the jQuery on Google's CDN (because it's injected as a string into the ASP.NET response stream), while ignoring the local file when executed.

You may notice that I've stuck my local jquery-1.3.2.js and jquery-1.3.2-vsdoc.js files into the App_Data directory of my project. This is just because it's easier to Publish the web site that way (they don't actually get copied). The code in this post will work for any local JavaScript file, regardless of its location.

Update (2010-02-03)

The original solution above used a comment block instead of an "if (false)" block:

<%= "<script type='text/jscript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'></script>" %>
<% /* %><script type="text/javascript" src="../../App_Data/jquery-1.3.2.js"></script><% */ %>

Unfortunately, this caused C# IntelliSense to fail! The new solution allows both C# and JavaScript IntelliSense.

Cross-browser Silverlight

I tried becoming a web developer back in the version 4 browser war days; my scars from that time made me swear off web page writing for years, until recently. In my mind, one of the greatest advantages of Silverlight is that it completely kills all that nasty browser compatibility stuff. However, when writing Silverlight controls that interact more heavily with HTML, cross-browser issues begin to rise up from the grave.

Partial URLs

Partial URLs are transformed by Internet Exporer into absolute URLs as they enter the object model. This can cause exceptions from the Uri constructor if you're only passing a single string argument. The workaround that I've opted to use is to always use the Uri constructor that takes a Uri and a string as arguments, and I pass in the current page's Uri as the first argument.

I recommend never using the Uri constructor taking a single string parameter; in the following example, the <a> item has an href attribute similar to "/", which is quietly transformed by IE into a full "http://www.tempuri.org/":

// Fails on Google Chrome
new Uri(item.GetAttribute("href"))

Instead, pass the document Uri as the context of the relative Uri string. If the second parameter is actually an absolute Uri (e.g., when running in Internet Explorer), the context Uri is ignored:

// Works on both Chrome and IE
new Uri(HtmlPage.Document.DocumentUri, item.GetAttribute("href"))

Text Content

Internet Explorer has an "innerText" attribute, while the DOM supports a "textContent" property. A simple extension method on HtmlElement suffices to get the text content of a node:

public static string GetTextContent(this HtmlElement htmlElement)
{
    string ret = htmlElement.GetProperty("textContent") as string;
    if (string.IsNullOrEmpty(ret))
    {
         ret = htmlElement.GetAttribute("innerText");
         if (ret == null)
         {
             return string.Empty;
         }
    }

    return ret;
}

DocumentReady

A long-standing bug in Internet Explorer (at least since IE6; still present in IE8) causes document.onreadystatechange to be fired incorrectly, and the standard DOMContentLoaded isn't suppored. This means that HtmlPage.Document.DocumentReady will fire when the document is not ready. This behavior has been discussed on a Silverlight forum.

I've also seen some situations where the jQuery ready handlers (and the window.onload handlers) will fire before the Silverlight control begins executing; this can happen if the page contains several images. To be safe, I recommend having both jQuery and Silverlight register "readyness", and the last one to register kicks off the initialization code.

This has a few steps; I'll be using as an example a Silverlight menu control I wrote that dynamically builds its menu from an unordered list in the HTML. First, the "initialization" code must be made a separate method in the Silverlight control (in this example, it's "BuildMenu"):

// In the Page constructor (there's only one page in this simple menu control):
HtmlPage.RegisterScriptableObject("SLMenu", this);

// Also defined as part of the Page class:
[ScriptableMember]
public void BuildMenu()

Define the "readyness" flags in plain, top-level JavaScript before the Silverlight control; this ensures that they're interpreted immediately:

// Top-level code; not in a function!
var silverlightLoaded = false;
var htmlLoaded = false;

Also define the JavaScript function that the Silverlight control should call in top-level JavaScript before the Silverlight control:

// Top-level code; not in a function!
MenuOnLoad = function() {
  if (!silverlightLoaded) {
    silverlightLoaded = true;
    if (htmlLoaded) {
      menuControl.Content.SLMenu.BuildMenu();
    }
  }
}

The code above is straightforward; it marks Silverlight as having loaded and then invokes the initialization code if the HTML has already loaded. The code run by the HTML when it loads is similar (shown here using jQuery, but window.onload could be used as well):

$(function() {
  if (!htmlLoaded) {
    htmlLoaded = true;
    if (silverlightLoaded) {
      menuControl.Content.SLMenu.BuildMenu();
    }
  }
});

Finally, the Silverlight control must invoke the JavaScript "MenuOnLoad" function when it is loaded. This must come after its registration with the browser:

// In the Page constructor (there's only one page in this simple menu control):
HtmlPage.RegisterScriptableObject("SLMenu", this);
HtmlPage.Window.Invoke("MenuOnLoad");

Conclusion

Of course, these few examples are just minor inconsistencies. I'm sure that many more ugly browser incompatibilities will become troublesome over the next few months.

The Silverlight menu used as the example above is live at this site: http://www.landmarkbaptist.ws/. If it doesn't work properly for you, please let me know! (I'm particularly interested if there are any holes in my initialization serialization logic).

I'll keep posting as I find more problems. :)