Wednesday, December 13, 2006 2:59 PM bart

Windows Vista - Exploring the Windows System Assessment Tool (WinSAT) API in C# (some reactions)

Yesterday I published a blog post about the WinSAT API in Windows Vista. It's always great to see others referring to posts and providing comments and suggestions, as this was the case with However, a few reactions on the reactions:

I really distaste the idea of "unsafe" code unless there is absolutely no way around using it... and even then I'd probably choose to implement a solution using C/C++ if that was the case.

What unsafe code is concerned, if you know what you're doing there's no real problem but I agree it's better to avoid it whenever you can. I didn't want to go through the whole burden of explaining GCHandle, pinning, etc and for sake of the demo I just took advantage of some simple pointer stuff. So, I certainly wouldn't exchange one line of unsafe code for a complete makeover in C/C++ unmanaged code. In that case I wouldn't have written the article at all since the Windows SDK has all the info C/C++ devs need. Nevertheless, thanks for pointing to the alternative using GCHandle.Alloc. I won't ever pronounce the word unsafe code anymore :p.

I also didn't like the fact that he was using GetEnumerator instead of using the much more friendly foreach functionality and because he was using GetEnumerator, he was having to do some unnecessary casting in the process.

Then tell me how to foreach over an enumeration without casting. Okay, I could just have duplicated the code five times, like this, which would be more explicit:

IProvideWinSATAssessmentInfo i; i = q.Info.GetAssessmentInfo(WINSAT_ASSESSMENT_TYPE.WINSAT_ASSESSMENT_CPU); Console.WriteLine(format, i.Title, i.Description, i.Score); i = q.Info.GetAssessmentInfo(WINSAT_ASSESSMENT_TYPE.WINSAT_ASSESSMENT_D3D); Console.WriteLine(format, i.Title, i.Description, i.Score); i = q.Info.GetAssessmentInfo(WINSAT_ASSESSMENT_TYPE.WINSAT_ASSESSMENT_DISK); Console.WriteLine(format, i.Title, i.Description, i.Score); i = q.Info.GetAssessmentInfo(WINSAT_ASSESSMENT_TYPE.WINSAT_ASSESSMENT_GRAPHICS); Console.WriteLine(format, i.Title, i.Description, i.Score); i = q.Info.GetAssessmentInfo(WINSAT_ASSESSMENT_TYPE.WINSAT_ASSESSMENT_MEMORY); Console.WriteLine(format, i.Title, i.Description, i.Score);

In the end we're talking about 5 casts; no big deal for a demo app. Difference in perf? Maybe 5 to 10 ms I guess; the relatively slow API calls overshadow the whole thing. Or maybe the source of the confusion is the classic misconception that the method A1 in the following piece of code would be more efficient than method A2:

private static void A1(CQueryWinSATClass q, string format) { foreach (WINSAT_ASSESSMENT_TYPE wat in Enum.GetValues(typeof(WINSAT_ASSESSMENT_TYPE))) { IProvideWinSATAssessmentInfo i = q.Info.GetAssessmentInfo(wat); Console.WriteLine(format, i.Title, i.Description, i.Score); } } private static void A2(CQueryWinSATClass q, string format) { IEnumerator e = Enum.GetValues(typeof(WINSAT_ASSESSMENT_TYPE)).GetEnumerator(); while (e.MoveNext()) { IProvideWinSATAssessmentInfo i = q.Info.GetAssessmentInfo((WINSAT_ASSESSMENT_TYPE)e.Current); Console.WriteLine(format, i.Title, i.Description, i.Score); } }

The point is that GetValues returns an Array, which contains elements of type System.Object, which in case of enums introduces boxing to occur. What happens in A1 is that the foreach loop is translated in similar logic as in A2, which is iterating over the (non-generic) IEnumerator returned by calling System.Array::GetEnumerator. Inside the loop, the IEnumerator's Current property will be retrieved and unboxed to the enum type. Because my blog readers like IL, I've included this analysis below. For A1 take a look at lines IL_001a and IL_001f, for A2 look at IL_0020 and IL_0025.

