Debugging Hosted Assemblies

We are well into winter in the Midwest and Northeast and that's got me thinking about sunshine and warmth. Sunshine and warmth makes me think of Las Vegas. I love Las Vegas and to play Black Jack. Generally my gambling is modest and I am a less than average player but committed to getting better. As an entertainment expense if I win $500 then I can go to a show, fly an Extra 300L at the Aerobatic Experience in Boulder City, have a great meal, visit the spa and get a massage, or go to the Richard Petty Experience on the casino's dime. So, winning a few hundred bucks means extra fun.

My other motivation for writing this particular article is that I really liked Chris Sells' Wahoo! Game on www.sellsbrothers.com/. Wahoo! is basically Tetris. You can download and play this Windows game from the Internet, and Chris uses it to talk about code access security. I liked the idea and BlackJack, so I thought I'd borrow the sentiment and create a Windows BlackJack game and use it to demonstrate how to debug a hosted assembly.

A hosted assembly is a non-executable assembly that runs in another process. COM+ server applications run in dllhost.exe. ASP.NET assemblies run in the aspnet_wp.exe process, and any assembly can be loaded and tested by NUnit test assemblies. For our purposes I elected to use a NUnit assembly and NUnit to demonstrate debugging a hosted process. The process we will be debugging is the library for my blackjack game. (As soon as I am happy with the results I will post the game, which you can download for free with source, from http://www.softconcepts.com/BlackJack.)

It is important to note that attaching to a host process and debugging your .NET assemblies is the same regardless of the host. All you need to know is the underlying name of the executable host process. (Examples are: COM+ is dllhost.exe, ASP.NET is aspnet_wp.exe, and NUnit is nunit-gui.exe.)

Building the BlackJack Game

The construction of the BlackJack took several hours to assemble. I understand the game well enough to identify some basic classes without a lot of analysis or fancy modeling; in short, I hacked the game together and refactored until I was reasonably happy with the result (see figure 1).



Click here for larger image

Figure 1: With a little practice my game might improve.

If you are familiar with the card games in general and Blackjack in particular then you won't be surprised by some of the classes implemented to support the game. Some of the classes we will need to test include BlackJack, Cards, Dealer, DealerHand, Deck, Decks, Hand, Hints, Player, PlayerCollection, PlayerHand, PlayerHandCollection, Shuffler, and Suits. (The source listing is too big to provide here, but it will be available online at http://www.softconcepts.com/BlackJack shortly.)

To permit the game to be played as a console game, Windows game, perhaps a CE game, and eventually a Web game, I implemented most of the classes in a separate class library. It is this class library (as well as the clients, but not for our purposes) that need to be tested in a host and that we will use to demonstrate host debugging in Visual Studio .NET.

Whereas some people like to build software starting with the presentation layer and finishing with the object layer, I generally build software from the object layer to presentation layer. I start with some core classes at the lowest level of complexity and then layer complexity, testing each layer as complexity is added. To this end it seemed natural to start with a card class, specific classes of cards—for example, in BlackJack Ace can have the value of 1 or 11—so it seemed suitable to subclass Card to define an Ace class as well as a class for each face value. Very quickly there were 15 classes to test—Card, Ace through King, and Deck. Because these are intrinsic classes it is useful to ensure these classes function correctly before layering complexity. Listing 1 shows supporting enumerations, and Listing 2 contains the core Card base class and the Ace class.

Listing 1: Enumerations for describing playing cards.

Imports System

Public Enum Face
    One
    Two
    Three
    Four
    Five
    Six
    Seven
    Eight
    Nine
    Ten
    Jack
    Queen
    King
End Enum

Public Enum Suit
  Diamond
  Club
  Heart
  Spade
End Enum

Listing 2: The Card base class and an Ace subclass.

Imports System
Imports System.Drawing

Public MustInherit Class Card
  Private FCardFace As Face
  Private FHighFaceValue As Integer
  Private FLowFaceValue As Integer
  Private FCardSuit As Suit

#Region "External methods and related fields"
  Private width As Integer = 0
  Private height As Integer = 0

  Declare Function cdtInit Lib "cards.dll" (ByRef width As Integer, _
    ByRef height As Integer) As Boolean
  Declare Function cdtDrawExt Lib "cards.dll" (ByVal hdc As IntPtr, _
    ByVal x As Integer, ByVal y As Integer, ByVal dx As Integer, _
    ByVal dy As Integer, ByVal card As Integer, _
    ByVal suit As Integer, ByVal color As Long) As Boolean
  Declare Sub cdtTerm Lib "cards.dll" ()
#End Region

  Public Sub New(ByVal lowValue As Integer, ByVal highValue As Integer, _
    ByVal cardSuit As Suit, ByVal cardFace As face)

    cdtInit(width, height)
    FHighFaceValue = highValue
    FLowFaceValue = lowValue
    FCardSuit = cardSuit
    FCardFace = cardFace

  End Sub

  Public Function GetLowFacevalue() As Integer
    Return FLowFaceValue
  End Function

  Public Function GetHighFaceValue() As Integer
    Return FHighFaceValue
  End Function

  Public Function GetFaceValue() As Integer
    Return FLowFaceValue
  End Function

  Public Property CardSuit() As Suit
  Get
    Return FCardSuit
  End Get
  Set(ByVal Value As Suit)
    FCardSuit = Value
  End Set
  End Property

  ' TODO: Convert various paint styles to interface
  Public Sub PaintTextFace()
    Console.WriteLine(GetCardValue())
  End Sub

  Public Sub PaintGraphicFace(ByVal g As Graphics, ByVal x As Integer, _
    ByVal y As Integer, ByVal dx As Integer, ByVal dy As Integer)

    Dim hdc As IntPtr = g.GetHdc()
    Try
      Dim Card As Integer = CType(Me.FCardFace, Integer)
      cdtDrawExt(hdc, x, y, dx, dy, Card, 0, 0)
    Finally
      ' If Intellisense doesn't show this method unhine advanced 
      ' members in Tools|Options
      g.ReleaseHdc(hdc)
    End Try
  End Sub

  Public Sub PaintGraphicBack(ByVal g As Graphics, ByVal x As Integer, _
    ByVal y As Integer, ByVal dx As Integer, ByVal dy As Integer)

    Dim hdc As IntPtr = g.GetHdc()
    Try
      ' TODO: Make card style (hardcoded 61) a configurable property
      cdtDrawExt(hdc, x, y, dx, dy, 61, 0, 0)
    Finally
      g.ReleaseHdc(hdc)
    End Try

  End Sub

  Protected Overridable Function GetTextValue() As String
    Return GetLowFacevalue().ToString()
  End Function

  Protected Function GetTextSuit() As String
    Return FCardSuit.ToString().Chars(0).ToString
  End Function

  Public Overridable Function GetCardValue() As String
    Return String.Format("{0}{1}", GetTextValue(), GetTextSuit())
  End Function
End Class

Public Class Ace
  Inherits Card

  Public Sub New(ByVal cardSuit As Suit)
    MyBase.New(1, 11, cardSuit, Face.One)
  End Sub

  Protected Overrides Function GetTextValue() As String
    Return "A"
  End Function
 End Class



Click here for larger image

Figure 2: Show advanced methods that are concealed in Intellisense by default; an example of an advanced method is the Graphics.ReleaseHdc method.

The enumerations Face and Suit use strongly typed enumerations to describe the value and suit of a card. The Card class stores properties like the face and suit values as well as the underlying value of the card. In addition, I have declared some API methods from cards.dll, which already contain capabilities for drawing graphic playing cards. (Cards.dll ships with Windows and supports games like Solitaire (sol.exe), which ships with Windows.)

Some changes I'd like to see before this code goes live are to permit the dynamic configuration of the back of the playing card—it is hard coded to 61, a value described in the documentation for cards.dll available with a Google search—and convert the Paint methods into overloaded methods or specific implementations of a graphic and text card interface.

Generally, when I get write as much code as shown in figures 1 and 2 I begin testing.

Defining the NUnit Tests

To test our code we can downloaded the superlative NUnit Version 2.1 testing software from www.nunit.org. (Refer to last month's article on www.codeguru.com for more information on debugging with NUnit.)

NUnit will play the role of our testing host. Listing 3 contains some NUnit tests that we can very quickly assemble to begin scaffolding a suite of tests in conjunction with our application development.

Listing 3: NUnit tests for our Card and Ace classes.

Imports NUnit.Framework
Imports BlackJackLibVB
Imports System.Windows.Forms

 _
Public Class BlackJackTests

   _
  Public Sub CardSuitTest()
    Dim aceOfClubs As Ace = New Ace(Suit.Club)
    Console.WriteLine(aceOfClubs.GetCardValue())
    Assertion.AssertEquals("Expected 'AC'", aceOfClubs.GetCardValue(), "AC")
  End Sub

   _
  Public Sub CardAceLowValueTest()
    Dim a As Ace = New Ace(Suit.Heart)
    Console.WriteLine(a.GetCardValue())
    Assertion.AssertEquals("Expected 1", 1, a.GetLowFaceValue())
  End Sub

   _
  Public Sub CardAceHighValueTest()
    Dim a As Ace = New Ace(Suit.Heart)
    Console.WriteLine(a.GetCardValue())
    Assertion.AssertEquals("Expected 11", 11, a.GetHighFaceValue())
  End Sub

  Private spade As Ace
   _
  Public Sub GraphicPaintAceTest()
    spade = New Ace(Suit.Spade)
    Dim F As Form = New Form
    AddHandler F.Paint, AddressOf OnPaint
    F.ShowDialog()
    Assertion.Assert(MsgBox("Did you see the ace of spades?", _
      MsgBoxStyle.Question Or MsgBoxStyle.YesNo, "Ace of Spades") _
      = MsgBoxResult.Yes)

  End Sub

  Private Sub OnPaint(ByVal sender As Object, ByVal e As PaintEventArgs)
    If (spade Is Nothing) Then Return
    spade.PaintGraphicFace(e.Graphics, 0, 0, 75, 100)
  End Sub

End Class

NUnit tests can be as advanced or as simple as you like. The real benefit of using NUnit is that it was designed to work with .NET specifically, uses a simple green for pass and red for fail visual metaphor, and offers a consistent predetermined means of defining, running, and evaluating tests.

To demonstrate I implemented some simple test that evaluate the text face-value of a card and one test that displays the graphic representation of the Ace of Spades. Figure 3 shows NUnit running in the background with the dynamic form and Ace card shown in the foreground.



Click here for larger image

Figure 3: The paint test for the ace of spades.

NUnit can be used quite simply as a pass or fail testing tool. Generally, you will need a testing scaffold that permits you to interact with your code while it's running; NUnit can be used for this too.

Attaching Visual Studio .NET to the Host Process

As the king of your demesne you can elect to step through your code for any reason. I wanted to work on the precise positioning of the playing cards. Pretending that the Ace of Spades didn't print where I anticipated we could test the BlackJackLibVB library while it is running in Visual Studio .NET. To do this we need to attach to the host process, NUnit. To debug a library running in its host, follow these steps:

  1. Open the library project you would like to test in Visual Studio .NET
  2. Run the host process that loads the library you will be testing. (In the example we need to run nunit-gui.exe and load the BlackJackLibVB.dll as shown in figure 3.)
  3. Back in Visual Studio .NET select Debug|Processes
  4. In the list of Available Processes find the nunit-gui.exe process hosting the BlackJackLibVB.dll as shown in figure 4
  5. Click the hosting process and click Attach
  6. In the Attach to Process dialog (see figure 5) check the Common Language Runtime program type and click OK
  7. Click Close to close the Processes dialog



Click here for larger image

Figure 4: Attached to the host process hosting your library.

Figure 5: We are debugging .NET code so select the Common Language Runtime program type.

After you click close you will see the dependent assemblies loaded into the integrated debugger in the Debug view of the Output window. Your library code is now running in the integrated debugger.

Debugging the BlackJackLibVB

Debugging a library this way is identical to debugging an executable, once the library is loaded via the host process. To debug the BlackJackLibVB set some breakpoints in the code at areas in which you are interested and run the tests. When the debugger hits your breakpoint the debugger will suspend code execution and you can take over. All of the great features you are used to in Visual Studio .NET are now at your fingertips when debugging library assemblies.

When You are Finished Debugging

When you have finished debugging your hosted assembly you can kill the host process or detach the debugger from the host process by selecting Debug|Processes, clicking the attached process and clicking Detach (see figure 4).

If you are debugging and NUnit is your host you have the luxury of detaching the debugger from NUnit, modifying your code, rebuilding, and re-attaching the debugger to NUnit all without shutting down VS.NET or NUnit. Collectively, these tools will yield some powerful results.

Summary

Some programmers aren't building rich client applications. Some of us, sometimes, are building frameworks of our own. Instead of spending a lot of time building test applications use the host your assembly will really run in and attach VS.NET to that process.

Using the VS.NET Debug|Processes dialog to attach to a running host process permits you to use the powerful integrated debugger in VS.NET without a lot of extra effort.

About the Author

Paul Kimmel is the VB Today columnist for Codeguru.com and Developer.com and has written several books on object oriented programming, including the recently released Visual Basic .NET Power Coding from Addison-Wesley and the upcoming Excel VBA 2003: Programmer's Reference from Wiley. He is the chief architect for Software Conceptions and is available to help design and build your next application.

# # #



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

  • Event Date: April 15, 2014 The ability to effectively set sales goals, assign quotas and territories, bring new people on board and quickly make adjustments to the sales force is often crucial to success--and to the field experience! But for sales operations leaders, managing the administrative processes, systems, data and various departments to get it all right can often be difficult, inefficient and manually intensive. Register for this webinar and learn how you can: Align sales goals, quotas and …

  • Hybrid cloud platforms need to think in terms of sweet spots when it comes to application platform interface (API) integration. Cloud Velocity has taken a unique approach to tight integration with the API sweet spot; enough to support the agility of physical and virtual apps, including multi-tier environments and databases, while reducing capital and operating costs. Read this case study to learn how a global-level Fortune 1000 company was able to deploy an entire 6+ TB Oracle eCommerce stack in Amazon Web …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds