Gavin Pugh - A Videogame Programming Blog

XNA/C# – StringBuilder changes in XNA 4.0

21 December, 2010 at 11:00am | XNA / C#


StringBuilder

So yeah, this is another post detailing a change made in the latest version of XNA.

This one was inspired by some comments on a previous blog post of mine: “StringBuilder to String with no garbage”. A developer named Matt, who was targeting Windows 7 Phones, pointed out that this particular trick with reflection was not working. He’d get a ‘FieldAccessException’ when attempting to access the internal private System.String object.

I’d just got round to installing the Windows Phone stuff to check this out, and indeed this is the case. I tried various different flags in the GetField() call, but had no luck in getting it to work. I tried setting up a trivial case with my own class containing a private ‘System.String’ and had the exact same problem.

So for whatever reason Windows Phone’s Compact Framework doesn’t allow access to private members via the GetValue() reflection call. In my aforementioned test with my own custom class, public members worked fine. In both cases the GetField() method was all good, it was just the GetValue() that throws ‘FieldAccessException’ in the case of private members.

Well… That’s a bit of a fly in the ointment right there. 🙁

Windows PC: A totally new version of StringBuilder?

Upon further inspection with the new XNA, I saw that the Windows setup had changed too. The StringBuilder object contents had changed completely.

For reference, here’s what I’ll call ‘version one’ of the StringBuilder. The one we’re used to from good ‘ol XNA 3.1:


StringBuilder_v1
StringBuilder version one

Here’s ‘version two’:


StringBuilder_v2
StringBuilder version two

Very, very different.

This new version maintains a series of chunks. As chunks are added it actually ends up linking together multiple new StringBuilder objects. The member ‘m_ChunkPrevious’ is this link.

From checking out the XNA 4.0 platforms, only the PC Windows build target uses this new StringBuilder. It could be argued that given that the GC performs well on PC that you need not use any tricks to grab internal strings anyway.

If you’re desperate though, I did come up with a crude method of grabbing the internal string without generating garbage. Be warned though, it’s far uglier than the previous ‘3.1’ method I had.

Here goes:

const int MAX_STRING_SIZE = 64;
 
StringBuilder my_string_builder;
char[] my_char_array;
string my_dest_string = new string(' ', MAX_STRING_SIZE);
 
public static unsafe void CopyIntoString( string dest_string, char[] char_buffer, int length )
{
    System.Diagnostics.Debug.Assert( dest_string.Length >= length );
 
    unsafe
    {
        fixed ( char* dest_fixed = dest_string )
        {
                // Copy in the string data
                for ( int i = 0; i < length; i++ )
                {
                    dest_fixed[i] = char_buffer[i];
                }
 
                // NULL terminate the dest string
                if ( length < dest_string.Length )
                {
                    dest_fixed[length] = (char)0;
                }
        }
    }
}
 
public void PrepareSB()
{            
    my_string_builder = new StringBuilder(MAX_STRING_SIZE, MAX_STRING_SIZE);
 
    // Ensure this StringBuilder only has one chunk
    StringBuilder sanity_check = (StringBuilder)my_string_builder.GetType().GetField(
        "m_ChunkPrevious", BindingFlags.NonPublic | BindingFlags.Instance )
        .GetValue(my_string_builder);
    System.Diagnostics.Debug.Assert( sanity_check == null );
 
    // Grab the char array from the StringBuilder
    my_char_array = (char[])my_string_builder.GetType().GetField(
        "m_ChunkChars", BindingFlags.NonPublic | BindingFlags.Instance )
        .GetValue(my_string_builder);
}
 
//! This function can be called iteratively and generates no garbage
public void TestSB()
{
    // Put something in there
    my_string_builder.Length = 0;
    my_string_builder.Append("Hello World!");
 
    // Copy into our dest string
    CopyIntoString(my_dest_string, my_char_array, my_string_builder.Length);
 
    // Note this dest string is a little odd, the NULL terminator shows in the 
    // debugger with all the spaces after it.
    // On use of the string though in code, I'm seeing the correct amount of 
    // characters used. It stops at the NULL.
}

My eyes! 🙂

It works… But really; I wouldn’t touch this monstrosity.
 


 

Summary by Version and Platform

 

So what follows here is a summary of the version of StringBuilder used on each platform, under both XNA 3.1 and 4.0. I also note the version of ‘mscorlib’ as listed in the project references section; this is the assembly which contains the System.Text.StringBuilder class.

Visual Studio 2008, and XNA v3.1

Windows

mscorlib v2.0.0.0, Runtime Version v2.0.50727
Uses StringBuilder version one. The technique detailed here works fine.

Xbox 360

mscorlib v3.5.0.0, Runtime Version v2.0.50727
Uses StringBuilder version one. The technique detailed here works fine.

Visual Studio 2010, and XNA v4.0

Windows Phone 7

mscorlib v2.0.5.0, Runtime Version v2.0.50727
Uses StringBuilder version one. Unfortunately reflection access to the internal System.String is disallowed. You’re stuck with using ToString() and incurring garbage.

Windows

mscorlib v4.0.0.0, Runtime Version v4.0.30319
Uses StringBuilder version two. This new implementation of StringBuilder requires a convoluted way of converting the StringBuilder to a System.String, without generating garbage. I’d say incurring the garbage hit on PC is preferable to using the hacky stuff I’ve published in this post.

Xbox 360

mscorlib v2.0.5.0, Runtime Version v2.0.50727
Uses StringBuilder version one. The technique detailed here works fine.

Oddly enough the main version number dropped to v2.0.5.0, from v3.5.0.0. A little confusing. The versions though match the Windows Phone 7 assemblies exactly.

Conclusions

So, under XNA 4.0 you’re out of luck on Windows Phone 7 unfortunately. Please let me know though if this isn’t the case, if you find some other way of getting round it please drop a comment on this post.

For Xbox 360 we’re fortunately still good to use the same methods which worked under XNA 3.1.

It’s pretty much inevitable though that in future both Phone 7 and Xbox 360 are bumped up to the new .NET StringBuilder class, which Windows currently uses. Possibly even Xbox 360 may get the same reflection shackles that Phone 7 has now too.

Can only hope that when we get a new Compact Framework, it comes with a shiny new generational garbage collector implementation. 🙂

References

Comments

Comment from Dave
Time: December 22, 2010, 1:14 pm

I was just converting my last game to XNA GS4 and had the problem you’re adressing here (I used all your awesome no garbage tricks). I don’t know however if I’ll use this one because it’s not really clean and handy, having to store a char[] too.
It’s really too bad that this feature is not included directly in the XNA SDK.

Pingback from No more memory allocation! | Michael Pullman Games
Time: February 19, 2011, 11:23 pm

[…] which apparently doesn’t work on XNA 4.0 anymore.  Fortunately, he did come up with another way– tap the internal string directly using unsafe code/pointers.  I personally think it’s […]

Comment from vexe
Time: July 12, 2015, 5:18 am

Hey thanks for the article! Thought I’d share my solution http://forum.unity3d.com/threads/gstring-gc-free-string-for-unity.338588/

Write a comment



 (not shown when published)



 (comments will appear after moderation)

*