Recently in his blog, Charles Petzold wondered how anyone could write a book while working full time. When I did the first edition of my book, I didn’t have a job as I left NuMega to write it. Now that I have a job, I can tell you how much harder it is. Fortunately, things will be calming down here soon so I can really get a bunch of the writing done. Since I last wrote about the book gyrations, I’ve worked in China, Silicon Valley, and Redmond a couple or three times. Beijing was a blast, as always, and was the second time I’ve been this year. I love to do the international trips! This year I’ve been to India, England, Czech Republic (I promise to stay away from the Absinthe the next time), and the two China trips. It’s no wonder I had 100K airline miles by the end of May. Now that the world jet setting is done, I’ll get my rear in gear on the book.
In Redmond last week, we got to talking about all the myriad of new keystrokes in Visual Studio .NET 2005. It seems that every release adds another couple of hundred so it’s becoming overwhelming. As I’m a keyboard kind of guy, it sure helps to know them so you’re not forced to reach for the mouse so much. After putting on my special Macro Man cape (dum de dum dum), I used my superhero powers for good and whipped up the macro files below. The first macro, EnumerateAllCommands, does as the name implies and dumps out the all the commands available in Visual Studio and any key bindings associated with them to an Output window. On my system, with no AddIns loaded, there are 2239 commands in Visual Studio. The second macro, EnumerateKeyboardBoundCommands, dumps out just those commands that have keyboard bindings. The third macro, EnumerateBindingsByCommand, is probably the most useful as it sorts the keyboard bindings and shows each keyboard binding and the command it executes.
In the EnumerateAllCommands and EnumerateKeyboardBoundCommands macros, I show all associated keystrokes with the command. For example, the following output:
Edit.DocumentEnd (Windows Forms Designer::End | Text Editor::Ctrl+End)
Shows that the Edit.DocumentEnd command is mapped to the End key in the Windows Forms Designer and the Ctrl+End key in the Text Editor. The pipe in the middle is what I used to separate the multiple commands as some commands use the comma so I didn’t want to confuse anyone trying to read the output.
Create a new macro project in the Macro Explorer. called Keys. In the macro project, rename Module1 to Keys and paste in the code below. Create a second module called Utilities and paste in the code farther down the entry. Enjoy!
Below is the Keys module:
”””””””””””””””””””””””””””””””””””””””’
‘ Macros to dump out commands and their keyboard bindings.
‘ Copyright © 2005 — John Robbins — All rights reserved.
‘ Version 1.0 – October 31, 2005
‘ – Initial version.
”””””””””””””””””””””””””””””””””””””””’
Imports System
Imports System.Text
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics
Imports System.Collections.Generic
Public Module Keys
‘ Shows all the availible commands and any keyboard mappings.
Public Sub EnumerateAllCommands()
Dim list As SortedList(Of String, Command)
list = AllNamedCommands()
OutputCommandList(list)
End Sub
‘ Shows only the commands that have keyboard equivalents.
Public Sub EnumerateKeyboardBoundCommands()
Dim list As SortedList(Of String, Command)
list = OnlyBoundCommands()
OutputCommandList(list)
End Sub
‘ Shows all the keyboard bindings and their commands.
Public Sub EnumerateBindingsByCommand()
Dim cmdList As SortedList(Of String, Command)
cmdList = OnlyBoundCommands()
Dim bindingList As SortedList(Of String, String) = _
New SortedList(Of String, String)(cmdList.Count)
Dim i As Integer
For i = 0 To cmdList.Count – 1
Dim currCmd As String = cmdList.Keys(i)
‘ Get the array of bindings.
Dim sa() As Object = cmdList.Item(currCmd).Bindings
Dim j As Integer
For j = 0 To sa.Length – 1
bindingList.Add(sa(j), currCmd)
Next
Next
Dim ow As OutputPane = New OutputPane(“Dumped Commands”)
ow.Clear()
Dim sb As StringBuilder = New StringBuilder()
Dim k As Integer
For k = 0 To bindingList.Count – 1
sb.Length = 0
sb.AppendFormat(“{0}. {1} ({2})”, _
k, _
bindingList.Keys(k), _
bindingList(bindingList.Keys(k)))
ow.WriteLine(sb.ToString())
Next
End Sub
‘ Gets all the named commands into a list.
Private Function AllNamedCommands() As SortedList(Of String, Command)
‘ Allocate up a list sorted by command name. Since I know the number
‘ of commands, I’ll presize the list.
Dim list As SortedList(Of String, Command) = _
New SortedList(Of String, Command)(DTE.Commands.Count)
Dim Cmd As Command
‘ Get the sorted list.
For Each Cmd In DTE.Commands
‘ For some reason, there’s all sorts of strange empty command
‘ names.
If (String.IsNullOrEmpty(Cmd.Name) = False) Then
‘ Also strange is that there seems to be multiple commands of
‘ the same name!
Dim tempValue As Command
If (False = list.TryGetValue(Cmd.Name, tempValue)) Then
list.Add(Cmd.Name, Cmd)
End If
End If
Next
Return (list)
End Function
‘ Returns only those commands with key bindings.
Private Function OnlyBoundCommands() As SortedList(Of String, Command)
Dim Cmd As Command
‘ Allocate up a list sorted by command name.
Dim list As SortedList(Of String, Command) = _
New SortedList(Of String, Command)(DTE.Commands.Count)
For Each Cmd In DTE.Commands
‘ Skip the empty ones.
If (String.IsNullOrEmpty(Cmd.Name) = False) Then
‘ Skip the same named ones.
Dim tempValue As Command
If (False = list.TryGetValue(Cmd.Name, tempValue)) Then
Dim sa() As Object = Cmd.Bindings
If (sa.Length > 0) Then
‘ Only add the bound commands.
list.Add(Cmd.Name, Cmd)
End If
End If
End If
Next
Return (list)
End Function
‘ Outputs the command lists.
Private Sub OutputCommandList(ByVal list As SortedList(Of String, Command))
Dim ow As OutputPane = New OutputPane(“Dumped Commands”)
ow.Clear()
Dim sbMainString As StringBuilder = New StringBuilder()
Dim i As Integer
For i = 0 To list.Count – 1
sbMainString.Length = 0
‘ Get the command name.
Dim currCmd As String = list.Keys(i)
‘ Get the array of bindings.
Dim sa() As Object = list.Item(currCmd).Bindings
If (sa.Length > 0) Then
Dim bindStrings As StringBuilder = New StringBuilder()
Dim j As Integer
For j = 0 To sa.Length – 1
bindStrings.Append(sa(j))
If (j <> sa.Length – 1) Then
bindStrings.Append(” | “)
End If
Next
sbMainString.AppendFormat(“{0}. {1} ({2})”, _
i, _
currCmd, _
bindStrings.ToString())
Else
sbMainString.AppendFormat(“{0}. {1}”, i, currCmd)
End If
ow.WriteLine(sbMainString.ToString())
Next
End Sub
End Module
Below is the Utilities module:
””””””””””””””””””””””””””””””””””””
‘ Debugging Applications for Microsoft .NET and Microsoft Windows
‘ Copyright © 1997 – 2003 John Robbins — All rights reserved.
””””””””””””””””””””””””””””””””””””
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics
Public Module Utilities
Public Class OutputPane
‘ The output pane this class wraps.
Private m_OutPane As OutputWindowPane
‘ The class constructor.
Public Sub New(ByVal Name As String, _
Optional ByVal ShowIt As Boolean = True)
‘ First, get the main output window itself.
Dim Win As Window = _
DTE.Windows.Item(EnvDTE.Constants.vsWindowKindOutput)
‘ Show the window if I’m supposed to.
If (True = ShowIt) Then
Win.Visible = True
End If
‘ Convert the window to a real output window.
‘ The VB way of casting!
Dim OutWin As OutputWindow = Win.Object
‘ Use exception handling to access this pane if it already exists.
Try
m_OutPane = OutWin.OutputWindowPanes.Item(Name)
Catch e As System.Exception
‘ This output tab doesn’t exist, so create it.
m_OutPane = OutWin.OutputWindowPanes.Add(Name)
End Try
‘ If it’s supposed to be visible, make it so.
If (True = ShowIt) Then
m_OutPane.Activate()
End If
End Sub
‘ Allows access to the value itself.
Public ReadOnly Property OutPane() As OutputWindowPane
Get
Return m_OutPane
End Get
End Property
‘ Wrapper. Get the underlying object for all the rest
‘ of the OutputWindowPane methods.
Public Sub Clear()
m_OutPane.Clear()
End Sub
‘ The functions I wanted to add.
Public Sub WriteLine(ByVal Text As String)
m_OutPane.OutputString(Text + vbCrLf)
End Sub
End Class
End Module
As part of the research for the book, I’ve been poking at the performance and coverage tools in Visual Studio. On the performance side, what’s barely mentioned at all is that there are all sorts of amazingly cool options for doing hard core performance tuning with instrumentation. Since you’ll be using instrumentation to look very closely at just portions of your application, you need to run VSINSTR.EXE /?, the instrumentation program, at the command line. In the output, you’ll see lots of great options like /INCLUDE, which will only instrument only the specific functions you want performance numbers on. Additionally, options like /START let you say that you want performance to start counting at the specific function and /STOP on the another function. Of course, they have switches to let you suspend and resume on functions as well. The important item to take away here is, as Steve Carroll says: overhead. The less you perturb the bits the better. You can either do all your key profiling instrumentation from the command line or set those options in the performance session property pages, Advanced page. After I figured out all the cool instrumentation options, I ran across Ian Huff’s list of the profiling team’s greatest blog hits. Definitely worth reading.
Along with profiling on the command line, you can do code coverage all day long by instrumenting at the command line as well. I had to do some native C++ work recently and the command line tools helped considerably. Since the testing tools in Visual Studio are geared to managed code, another trick I did in the native application was take advantage of the GenericTest. The module I was working on already had a unit test suit based on console applications. The GenericTest is there to execute a program. The only problem is that the GenericTest basically requires that you hard code the location of the executable to run as Visual Studio testing tools play some games copying files around and what not. I managed to somewhat figure out what I needed to do to have relative paths, but it took 30 minutes of trial and error. What they need to do is add a property to the .VSMDI or .TESTCONFIG file that lets you set the path to look in for programs to be executed by GenericTests. One very nice feature of GenericTest is that it will capture the stdio output of the program so you can verify results. However, I found a problem with one of the console test applications I had that pumped out a bunch of data: the test runner hung after about 40 lines of fast output. Of course I would find this after Visual Studio .NET went RTM.
In the last two months, I’ve had a bizarre hardware problem on the big giant dual dual-core Opteron I bought, which left me without my beloved speed. I actually had to use my laptop for most of my development. Yes, I hear your tears of lament. The big machine started randomly rebooting after I’d had it a couple of months. It started out maybe once every two weeks and I chalked it up to the fact I was running Win64 and it was probably some sort of driver problem. Unfortunately, the spontaneous rebooting started occurring frequently enough that I started looking at the dumps to see what’s going on.
Before I get too far into the story, I want to tell you how you can look at machine dumps. I took it for granted that everyone doing development knew how to do this, but it turns out few do. Now you too will be able to impress the chicks at parties with your BSOD prowess. Here’s the steps:
- Log in as an administrator and right click on My Computer, and select Properties. In the System Properties dialog, click on the Advanced tab and the Settings button in the Startup and Recovery group box. In the Startup and Recovery dialog, Write debugging information group box, the defaults should Small memory dump and the directory is set to %SystemRoot%Minidump. If these are not set, set them. The environment variable %SystemRoot% means you windows directory, which, in most cases, is c:windows.
- Install the Debugging Tools for Windows, AKA WinDBG, either on the machine having the problems or on a stable machine. Now’s an excellent time to also set up a symbol server. See the WinDBG documentation or this Bugslayer column for more information.
- Once you’ve had a BSOD, scurry over to %SystemRoot%Minidump and open the latest dated mini dump in WinDBG by hitting the File, Open Crash dump menu. If WinDBG is in your path, which is should be, you can use windbg -z file.dmp in a Command prompt.
- If you have not set up a symbol server, shame on you. The first command you’ll need to type in the WinDBG Command window is .sympath SRV*c:symbols*http://msdl.microsoft.com/download/symbols to tell WinDBG where to automatically download the operating system symbols.
- The magic command in WinDBG is !analyze -v. That’ll run through the mini dump and do some automatic analysis. The best part is that it will tell you the exact driver that had the problem along with the bug check code you can look up in the WinDBG help.
Now back to our story. The problem I was having was that my spontaneous reboots were rarely producing mini dumps. As I was digging through my closet looking for a null modem cable so I could hook up a kernel debugger, I noticed a reboot and checked if I got a dump. I finally got one, but was perplexed to see that the error was a 0x9C MACHINE_CHECK_EXCEPTION. That certainly isn’t a good sign as that almost always means a hardware problem. I got lucky the rest of the day and managed to get a couple of more dumps, which all reported 0x9C as the problem. Now that I had a few more dumps, I could look at the call stacks to see what was being executed at the time of the hardware error. All but one of the couple of dumps indicated that the last code executing was in the video drivers. Granted that the video drivers are being hit the hardest in most machines, that could have been a blue herring. (We are dealing with blue screens of death after all. When we get to Vista, we can change them back to red herrings.) However, the vast majority of BSODs are from the video drivers so it was someplace to start.
I pulled one of the two NVIDIA 6600 cards and tried to see if reboot city would still happen. Everything ran great for ten hours or so before the reboot. I swapped in the other video card and got a reboot within fifteen minutes. My conclusion at that point was maybe there was something wrong with the mother board.What I really wanted was a 100% duplicatable case. Thinking it still might be something with video drivers, I hit the DirectX diagnostic program and found I could reboot at will with the 3D tests. I was using the WHCL versions of the NVIDIA drivers, but my next step was to try every possible NVIDIA driver version to see if that would make the problem go away. Unfortunately, the problem occurred with every 64-bit driver version.
At this point, I’d wasted about three days tracking this down and called @XiComputer support and did the usual support dance. I do have to admit that when they’d tell me to do something that I’d already done, I just put the phone on mute, wait an appropriate amount of time and come back to say that that didn’t work either. Don’t try that trick at home, but I’m a computer professional so I can get away with hacking support. The support guy tried valiantly and even had my try an unreleased BIOS, which really horked the machine (that one I didn’t fake, but I was able to restore the original BIOS version). We didn’t get anywhere so I gave up for the day. After more fiddling on my part, I finally gave up and called support again asking for the RMA for them to look at it. Given the cost of this monster computer, I didn’t feel bad about sending it back.
Between my trips and @XiComputer working on the machine, it took about a four weeks before I was sitting in front of my precious. The fine folks at @XiComputer tracked the problem down. Turns out both video cards were bad! They tried my video cards in other machines and would get the same 0x9C bug checks. They upgraded my video cards to SLI versions and sent it back. Of course, I didn’t know what SLI was, nor cared, but that mattered was getting my groove back.
From now on I need to write smaller blog entries about the book process. Moving from VB.NET to kernel debugging in one blog post has to be against bletiquette.
The Latest I’m Totally Loving about Visual Studio .NET 2005/.NET 2.0
- The Unit Test wizard is simply dreamy. It’s such a huge timesaver.
- Have you tried the new Object Test Bench? It’s pretty wild that you can create an instance of a class and execute methods and properties right from the Object Test Bench window. It’ll never replace unit testing, but for quick checking or debugging of some code, it’s very convenient.
The Latest Problems I’m Having with Visual Studio .NET 2005/.NET 2.0
- I already mentioned the problems with the testing tools earlier in this post.
- AddIn registration has gotten massively simpler, and the team deserves great credit for cleaning it up. However, the FriendlyName element in the .AddIn file requires the fully qualified class name. That wouldn’t be a problem if that wasn’t also the prefix put on all of your AddIn’s commands. To work around this, don’t put your IDTCommandTarget derived class in any namespace. That way the commands you add will be something like MyTool.Command. The wizards still like to add the Connect in the class name, rename the class as well.
What’s up Next?
- Symbol management utilities
- Some cool AddIn coding