.method private hidebysig static void A1(class [Interop.WINSATLib]WINSATLib.CQueryWinSATClass q, string format) cil managed { // Code size 122 (0x7a) .maxstack 4 .locals init ([0] valuetype [Interop.WINSATLib]WINSATLib.WINSAT_ASSESSMENT_TYPE wat, [1] class [Interop.WINSATLib]WINSATLib.IProvideWinSATAssessmentInfo i, [2] class [mscorlib]System.Collections.IEnumerator CS$5$0000, [3] bool CS$4$0001, [4] class [mscorlib]System.IDisposable CS$0$0002) IL_0000: nop IL_0001: nop IL_0002: ldtoken [Interop.WINSATLib]WINSATLib.WINSAT_ASSESSMENT_TYPE IL_0007: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_000c: call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type) IL_0011: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator() IL_0016: stloc.2 .try { IL_0017: br.s IL_0052 IL_0019: ldloc.2 IL_001a: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current() IL_001f: unbox.any [Interop.WINSATLib]WINSATLib.WINSAT_ASSESSMENT_TYPE IL_0024: stloc.0 IL_0025: nop IL_0026: ldarg.0 IL_0027: callvirt instance class [Interop.WINSATLib]WINSATLib.IProvideWinSATResultsInfo [Interop.WINSATLib]WINSATLib.CQueryWinSATClass::get_Info() IL_002c: ldloc.0 IL_002d: callvirt instance class [Interop.WINSATLib]WINSATLib.IProvideWinSATAssessmentInfo [Interop.WINSATLib]WINSATLib.IProvideWinSATResultsInfo::GetAssessmentInfo(valuetype [Interop.WINSATLib]WINSATLib.WINSAT_ASSESSMENT_TYPE) IL_0032: stloc.1 IL_0033: ldarg.1 IL_0034: ldloc.1 IL_0035: callvirt instance string [Interop.WINSATLib]WINSATLib.IProvideWinSATAssessmentInfo::get_Title() IL_003a: ldloc.1 IL_003b: callvirt instance string [Interop.WINSATLib]WINSATLib.IProvideWinSATAssessmentInfo::get_Description() IL_0040: ldloc.1 IL_0041: callvirt instance float32 [Interop.WINSATLib]WINSATLib.IProvideWinSATAssessmentInfo::get_Score() IL_0046: box [mscorlib]System.Single IL_004b: call void [mscorlib]System.Console::WriteLine(string, object, object, object) IL_0050: nop IL_0051: nop IL_0052: ldloc.2 IL_0053: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() IL_0058: stloc.3 IL_0059: ldloc.3 IL_005a: brtrue.s IL_0019 IL_005c: leave.s IL_0078 } // end .try finally { IL_005e: ldloc.2 IL_005f: isinst [mscorlib]System.IDisposable IL_0064: stloc.s CS$0$0002 IL_0066: ldloc.s CS$0$0002 IL_0068: ldnull IL_0069: ceq IL_006b: stloc.3 IL_006c: ldloc.3 IL_006d: brtrue.s IL_0077 IL_006f: ldloc.s CS$0$0002 IL_0071: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0076: nop IL_0077: endfinally } // end handler IL_0078: nop IL_0079: ret } // end of method Program::A1
.method private hidebysig static void A2(class [Interop.WINSATLib]WINSATLib.CQueryWinSATClass q, string format) cil managed { // Code size 90 (0x5a) .maxstack 4 .locals init ([0] class [mscorlib]System.Collections.IEnumerator e, [1] class [Interop.WINSATLib]WINSATLib.IProvideWinSATAssessmentInfo i, [2] bool CS$4$0000) IL_0000: nop IL_0001: ldtoken [Interop.WINSATLib]WINSATLib.WINSAT_ASSESSMENT_TYPE IL_0006: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_000b: call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type) IL_0010: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator() IL_0015: stloc.0 IL_0016: br.s IL_004f IL_0018: nop IL_0019: ldarg.0 IL_001a: callvirt instance class [Interop.WINSATLib]WINSATLib.IProvideWinSATResultsInfo [Interop.WINSATLib]WINSATLib.CQueryWinSATClass::get_Info() IL_001f: ldloc.0 IL_0020: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current() IL_0025: unbox.any [Interop.WINSATLib]WINSATLib.WINSAT_ASSESSMENT_TYPE IL_002a: callvirt instance class [Interop.WINSATLib]WINSATLib.IProvideWinSATAssessmentInfo [Interop.WINSATLib]WINSATLib.IProvideWinSATResultsInfo::GetAssessmentInfo(valuetype [Interop.WINSATLib]WINSATLib.WINSAT_ASSESSMENT_TYPE) IL_002f: stloc.1 IL_0030: ldarg.1 IL_0031: ldloc.1 IL_0032: callvirt instance string [Interop.WINSATLib]WINSATLib.IProvideWinSATAssessmentInfo::get_Title() IL_0037: ldloc.1 IL_0038: callvirt instance string [Interop.WINSATLib]WINSATLib.IProvideWinSATAssessmentInfo::get_Description() IL_003d: ldloc.1 IL_003e: callvirt instance float32 [Interop.WINSATLib]WINSATLib.IProvideWinSATAssessmentInfo::get_Score() IL_0043: box [mscorlib]System.Single IL_0048: call void [mscorlib]System.Console::WriteLine(string, object, object, object) IL_004d: nop IL_004e: nop IL_004f: ldloc.0 IL_0050: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() IL_0055: stloc.2 IL_0056: ldloc.2 IL_0057: brtrue.s IL_0018 IL_0059: ret } // end of method Program::A2

As the matter in fact, the explicit enumeration logic using a while-loop might look less elegant, but it makes the casting explicit which is otherwise introduced by compiler magic. By the way, don't worry about the try-finally block in A1 which doesn't provide us with more value, since System.Array isn't IDisposable. (Btw, if you really want to optimize somewhat, take q.Info out of the loop.)

Finally, implementing this as a console application just seemed wrong somehow; especially since he was still showing a windows form from the console app to demonstrate the ability to garner the Windows Vista generated image representing the base score.

I always intend to make things as simple as possible (cf. Einstein's famous quote mentioned in this post), in this case the console app just does the trick; everyone knows you can put the whole stuff in a WinForms app too in a completely similar manner. The score display was the only aspect that required WinForms and the orginal implementation just invoked the Save method on the bitmap to store it as a .bmp file, like this:

if (t != IntPtr.Zero) Bitmap.FromHbitmap(t).Save("WinSATDemo.bmp");

It would have been even shorter, I agree. Ultimately, I decided to use WinForms instead to display the picture. For me, the focus is on retrieving the numbers, not cloning what's already available in Windows to display the scores. Lots of WinForms code hide the real intention of the app in quite some cases. Anyway, just my .314 cents.

(PS: Sorry I had to delay the real technical post of today, but I felt I had to put a reaction online.) | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:


# re: Windows Vista - Exploring the Windows System Assessment Tool (WinSAT) API in C# (some reactions)

Thursday, December 14, 2006 7:49 AM by Greg Duncan

I read your original article when first posted and then the AddressOf one today... It's tone put me off a little, but IMHO I think it was unintentional. It's a classic "can't read/see body language or emotional queues so the text comes across a little harsher than intended" thing. I am glad you responded professionally to the points, sticking to the technical issues and discussing you thought process (no one likes flame wars... cause no one wins and we all lose) In any case, one of the harder things in coding (for me at least) is not knowing what I don't know. Your code samples, projects and posts provide a great road map to those of us who are trying to discover what is yet unknown to us. And of course you can't be everything to everyone, nor should you try. You can only do what you can do. And what you do is a great deal... All I'm trying to say is you rock. And what you do and post is appreciated. And to thank you... Take care, Greg