XML Documents from Comments

Introduction

I like writing. I like writing books, articles, and I especially like writing code. Comments. Not so much. I tell people: my code is self-documenting. The truth is that avoiding prefixes, abbreviations, and using whole words and short function helps make code comprehensible. That said, there is nothing like plain old English (or whatever plain old language you read) to make code comprehensible to any reader.

Comments help everyone remember what the code is supposed to be doing when it has become stale. Even one's own code gets stale after a while. Another benefit is that if you use XML comments, your code's documentation will be integrated into Visual Studio through Intellisense. This one feature by itself makes XML comments worth writing, worth their weight in gold.

In this article, you'll look at the XML tags for commenting and how to generate the XML commenting documentation. (With some XSLT, the comments can be formatted into some real nice documentation. I will leave that for another day and another article.)

Adding XML Comments to Your Code

Several readers routinely write me about the keyboard hooking example written quite some time ago. Some readers expressed that they had difficulty getting the code to work. The keyboard hooker uses interfaces and delegates, and there are a couple of tricks to get the code to work right. To that end, I will demonstrate how to use XML code commenting on that code. The code was compiled and re-tested in VB9, and you will see that code and the new comments in this article. (Remember the code works even in VB9, but the emphasis here is on commenting it with XML.)

Adding the Basic XML Comment Elements

The basic XML comment is auto-generated for you by typing three apostrophes ('''). The basic comment generates the <summary> tag and the <remarks> tags. There are many more, but I will start with these two and include <returns>, <param>, and <seealso>. Listing 1 contains the form used to test to the keyboard hook capability.

Listing 1: The form to test the keyboard hooking capability

Option Explicit On
Imports Hooker


''' <summary>
''' A form to test the keyboard hooker, Implements the
''' IHooker interface
''' </summary>
''' <remarks><seealso cref="IHooker" /></remarks>
Public Class TestForm
   Implements IHooker

   ''' <summary>
   ''' The event handler for alt+tab displays a message to the
   ''' form. We return true to indicate that Alt+Tab combinations
   ''' are blocked.
   ''' </summary>
   ''' <returns></returns>
   ''' <remarks><seealso cref="IHooker.BlockAltTab"/></remarks>
   Function BlockAltTab() As Boolean Implements
      IHooker.BlockAltTab
      TextBox1.Text = "Alt+Tab Blocked" +
         DateTime.Now.ToShortDateString
      Return True
   End Function

   ''' <summary>
   ''' The event handler for alt+esc displays a message and
   ''' returns true to indicate that Alt+Esc is blocked.
   ''' </summary>
   ''' <returns></returns>
   ''' <remarks><seealso cref="IHooker.BlockAltEscape"/></remarks>
   Function BlockAltEscape() As Boolean Implements
      IHooker.BlockAltEscape
      TextBox1.Text = "Alt+Esc Blocked" +
         DateTime.Now.ToShortDateString
      Return True
   End Function

   ''' <summary>
   ''' The event handler for ctrl+esc displays a message. If we
   ''' return false we would display the message but the keystroke
   ''' would not be blocked.
   ''' </summary>
   ''' <returns></returns>
   ''' <remarks><seealso cref="IHooker.BlockControlEscape"/>
   ''' </remarks>
   Function BlockControlEscape() As Boolean Implements
      IHooker.BlockControlEscape
      TextBox1.Text = "Ctrl+Esc Blocked" +
         DateTime.Now.ToShortDateString
      Return True
   End Function

   ''' <summary>
   ''' Assign the form's hooker field to this form. Linking
   ''' the KeyboardHooker instance
   ''' to this form. Finally, hook the keyboard.
   ''' </summary>
   ''' <param name="sender"></param>
   ''' <param name="e"></param>
   ''' <remarks><seealso cref="KeyboardHooker"/></remarks>
   Private Sub TestForm_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
      hooker.Hooker = Me
      hooker.HookKeyboard()
   End Sub

   ''' <summary>
   ''' A private field representing an instance of the keyboard
   ''' hooker wrapper
   ''' </summary>
   ''' <remarks></remarks>
   Private hooker As KeyboardHooker = New KeyboardHooker

   ''' <summary>
   ''' We want to unhook the keyboard before we close
   ''' the application.
   ''' </summary>
   ''' <param name="sender"></param>
   ''' <param name="e"></param>
   ''' <remarks><seealos cref="KeyboardHooker.UnhookKeyboard"/>
   ''' </remarks>
   Private Sub TestForm_FormClosing(ByVal sender As System.Object, _
      ByVal e As System.Windows.Forms.FormClosingEventArgs) _
      Handles MyBase.FormClosing
      hooker.UnhookKeyboard()
   End Sub

End Class

XML Documents from Comments

By typing ''' (three apostrophes), the IDE will generate the <summary> and <remarks> tags. If the commented item is a function, a <param> tag with the name attribute will be generated for each argument. Also, if the commented member is a function, the <returns> tag will be generated. (See any method for examples of <summary> and <remarks>. Refer to BlockAltTab for an example of the <returns> tag, and see TestForm_Load for examples of <param>.

The <summary> block represents straight code comments. The <remarks> tag can used to add cross references with the <seealso> tag and cref attribute. (See BlockAltEscape for an example of <seealso>. The <param> tag has a name attribute indicating the name of the parameter, and the <returns> tag is empty. You can specify the type that the function returns by typing it in between the opening and closing <returns></returns> tag pair.

Indicating an Exception that Can be Thrown

There are many kinds of tags you can add to your comments. The complete list is described in the MSDN help top "Recommended XML Tags for Documentation Comments." For example, if a function throws an exception as does KeyboardHooker.CheckedHooked, you can indicate that in the comments with the <exception> tag. (See Listing 2 for the KeyboardHooker class.)

Listing 2: The KeyboardHooker class demonstrates how to comment various kinds of code elements as well as introduces the <exception> tag with a cref attribute.

Option Strict Off


Imports System.Runtime.InteropServices
Imports System.Reflection
Imports System.Windows.Forms
Imports System.Diagnostics

''' <summary>
''' A wrapper for Windows API methods for low-level keyboard hooks.
''' </summary>
''' <remarks></remarks>
PublicClass KeyboardHooker

   ''' <summary>
   ''' An API method to restore the keyboard event handling chain
   ''' </summary>
   ''' <param name="hHook"></param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Declare Auto Function UnhookWindowsHookEx Lib "user32" _
      (ByVal hHook As Integer) As Integer

   ''' <summary>
   ''' An extended API method to hook the keyboard
   ''' </summary>
   ''' <param name="idHook"></param>
   ''' <param name="lpfn">Use System.Runtime.InteropServices.
   ''' MarshalAs for pointers</param>
   ''' <param name="hmod"></param>
   ''' <param name="dwThreadId"></param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Declare Auto Function SetWindowsHookEx Lib "User32" _
      Alias "SetWindowsHookExA" ( _
      ByVal idHook As Integer, _
      <MarshalAs(UnmanagedType.FunctionPtr)> _
      ByVal lpfn As KeyboardHookDelegate, _
   ByVal hmod As Integer, ByVal dwThreadId As Integer) As Integer

   ''' <summary>
   ''' Gets the asynchronous keyboard state for detecting Ctrl key
   ''' combinations and such
   ''' </summary>
   ''' <param name="vKey"></param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Private Declare Function GetAsyncKeyState Lib "user32" _
      (ByVal vKey As Integer) As Integer

   ''' <summary>
   ''' Used to call the next eyboard handler in the chain. By not
   ''' calling this we effectively block other applications from
   ''' getting keyboard events
   ''' </summary>
   ''' <param name="hHook"></param>
   ''' <param name="nCode"></param>
   ''' <param name="wParam"></param>
   ''' <param name="lParam"></param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Private Declare Function CallNextHookEx Lib "user32" _
      (ByVal hHook As Integer, _
      ByVal nCode As Integer, _
      ByVal wParam As Integer, _
      <MarshalAs(UnmanagedType.Struct)> _
      ByVal lParam As KBDLLHOOKSTRUCT) As Integer

   ''' <summary>
   ''' The structure representing the keyboard state when the
   ''' low-level keyboard handler is called
   ''' </summary>
   ''' <remarks></remarks>
   Public Structure KBDLLHOOKSTRUCT
      Public vkCode As Integer
      Public scanCode As Integer
      Public flags As Integer
      Public time As Integer
      <MarshalAs(UnmanagedType.U8)> _
      Public dwExtraInfo As Long
   End Structure


   ''' <summary>
   ''' Low-level keybard constants. These values
   ''' can go in the KBDLLHOOKSTRUCT.flags field
   ''' </summary>
   ''' <remarks><seealso cref="KBDLLHOOKSTRUCT"/></remarks>
   Private Const HC_ACTION = 0
   ''' <summary>
   ''' Bitmask to check for extended keys
   ''' </summary>
   ''' <remarks></remarks>
   Private Const LLKHF_EXTENDED = &H1
   ''' <summary>
   ''' Test the event-injected flag
   ''' </summary>
   ''' <remarks></remarks>
   Private Const LLKHF_INJECTED = &H10
   ''' <summary>
   ''' Test the context code
   ''' </summary>
   ''' <remarks></remarks>
   Private Const LLKHF_ALTDOWN = &H20
   ''' <summary>
   ''' Set the transition state
   ''' </summary>
   ''' <remarks></remarks>
   Private Const LLKHF_UP = &H80

   ''' <summary>
   ''' Represents the tab key
   ''' </summary>
   ''' <remarks></remarks>
   Public Const VK_TAB = &H9
   ''' <summary>
   ''' Represents the control key
   ''' </summary>
   ''' <remarks></remarks>
   Public Const VK_CONTROL = &H11
   ''' <summary>
   ''' Represents the escape key
   ''' </summary>
   ''' <remarks></remarks>
   Public Const VK_ESCAPE = &H1B
   ''' <summary>
   ''' Represents the delete key
   ''' </summary>
   ''' <remarks></remarks>
   Public Const VK_DELETE = &H2E

   ''' <summary>
   ''' The function value to pass to idHook for the
   ''' SetWindowsHookEx API
   ''' </summary>
   ''' <remarks><seealso cref="SetWindowsHookEx"/></remarks>
   Private Const WH_KEYBOARD_LL = 13&
   ''' <summary>
   ''' Keyboard hook handle return by SetWindowsHookEx.
   ''' We're hooked if the value return is not 0
   ''' </summary>
   ''' <remarks><seealso cref="SetWindowsHookEx"/></remarks>
   Public KeyboardHandle AsInteger

   ''' <summary>
   ''' An instance of the IHooker interface, representing the
   ''' thing that will repsond to keyboard events {i.e. the form
   ''' in our example}
   ''' </summary>
   ''' <remarks></remarks>
   Public Hooker As IHooker

   ''' <summary>
   ''' Scans the Hookstruct to figure out what combinations of
   ''' keys are pressed
   ''' </summary>
   ''' <param name="Hookstruct"></param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Function IsHooked(ByRef Hookstruct As KBDLLHOOKSTRUCT) _
      AsBoolean

      Debug.WriteLine(Hookstruct.vkCode.ToString())


      If (Hookstruct.vkCode = VK_ESCAPE) And _
         CBool(GetAsyncKeyState(VK_CONTROL) _
         And &H8000) Then

         Call HookedState(Hooker.BlockControlEscape,
            "Ctrl + Esc blocked")
         Return Hooker.BlockControlEscape
      End If

      If (Hookstruct.vkCode = VK_TAB) And _
         CBool(Hookstruct.flags And _
         LLKHF_ALTDOWN) Then

         Call HookedState(Hooker.BlockAltTab, "Alt + Tab blocked")
         Return Hooker.BlockAltTab
      End If


      If (Hookstruct.vkCode = VK_ESCAPE) And _
         CBool(Hookstruct.flags And _
            LLKHF_ALTDOWN) Then

         Call HookedState(Hooker.BlockAltEscape,
            "Alt + Escape blocked")
         Return Hooker.BlockAltEscape
      End If

   End Function

   ''' <summary>
   ''' Checks to determine if we are hooked, sending output to the
   ''' debug window.
   ''' </summary>
   ''' <param name="Hooked"></param>
   ''' <param name="Text"></param>
   ''' <remarks>If we use a messagebox then the hook chain is
   ''' goofed up by that window</remarks>
   Private Sub HookedState(ByVal Hooked As Boolean, _
                           ByVal Text As String)
   If (Hooked) Then
      'MessageBox.Show(Text) -' lets the key stroke go through
      Debug.Print(Text)
   End If
   End Sub

   ''' <summary>
   ''' The wrapper's event handler to catch keyboard events and
   ''' pass that information to the IsHooked method for specific
   ''' combinations
   ''' </summary>
   ''' <param name="Code"></param>
   ''' <param name="wParam"></param>
   ''' <param name="lParam"></param>
   ''' <returns>Integer</returns>
   ''' <remarks>If we don't call CallNextHookEx then the keys are
   ''' blocked</remarks>
   Public Function KeyboardCallback(ByVal Code As Integer, _
      ByVal wParam As Integer, ByRef lParam _
         As KBDLLHOOKSTRUCT) _As Integer

      If (Code = HC_ACTION) Then
         If (IsHooked(lParam)) Then
            Return 1
         End If
      End If

      Return CallNextHookEx(KeyboardHandle, _
         Code, wParam, lParam)

   End Function

   ''' <summary>
   ''' A definition of our keyboard event handler
   ''' </summary>
   ''' <param name="Code"></param>
   ''' <param name="wParam"></param>
   ''' <param name="lparam"></param>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Public Delegate Function KeyboardHookDelegate( _
      ByVal Code As Integer, ByVal wParam As Integer, _
      ByRef lparam As KBDLLHOOKSTRUCT) As Integer

   ''' <summary>
   ''' The instance of the keyboard event handler we pass this
   ''' as lpfn (the function pointer) to SetWindowsHookEx
   ''' </summary>
   ''' <remarks><seealso cref="HookKeyboard"/></remarks>

   <MarshalAs(UnmanagedType.FunctionPtr)> _
   Private callback As KeyboardHookDelegate


   Public Sub HookKeyboard()
      callback = New _
         KeyboardHookDelegate(AddressOf KeyboardCallback)

      KeyboardHandle = SetWindowsHookEx( _
         WH_KEYBOARD_LL, callback, _
         Marshal.GetHINSTANCE([Assembly]. _
         GetExecutingAssembly.GetModules()(0)).ToInt32(), 0)
      Call CheckHooked()
   End Sub

   ''' <summary>
   ''' Were we able to hook the keyboard
   ''' </summary>
   ''' <remarks></remarks>
   ''' <exception cref="Exception">Exception</exception>
   Public Sub CheckHooked()
      If (Hooked()) Then
         Debug.Print("Keyboard hooked")
      Else
         Debug.Print("Keyboard hook failed: " & Err.LastDllError)
         Throw New Exception("Keyboard hook failed")
      End If
   End Sub

   ''' <summary>
   ''' Test to see if the handle is 0 or not. Non-zero means
   ''' the keyboard was hooked
   ''' </summary>
   ''' <returns></returns>
   ''' <remarks></remarks>
   Private Function Hooked()
      Hooked = KeyboardHandle <> 0
   End Function

   ''' <summary>
   ''' Unhook the keybaord and restore the chain as it was before
   ''' we changed it
   ''' </summary>
   ''' <remarks></remarks>
   Public Sub UnhookKeyboard()
      If (Hooked()) Then
         Call UnhookWindowsHookEx(KeyboardHandle)
      End If
   End Sub

End Class

XML Documents from Comments

If you want to read the original article, refer to "Managing Low-Level Keyboard Hooks with the Windows API" at http://www.codeguru.com/vb/gen/vb_system/keyboard/print.php/c4829/. For your convenience, the complete listing for this version of the keyboard hook was uploaded with the article to codeguru.com.

Generating XML Documentation

The benefits of XML documentation include well-commented code in a uniform format, and your comments will be incorporated into Visual Studio's Intellisense system (see Figure 1) and Visual Studio will generate an .xml documentation file. (There are some tools that will convert these files to formatted help document too. Check out NDoc by Googling it.)

[PKHook1.jpg]

Figure 1: My XML comments are incorporated in the Intellisense system.

The XML documentation is generated by default. Generating documentation is equal to the /doc command line compiler option and is represented in the IDE in the Project Properties Compile tab by the (checked) checkbox next to the Generate XML documentation file (see Figure 2).

[PKHook2.jpg]

Figure 2: XML Documentation is configured to be generated by default.

Summary

I made excuses not to comment much for years. My code was self-commenting. Comments in code are ugly. (I still think they are.) And, I still submit that short, well-named methods help readability and debugging code gets stale and there is nothing like plain text to refresh one's memory (especially as the years collect).

With XML documentation, comments are well-formatted and uniform. They are automatically generated as XML files that easily can be converted into professional help documentation, and best of all, XML comments are incorporated into Microsoft Visual Studio's Intellisense system. Nothing beats Intellisense for quickly figuring out which element to use and how.

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his upcoming book LINQ Unleashed for C# due in Spring 2008. You may contact him for technology questions at pkimmel@softconcepts.com.

If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org. Glugnet opened a users group branch in Flint, Michigan in August 2007. If you are interested in attending, check out the www.glugnet.org web site for updates.

Copyright © 2007 by Paul T. Kimmel. All Rights Reserved.



Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds