Extending IronPython Using Visual Basic.NET

Considering C# and VISUAL BASIC.NET Extension Similarities

Visual Basic.NET does have some distinct advantages over C# when building an extension. The most important of these distinctions is that Visual Basic.NET does more for you in the background. For example, Visual Basic.NET automatically creates a namespace for you — it isn’t something you have to think about. Visual Basic.NET also performs some type conversions automatically, so you don’t have to think about type conversions as much either. When you do need to perform a type conversion, you use the CType() function, which makes the kind of conversion a little more apparent.

You can easily use either C# or Visual Basic.NET to perform simple tasks. For example, either language works fine for creating a math library or for working with files. C# probably has an advantage in working with low-level extensions, especially those that interact with the Win32 API. On the other hand, the tendency of Visual Basic.NET to hide some of the gory details of programming works to your advantage when working with higher-level programming requirements, such as database access. Consequently, this chapter describes the requirements for creating a database extension.

Creating the Simple Visual Basic.NET Extension

The best place to begin learning how to create extensions is to create a very simple one. The sections that follow explore a simple Visual Basic.NET extension. This project creates a simple math library. In the process, it demonstrates some unique principles of creating extensions using Visual Basic.NET.

Creating the Project

A Visual Basic.NET extension project is nothing more than the typical class library. The following steps help you create the project for this example. You can use the same steps when working with the other examples — simply change the project name.

  1. Choose File ➪ New ➪ Project. You’ll see the New Project dialog box shown in Figure 17-1.

    Create a new project to hold your Visual Basic.NET extension.
    Figure 17-1: Create a new project to hold your Visual Basic.NET extension.
  2. Choose the Visual Basic folder in the Installed Templates list.
  3. Select .NET Framework 3.5 or an earlier version of the .NET Framework if you’re using Visual Studio 2010. Don’t select the .NET Framework 4.0 entry because IronPython won’t load extensions based on the .NET Framework 4.0. The list of templates changes when you change the .NET Framework version.
  4. Select the Class Library template.
  5. Check Create Directory for Solution if it isn’t already checked. When working with extensions, creating a solution directory provides a place for putting solution-level objects.
  6. Type Calcs in the Name field and click OK. Visual Studio creates a class library project for you.
  7. Right-click Class1.vb in Solution Explorer and choose Rename from the context menu. Visual Studio makes the filename editable.
  8. Type Calcs.VB for the new filename and press Enter. Visual Studio displays a dialog box that asks whether you’d like to rename all of the Class1.vb references to match the new filename.
  9. Click Yes. The project is ready for use.

Developing the Visual Basic.NET Extension

The Visual Basic.NET extension code for this example is relatively simple. Listing 17-1 shows the constructor, operator overrides, and methods used for this example.

Listin g 17-1: A simple calculations extension

[code]
Public Class Calcs
Private Data As Int32
Public Sub New(ByVal Value As Int32)
Me.Data = Value
End Sub
Public Overrides Function ToString() As String
Return Data.ToString()
End Function
Public Shared Operator +(ByVal Value1 As Calcs, _
ByVal Value2 As Calcs) As Calcs
Return New Calcs(Value1.Data + Value2.Data)
End Operator
Public Shared Operator -(ByVal Value1 As Calcs, _
ByVal Value2 As Calcs) As Calcs
Return New Calcs(Value1.Data – Value2.Data)
End Operator
Public Shared Operator *(ByVal Value1 As Calcs, _
ByVal Value2 As Calcs) As Calcs
Return New Calcs(Value1.Data * Value2.Data)
End Operator
Public Shared Operator /(ByVal Value1 As Calcs, _
ByVal Value2 As Calcs) As Calcs
Return New Calcs(Value1.Data / Value2.Data)
End Operator
Public Function Inc() As Calcs
Return New Calcs(Me.Data + 1)
End Function
Public Function Dec() As Calcs
Return New Calcs(Me.Data – 1)
End Function
End Class
[/code]

The code begins with a constructor that accepts an Int32 value as input. The example doesn’t include a default constructor because IronPython needs to assign a value to the object during the instantiation process. A default constructor would still need to assign a value to the private Data member, so it’s just better to assign a valid value to Data at the outset.

The ToString() override comes next. The default behavior for ToString() is to display the name of the class. You must override this behavior to display the value of Data. Notice that you must access Data as Me.Data — the copy of Data associated with this particular instance of the Calcs class.

The four Operator methods are defined as Shared, rather than Overrides. The Operator methods act as static class members so that you can use them naturally in IronPython. The input arguments for each method are the objects you create within IronPython. Consequently, there isn’t any concept of numeric type for Value1 or Value2 (you could theoretically use the same methods for any numeric value). The actual math operation occurs on the Data member of each object.

IronPython doesn’t support the ++ or — operators that are supported by Visual Basic for increment and decrement. Consequently, the class provides an Inc() and Dec() method. Notice that these methods aren’t defined as Shared because they work with a single object. You need to consider the differences between binary (those that work with two objects) and unary (those that work with a single object) operators when creating your extension. Binary operators are always declared as Shared, while unary operators appear as a standard method.

At this point, you can compile the class if desired. Start a copy of the IronPython console and type the following commands to load the extension.

[code]
import clr
clr.AddReferenceToFile(‘Calcs.DLL’)
import Calcs
dir(Calcs.Calcs)
[/code]

The dir() function shows the content of the Calcs extension as shown in Figure 17-2. Notice that Inc() and Dec() appear as you expect. However, there aren’t any entries for +, -, *, and / methods. These operators still work as you expect, but IronPython shows a Python equivalent for the operators in the form of __add__(), __radd__(), __sub__(), __rsub__(), __mul__(), __rmul__(), __div__ (), and __rdiv__(). These methods don’t appear unless you define the operators in your class.

If you’re looking at the class in the IronPython console, you might want to give it a quick try before you close up the console and move on to the next part of the example. Try this code and you’ll see an output of 15 from the __add__() method. Figure 17-2 shows the results of the calculation.

[code]
Value1 = Calcs.Calcs(10)
Value2 = Calcs.Calcs(5)
print Value1.__add__(Value2)
[/code]

The dir() function shows the content of the Calcs class.
Figure 17-2: The dir() function shows the content of the Calcs class.

Adding the IronPython Project

At this point, you have a Visual Basic.NET extension (or module) to use with IronPython. Of course, you’ll want to test it. The easiest way to do this is to add the IronPython project directly to the current solution. The following steps describe how to perform this task.

  1. Right-click the solution entry in Solution Explorer and choose Add ➪ Existing Project from the context menu. You’ll see the Add Existing Project dialog box shown in Figure 17-3.

    Locate IPY.EXE and add it to your solution.
    Figure 17-3: Locate IPY.EXE and add it to your solution.
  2. Locate IPY.EXE on your hard drive and highlight it. Click Open. You’ll see a new project entry added to the solution.
  3. Right-click the ipy entry in Solution Explorer and choose Set as Startup Project from the context menu. This step ensures that choosing one of the startup options from the Debug menu starts the IronPython application.
  4. Right-click the ipy entry in Solution Explorer and choose Properties from the context menu. You’ll see the General tab of the ipy Properties window shown in Figure 17-4.

    Configure the IronPython application to work with Calcs.DLL.
    Figure 17-4: Configure the IronPython application to work with Calcs.DLL.
  5. Type -D TestCalcs.py in the Arguments field.
  6. Click the ellipses in the Working Directory field to display the Browse for Folder dialog box. Locate the output folder of the Calcs.DLL (or other extension) file. Click OK. The IDE adds the correct directory information to the Working Directory field.
  7. Open Windows Explorer. Locate the CalcsCalcsbinDebug folder. Right-click in the right pane and choose New ➪ Text Document from the context menu. Name the file TestCalcs.py and press Enter. Click Yes if asked if you want to rename the file extension.
  8. Right-click the solution item in Solution Explorer and choose Add ➪ Existing Item from the context menu to display the Add Existing Item dialog box shown in Figure 17-5.
  9. Locate the TestCalcs.py file in the solution and click Add. Visual Studio adds TestCalcs.py to the Solution Items folder in Solution Explorer and automatically opens the file for you. You’re ready to add test code for the application.
Add the TestCalcs.py file to the solution.
Figure 17-5: Add the TestCalcs.py file to the solution.

Creating the IronPython Application

It’s time to write code to test Calcs.DLL. Listing 17-2 shows the code you’ll use for testing purposes.

Listin g 17-2: Testing the extension using IronPython

[code]
# Add a reference to the CLR
import clr
# Obtain access to the extension.
clr.AddReferenceToFile(‘Calcs.DLL’)
import Calcs
# Create an instance of the class and fill it with data.
Value1 = Calcs.Calcs(10)
# Print the original value, then decrement and increment it.
print ‘Original Value1 Content: ‘, Value1
print ‘Value1 + 1: ‘, Value1.Inc()
print ‘Value1 – 1: ‘, Value1.Dec()
# Create a second value and display it.
Value2 = Calcs.Calcs(5)
print ‘nOriginal Value2 Content: ‘, Value2
# Use the two values together in different ways.
print ‘nValue1 + Value2 = ‘, Value1 + Value2
print ‘Value1 – Value2 = ‘, Value1 – Value2
print ‘Value1 * Value2 = ‘, Value1 * Value2
print ‘Value1 / Value2 = ‘, Value1 / Value2
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The code begins by importing the Common Language Runtime (CLR). It then uses the AddReferenceToFile() method to create a reference to Calcs.DLL. The final step is to import the Calcs code.

Before the code can use the Calcs code, it must create an instance of it, Value1. Notice that the code calls the Calcs.Calcs() constructor with an initial value. Any time you want to assign a value to Value1, you must use the Calcs.Calcs() constructor. If you were to assign a value using Value1 = 15, it would change the type of Value1. A consequent addition, such as Value1 + Value2, would produce the following error:

[code]
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
TypeError: unsupported operand type(s) for +: ‘int’ and ‘Calcs’
[/code]

One way to overcome this problem would be to override the = operator.

After the code creates Value1, it demonstrates the use of the Inc() and Dec() methods. These two methods simply add or remove 1 from the value of Value1. If you want to change the actual value of Value1, you need to make Value1 equal to the output of the method like this:

[code]
Value1 = Value1.Inc()
[/code]

The next step is to create Value2, a second Calcs object you can use for binary operations. The code outputs the initial value of Value2. The remainder of the example demonstrates the use of the various operators. As you can see, they work precisely as you would expect. You could even use them to create a third value like this:

[code]
Value3 = Value1 + Value2
[/code]

Figure 17-6 shows the output from this example. Except for the absence of the ++ and — operators, everything works much as you would expect.

Using Visual Basic.NET for User Interface Support

It’s certainly possible to create message boxes and even Windows Forms applications using IronPython. The biggest issue is that IronPython lacks support for the designers that make the task of writing Windows Forms code so easy. You have to be able to picture the form you want in your mind and then use trial and error to get it to appear in the application. Consequently, most developers will probably want to use a language such as Visual Basic.NET to create their Windows Forms applications and then make those forms accessible from IronPython as part of an extension.

Here are the results of using the Visual Basic.NET extension within IronPython.
Figure 17-6: Here are the results of using the Visual Basic.NET extension within IronPython.

The examples in the sections that follow aren’t all that complicated, but they do demonstrate the principles required to build your own library of message boxes and Windows Forms classes. By the time you finish these examples, you’ll have everything needed to create your own user interface library for use in IronPython.

Creating the User Interface Library Module

From an IronPython perspective, user interface elements come in two forms: messages boxes and Windows Forms. Obviously, Visual Basic.NET can create a host of user interface presentations, but if you start at this basic level, you’ll find the task of creating a user interface library module easier. The following sections describe how to create both a message box class and a Windows Forms class that you place in a single DLL for use with your IronPython application. Of course, a production DLL could have hundreds of different forms, depending on the user interface requirements for the application.

Defining Simple Message Boxes

Message boxes (created using the MessageBox class) are extremely useful for displaying short messages and getting canned responses. Depending on the buttons you provide, a user could tell you that the application should retry an operation or answer yes to simple questions. If you need a little more input, you can always rely on an input box (created with the InputBox() method of the Interaction class). Of course, an input box is still limited to a single field, but even so, it does extend the kinds of input you can receive from the user.

Listing 17-3 demonstrates both the MessageBox.Show() and InputBox() methods. In addition, you’ll see how to implement the __doc__() method that most IronPython developers rely upon to obtain information about your extension.

Listin g 17-3: Working with simple message boxes

[code]
Imports System.Windows.Forms
Public Class Dialogs
Public Function ShowMessage(ByVal Msg As String) As String
Return MessageBox.Show(Msg).ToString()
End Function
Public Function ShowMessage(ByVal Msg As String, _
ByVal Title As String) As String
Return MessageBox.Show(Msg, Title).ToString()
End Function
Public Function ShowMessage(ByVal Msg As String, ByVal Title As String, _
ByVal Buttons As Int16) As String
Return MessageBox.Show(Msg, Title, CType(Buttons, MessageBoxButtons) _
).ToString()
End Function
Public Function ShowMessage(ByVal Msg As String, ByVal Title As String, _
ByVal Buttons As Int16, ByVal Icon As Int16 _
) As String
Return MessageBox.Show(Msg, Title, CType(Buttons, MessageBoxButtons), _
CType(Icon, MessageBoxIcon)).ToString()
End Function
Public Function ShowMessage(ByVal Msg As String, ByVal Title As String, _
ByVal Buttons As Int16, ByVal Icon As Int16, _
ByVal DefaultButton As Int16) As String
Return MessageBox.Show(Msg, Title, CType(Buttons, MessageBoxButtons), _
CType(Icon, MessageBoxIcon), _
CType(DefaultButton, MessageBoxDefaultButton) _
).ToString()
End Function
Public Function GetInput(ByVal Msg As String, ByVal Title As String)
Return InputBox(Msg, Title, “Type a value”)
End Function
Public Function __doc__() As String
Return “This is a help string”
End Function
End Class
[/code]

Before you can compile this code, you need to add a reference to System.Windows .Forms.DLL. Right-click Dialogs in Solution Explorer and choose Add Reference from the context menu. You’ll see the Add Reference dialog box shown in Figure 17-7. Highlight the System.Windows.Forms entry and click OK. At this point, you also need to add an Imports System.Windows.Forms entry to your project and you’re ready to work with message boxes.

Add the System.Windows.Forms.DLL entry to your project.
Figure 17-7: Add the System.Windows.Forms.DLL entry to your project.

The code begins by creating a series of ShowMessage() methods. The first is relatively simple and the complexity increases with each ShowMessage() method entry. Notice that the ShowMessage() method uses Int16 input values to select the buttons, icon, and default button. You could also use enumerations to provide input values. The one thing you don’t want to do is ask the IronPython developer to provide a MessageBoxButtons, MessageBoxIcon, or MessageBoxDefaultButton value, because then the IronPython developer would need to import all the required .NET Framework functionality, reducing the usefulness of your extension. The CType() function helps you convert the Int16 values into the appropriate enumeration value. Interestingly enough, there are 21 forms of the MessageBox .Show() method, even though the example shows only five of them.

The GetInput() method shows just one of several InputBox() method variations you can use. In this case, the IronPython developer supplies the prompt (or message) and title to display onscreen. The GetInput() method supplies a default InputBox() value. Normally, you want to supply a value so that the user knows to type something and what you want the user to type. Even if the required input seems obvious to you, many users won’t know what to provide.

The __doc__() provides a help string for the IronPython developer. The example shows something quick, but in reality, you’d provide complete documentation for your class. The output string can use all the standard formatting characters. You could even read the content in from an external source, such as a file, to make it easy to provide updates without having to recompile the extension. Using an external file would also allow the IronPython developer to personalize the content.

Defining Complex Forms

A Windows Forms class can contain anything you want. It can even call other forms as needed. In fact, anything you can do with a Visual Basic.NET Windows Forms application is doable with IronPython. Of course, you do need to maintain interaction with the IronPython application. The following steps describe how to create a simple Windows Forms class for your extension.

  1. Right-click Dialogs in Solution Explorer and choose Add ➪ New Item. Select the Windows Forms entry in the Installed Templates list. You see the Add New Item dialog box shown in Figure 17-8.

    Add a Windows Form to your project.
    Figure 17-8: Add a Windows Form to your project.
  2. Highlight the Windows Form entry. Type TestForm.VB in the Name field and click Add. Visual Studio adds the new form to your project and automatically opens it for editing.
  3. Create the form just as you normally would for any static application. Figure 17-9 shows the form used for this example. It’s simple, but it contains multiple data entry fields and multiple exit options.

The form shown in Figure 17-9 is a little deceptive. Before you assume anything about this form, it does have a few differences from the forms you’ve created for your static applications.

The Windows Form can contain any level of complexity you desire.
Figure 17-9: The Windows Form can contain any level of complexity you desire.
  • Buttons that close the form, rather than do something within the form, must have the DialogResult property set to a unique value or you won’t be able to tell which button the user clicked. For this example, the DialogResult for btnOK is OK, while the DialogResult for btnCancel is Cancel.
  • Getting information from the form you create to the IronPython application can prove problematic. You could contrive all sorts of odd methods for accomplishing the task, but the simplest method is to set the Modifiers property for the individual controls (txtName and txtColor) to Public. In this case, using Public doesn’t create a problem because IronPython sets everything to public. In all other respects, there’s no difference between this form and any other form you’ve created in the past.

To make things simple, this example doesn’t use any code-behind for the form itself. Any codebehind works as you’d expect. There isn’t any difference between calling the form from IronPython than calling it from within your Visual Basic.NET application.

Accessing the User Interface Library Module from IronPython

It’s time to use the extension you’ve created with an IronPython application. The following sections describe an alternative way to set up your project so that you don’t have to create the IronPython file using Windows Explorer and show how to use the extension.

An Alternative Method for Adding the IronPython Project

There are a number of ways to configure a test setup for your extensions. The “Adding the IronPython Project” section shows one technique. The technique shown in that section works well when you want to maintain separate builds of your extension. For example, you might want to maintain separate debug and release builds.

Unfortunately, that earlier method is a bit clumsy — you have to create the IronPython file using Windows Explorer. The technique in this section avoids that problem. In addition, this technique shows how to maintain just one build — the build you’re currently using for debugging, testing, or experimentation. Use the following steps to create a centralized test configuration:

  1. Right-click Dialogs in Solution Explorer and choose Properties from the context menu. Select the Compile tab. You’ll see the Properties window shown in Figure 17-10.

    Configure the build to use a central output location.
    Figure 17-10: Configure the build to use a central output location.
  2. Click Browse next to the Build Output Path field to display the Select Output Path dialog box shown in Figure 17-11. Because you’ll add the IronPython test file at the solution level, you need to send the output to the solution level as well.
  3. Select the first Dialogs entry in the list and click OK. Visual Studio adds an absolute path to the Output Path field that you must change for every machine that uses the application. As an alternative, you could type .. (two periods and a backslash) in the field to place the output in the solution folder.
  4. Select the next configuration in the Configuration field.
  5. Perform Steps 2 through 4 for each configuration. Make sure each configuration uses the same output directory. Normally, your project will contain only Debug and Release configurations.
  6. Right-click the solution entry in Solution Explorer and choose Add ➪ Existing Project from the context menu. You’ll see the Add Existing Project dialog box shown in Figure 17-3.
  7. Locate IPY.EXE on your hard drive and highlight it. Click Open. You’ll see a new project entry added to the solution.

     Modify the output path as required for your application.
    Figure 17-11: Modify the output path as required for your application.
  8. Right-click the ipy entry in Solution Explorer and choose Set as Startup Project from the context menu.
  9. Right-click the ipy entry in Solution Explorer and choose Properties from the context menu. You’ll see the General tab of the ipy Properties window shown in Figure 17-4.
  10. Type -D DialogTest.py in the Arguments field.
  11. Click the ellipses in the Working Directory field to display the Browse for Folder dialog box. Locate the solution folder for the project (the first Dialogs folder). Click OK. The IDE adds the correct directory information to the Working Directory field.
  12. Right-click the solution entry in Solution Explorer and choose Add ➪ New Item from the context menu. You see the Add New Item dialog box shown in Figure 17-12.

    Add the IronPython test file to your project.
    Figure 17-12: Add the IronPython test file to your project.
  13. Type DialogTest.py in the Name field and click Add. Visual Studio adds the new file to the Solution Items folder in Solution Explorer and opens the file automatically for editing.

Performing the Message Box and Form Tests

The example is ready except for the test code. Listing 17-4 shows the IronPython code you need for this example.

Listin g 17-4: Testing the message boxes and forms

[code]
# Define the message box tests.
def TestMessages():
# Create a message box object.
MyDialog = Dialogs.Dialogs()
# Show the help information.
print ‘Dialogs Class Help Information.’
print MyDialog.__doc__()
# Test a simple message box.
print ‘nTesting a simple message box.’
print ‘Simple message box output: ‘,
print MyDialog.ShowMessage(‘Hello’)
# Perform a more complex test.
print ‘nA more complex message box.’
print ‘Complex message box output: ‘,
print MyDialog.ShowMessage(‘Hello Again’, ‘Title 2’, 3, 64, 256)
# Get some user input.
print ‘nUsing an InputBox.’
print ‘InputBox Output: ‘,
print MyDialog.GetInput(‘Type Your Name:’, ‘User Name Entry’)
# Define the form test.
def TestForm():
# Create the form instance.
MyForm = Dialogs.TestForm()
# Display the form and test the dialog result.
print ‘nThe form example.’
if MyForm.ShowDialog().ToString() == ‘OK’:
# Display the results.
print ‘The user clicked OK.’
print ‘User Name: ‘, MyForm.txtName.Text
print ‘Favorite Color: ‘, MyForm.txtColor.Text
# Display an alternate result.
else:
print ‘The user clicked cancel.’
# Import the Common Language Runtime.
import clr
# Access the extension.
clr.AddReferenceToFile(‘Dialogs.DLL’)
import Dialogs
# Test the message box code.
TestMessages()
# Test the form code.
TestForm()
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The code begins by importing CLR support and then uses the AddReferenceToFile() to add a reference to the Dialogs.DLL. The next step is to import the Dialogs namespace for use. The __main__() function calls two functions, TestMessages() and TestForm(), to test the content of the Dialogs namespace. It then pauses so you can see the results.

The TestMessages() function begins by creating an instance of Dialogs.Dialogs, MyDialog. It then calls the MyDialog.__doc__() method to output the help information provided by the Dialogs class. Normally you’d use this method at the interactive console, but it’s good to see how the method works.

The next step is to test the MyDialog.ShowMessage() method. To keep you from clicking all afternoon, the test code uses just two forms of the method. The first form shows the simplest dialog box, while the second shows the most complex. The most complex dialog box (shown in Figure 17-13) contains a message, title, icon, and three buttons. Notice that the second button, rather than the first button, is selected by default. Normally, a message box selects the first button by default.

The complex message box can convey quite a bit of information for such a simple call.
Figure 17-13: The complex message box can convey quite a bit of information for such a simple call.

The next step is to display an input box. In this case, the MyDialog.GetInput() method displays an input box that contains a simple prompt and a title, as shown in Figure 17-14. Notice the default message in the input box. The input box automatically highlights this default entry so that the first thing the user types will erase the default content. The output from the MyDialog .GetInput() method is the text that the user types in the input box.

 Input boxes are good for small amounts of custom user input.
Figure 17-14: Input boxes are good for small amounts of custom user input.

The TestForm() function begins by creating an instance of the Dialogs.TestForm class, MyForm. The code then displays the dialog box shown in Figure 17-9 using the MyForm.ShowDialog() method. Notice that the example code adds a call to ToString(), so that the entire method call is MyForm.ShowDialog().ToString(). This is a technique for converting the System.Windows .Forms.DialogResult to a simple string that you can compare with the desired output, which is ‘OK‘ in this case.

When the call succeeds (the user clicks OK), the code prints the user’s name and favorite color. Notice that the code directly accesses both txtName.Text and txtColor.Text to obtain the required information. When the call fails (the user clicks Cancel), the code outputs a simple failure message. Figure 17-15 shows typical output from this example.

The IronPython output shows the results of the various dialog and form selections.
Figure 17-15: The IronPython output shows the results of the various dialog and form selections.

Using Visual Basic.NET for Database Support

Visual Basic.NET makes database management easy. Of course, there are all the handy designers that Visual Basic.NET makes available. The features of Server Explorer help as well. However, the fact that Visual Basic.NET tends to hide some of the details is what helps the most. The following sections provide a simple database management example that you could easily expand to help IronPython work with all sorts of data.

Obtaining and Configuring the Database

This example relies on an old standby, the Northwind database. Microsoft has passed this database by for significantly more complex examples, but Northwind remains unsurpassed in its ability to create useful examples with very little code, so it’s the database of choice for this chapter. You can download the Northwind database from http://www.microsoft.com/downloads/details .aspx?FamilyID=06616212-0356-46A0-8DA2-EEBC53A68034.

Make sure you have a database manager installed on your system. The Northwind database works just fine with versions of SQL Server as old as SQL Server 2000, but you should at least try a newer version, even if it’s SQL Server 2008 Express. The following steps tell you how to install the Northwind database.

  1. Double-click the SQL2000SampleDb.msi. You’ll see the normal Welcome dialog box for installing Microsoft products. Click Next. You’ll see the licensing agreement.
  2. Click I Agree after reading the license agreement, and then click Next. You’ll see an Installation Options dialog box. There aren’t any actual installation options.
  3. Click Next. You’ll see a Confirm Installation dialog box.
  4. Click Next. The installer installs the files into the C:SQL Server 2000 Sample Databases folder on your machine (you aren’t given a choice about the installation folder). After the installation is complete, you’ll see an Installation Complete dialog box.
  5. Click Close. The Northwind database and its associated script are now loaded on your machine.
  6. Open a command prompt in the C:SQL Server 2000 Sample Databases folder.
  7. Type OSQL -E -i InstNwnd.SQL and press Enter (the command line switches are case sensitive — make sure you type the command correctly). The OSQL utility will start building and installing the Northwind database. This process can take a while to complete — get a cup of coffee and enjoy. When the process is complete, you see a command prompt with a bunch of numbers on it and no error message, as shown in Figure 17-16.
The output from the OSQL utility doesn’t tell you much except if it encountered errors.
Figure 17-16: The output from the OSQL utility doesn’t tell you much except if it encountered errors.

Creating the Database Support Module

Creating a database support module is a multi-step process. At a minimum, you must first create a connection to the database and then work with that connection using code. The example that follows isn’t very complex. All that this example will do is retrieve some information from the database in the interest of keeping things simple. Even so, the basics shown in the example provide enough information for you to start creating database extensions of your own.

Creating a Connection to the Database

The first step in working with the Northwind database is to create a connection to it. The following steps describe how to perform this task.

  1. Right-click on the Data Connections entry in Server Explorer and choose Add Connection from the context menu. You may see the Choose Data Source dialog box shown in Figure 17-17. If not, you’ll see the Add Connection dialog box shown in Figure 17-18 and will need to proceed to Step 3.
  2. Highlight the Microsoft SQL Server entry. Select the .NET Framework Data Provider for SQL Server entry in the Data Provider field. Click Continue. You’ll see the Add Connection dialog box shown in Figure 17-18.

    Select the SQL Server data source to make the Northwind connection.
    Figure 17-17: Select the SQL Server data source to make the Northwind connection.
  3. Select or type the server name in the Server Name field. You can type a period (.) for the default server. The Add Connection dialog box automatically enables the Select or Enter a Database Name field.
  4. Select the Northwind database in the Select or Enter a Database Name field.
  5. Click Test Connection. You see a success message box (click OK to dismiss it).
  6. Click OK. Visual Studio displays the new connection in Server Explorer, as shown in Figure 17-19.
    The Add Connection dialog box lets you create and test a connection to the Northwind database.
    Figure 17-18: The Add Connection dialog box lets you create and test a connection to the Northwind database.

    The new connection appears in Server Explorer where you can work with it directly.
    Figure 17-19: The new connection appears in Server Explorer where you can work with it directly.
  7. Choose Data ➪ Add New Data Source. You’ll see the Data Source Configuration Wizard dialog box shown in Figure 17-20.

    Use the Data Source Configuration Wizard to create a coded connection.
    Figure 17-20: Use the Data Source Configuration Wizard to create a coded connection.
  8. Highlight Database and click Next. You’ll see the Choose Database Model page.
  9. Highlight the Dataset option and click Next. You’ll see the Choose Your Data Connection page. Notice that the Northwind database connection already appears in the connection field. The connection name will have your machine name, followed by the database name, followed by .dbo, such as main.Northwind.dbo. If it doesn’t, make sure you select it from the list. If the connection doesn’t appear in the list, click Cancel and start over with Step 1 because your connection wasn’t successful.
  10. Select the Northwind connection and click Next. The wizard will ask how you want to save the connection. There isn’t a good reason to change the default name provided.
  11. Click Next. You see the Choose Your Database Objects page shown in Figure 17-21.
  12. Check the Customers table entry, as shown in Figure 17-21. The example relies on the Customers table and none of the other database content. Click Finish. The new data source appears in the Data Sources window, as shown in Figure 17-22. If you can’t see this window, choose Data ➪ Show Data Sources.

Adding Database Manipulation Code

After all the work you performed to obtain access to the data, the actual database manipulation code is relatively easy. Listing 17-5 shows the small amount of code used to actually retrieve a particular record from the database based on the CustomerID field. Of course, you can add any level of complexity required.

Select the Customers table for this example.
Figure 17-21: Select the Customers table for this example.
The data source is ready to use in the example extension.
Figure 17-22: The data source is ready to use in the example extension.

Listin g 17-5: Retrieving data from the database

[code]
Public Function GetData(ByVal Customer As String) As _
NorthwindDataSet.CustomersRow
‘ Obtain access to the table.
Dim MyData As NorthwindDataSetTableAdapters.CustomersTableAdapter = _
New NorthwindDataSetTableAdapters.CustomersTableAdapter()
‘ Create a DataSet.
Dim DS As NorthwindDataSet.CustomersDataTable = _
New NorthwindDataSet.CustomersDataTable()
‘ Fill the DataSet with data.
MyData.Fill(DS)
‘ Find a particular record using the Customer ID.
Return DS.FindByCustomerID(Customer)
End Function
[/code]

The code begins by creating a TableAdapter object. Because the example relies on the Data Source Configuration Wizard, it has a specific TableAdapter to use in the form of the NorthwindDataSetTableAdapters.CustomersTableAdapter, MyData object. MyData provides the means to select information from the table. In addition, it can update, delete, and insert records. Essentially, MyData is the database connection.

The next step is to create a DataTable object. Again, the example has a specific version, NorthwindDataSet.CustomersDataTable class, DS object. DS contains all the data selected from the database through the TableAdapter object.

In order to get data from the database into the DataTable object, the code calls the MyData.Fill() method. Until the code calls this method, DS contains all of the information about the Customers table, but none of the records.

Finally, the code calls the DS.FindByCustomerID() method to find the record requested by the caller. The input argument to this method, Customer, is a string that contains the CustomerID field value. The output from the call is a NorthwindDataSet.CustomersRow object, which is a specialized form of the DataRow. Interestingly enough, IronPython can use the DataRow directly without having to translate it in any way.

Accessing the Database Module through IronPython

The example extension has a method, GetData(), that accepts a CustomerID as input and provides a NorthwindDataSet.CustomersRow as output. All you need now is some IronPython code to make the request and display the result. Listing 17-6 shows a typical example.

Listin g 17-6: Displaying a record onscreen

[code]
# Import the Common Language Runtime.
import clr
# Access the extension.
clr.AddReferenceToFile(‘Northwind.DLL’)
import Northwind
# Create an instance of the Northwind access object.
MyData = Northwind.DBAccess()
# Fill a row with data.
Row = MyData.GetData(‘ALFKI’)
# Display the data on screen.
print ‘All the data for Customer ID ALFKI’
print ‘nCustomer ID: ‘, Row.CustomerID
print ‘Company Name: ‘, Row.CompanyName
print ‘Contact Name: ‘,
if Row.IsContactNameNull():
print ‘Nothing’
else:
print Row.ContactName
print ‘Contact Title: ‘,
if Row.IsContactTitleNull():
print ‘Nothing’
else:
print Row.ContactTitle
print ‘Address: ‘,
if Row.IsAddressNull():
print ‘Nothing’
else:
print Row.Address
print ‘City: ‘,
if Row.IsCityNull():
print ‘Nothing’
else:
print Row.City
print ‘Region: ‘,
if Row.Is_RegionNull():
print ‘Nothing’
else:
print Row._Region
print ‘Postal Code: ‘,
if Row.IsPostalCodeNull():
print ‘Nothing’
else:
print Row.PostalCode
print ‘Country: ‘,
if Row.IsCountryNull():
print ‘Nothing’
else:
print Row.Country
print ‘Phone: ‘,
if Row.IsPhoneNull():
print ‘Nothing’
else:
print Row.Phone
print ‘Fax: ‘,
if Row.IsFaxNull():
print ‘Nothing’
else:
print Row.Fax
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

This listing looks like a lot of code, but the process is relatively simple. The example begins as usual by gaining access to CLR, using the AddReferenceToFile() method to create a reference to the extension, and creating an instance of the extension class.

At this point, the code calls MyData.GetData() with a CustomerID of ‘ALFKI‘. The output is placed in Row. If you use the dir() function on Row, you see it provides a lot more than a listing of fields that appear as part of the output. Figure 17-23 shows the attributes Row provides.

The output fields come in two types. The first are fields that the row must contain. These fields always contain data. The second are optional fields that might not contain data. If you try to print these fields, you’ll get an error. Consequently, the next section of code displays the mandatory fields first.

Row contains more than just fields.
Figure 17-23: Row contains more than just fields.

Notice the if…else structures that appear next. Every optional field includes an IsFieldNameNull() method. Before you print these optional fields, use the null check, such as Row.IsContactNameNull(), to verify that the field contains data. In this case, the code simply prints ‘Nothing‘ when the field is null.

You need to consider one other issue when working through your database access methods. Notice that the _Region field has an underscore in front of it. This underscore doesn’t appear in the database or in the Visual Basic.NET code — IronPython adds it for some reason. If you suddenly find that some fields aren’t accessible, even though you’re using the right name, check for an underscore. Figure 17-24 shows the output from this example.

The extension provides data to IronPython to output.
Figure 17-24: The extension provides data to IronPython to output.

 

Extending IronPython Using C#

Understanding the Requirements for an Extension

It’s important to understand that an extension, any extension, probably ties your code to Windows. Whenever you use an extension with IronPython, you rely on something other than the Python libraries to perform a task, which means you lose the platform independence for which Python is so famous. In short, extensions provide considerable flexibility and help you provide additional capabilities for IronPython, but this flexibility isn’t without cost. Every time you make a design decision of this sort, you must pay a price in the following:

  • Reduced reliability: Due to increased failure points.
  • Weakened security: More languages mean more places where someone could leave a security hole.
  • Impaired speed: Marshaling data between language barriers takes time.
  • Fewer platforms: In order to use an extension, you must find a platform that supports both IronPython and the extension language.

Writing an extension isn’t always straightforward. It isn’t as simple as writing some class library code and putting it in a DLL. In fact, you must spend considerable effort thinking about how an extension should be designed to make it useable. The following list considers just a few of the most important factors for your extension.

  • Python language requirements: IronPython may not support every feature that the static language supports. For example, you may find that IronPython doesn’t support a particular static language operator, such as the ++ operator.
  • IronPython developer mentality: An extension that performs tasks in a way that runs completely counter to the way that an IronPython developer normally does them isn’t very useful, because the IronPython developer will have to think too hard about using the extension. The best kind of extension is one that feels natural to the IronPython developer.
  • Flexibility: An extension should provide some significant advantage in flexibility. When you write an extension, write it with the benefit to the IronPython developer in mind, not simply because the functionality the extension provides is interesting.

The one factor that you don’t need to consider is whether something is doable. Normally, if you can perform a task with the static language you want to use to build the extension, then you can do it with IronPython as well. Sometimes, you have to massage the data or present the technique in a way that doesn’t match your normal methodology, but you can normally perform the task with a bit of effort.

Considering IronPython and Static Language Differences

IronPython is a dynamic language (a language that does things like decide variable type at run time, which is contrasted with a static language that decides everything during compile time). As such, it has some significant advantages for the human developer that a static language can’t provide. It’s true that the concept of language is foreign to the computer, but the human developer relies on certain characteristics of language to accomplish tasks quickly and with few errors. Consequently, as part of defining the reason to use an extension, you must consider the differences between IronPython and the static language of your choice.

Defining Why You Use a Static Language with IronPython

Typically, you use a static language with IronPython to gain a specific advantage. For example, IronPython doesn’t create graphical user interfaces very well, so using a static language to perform this task could provide a significant advantage in development time. In addition, you could probably reuse code that you already have on hand, which may reduce debugging time as well. Look for the advantages that you can gain when using a static language with IronPython. If you have problems describing the material benefit of an extension, then perhaps you really should look at another solution.

Make sure you consider the strengths of the static language when making your selections. For example, C# is often the best choice for Win32 API interaction because it supports unsafe pointers — a requirement for certain specialized Win32 API tasks. Of course, you should make sure that the use of the Win32 API is actually required. Perhaps a third-party library already has the solution you require and with a lot less work. Visual Basic.NET is often the best choice for database work because it takes care of so many tasks in the background for the developer. You don’t have to worry so much about coercing data types because Visual Basic addresses the need for you in the background.

Sometimes the use of a static language is practical. For example, you might have an overwhelming number of developers on your team who know C# or Visual Basic.NET, but know nothing about IronPython. In general, this is one of the poorest reasons to use a static language with IronPython, but the reality of development today is that you often use the tools you have on hand to accomplish the task. No one can afford to have developers sitting on their hands simply because the dynamic language is the best choice for a particular job.

Understanding Line Noise

There are good reasons to avoid using a static language with IronPython. You can write most code in IronPython using far fewer lines than a static language requires. Fewer lines of code translate into higher developer productivity and sometimes into fewer coding errors as well.

The additional code that a static code developer must write is often referred to as line noise. The code doesn’t substantially translate into useful output, but the static language requires it. For example, IronPython doesn’t require that you declare the type of a variable — you simply leave this task to IronPython.

While the extra code in a static language does tend to reduce the potential for unintended output, it can also make the code harder to read. With every benefit, there’s a corresponding negative. When you decide to use an extension with IronPython, you need to consider when it’s appropriate to work through the extra code and cumbersome features of static languages and when IronPython is truly the better choice.

Let’s look at a quick example. Say you want to create an array of names in a function and pass them back to a caller. Here’s the C# code to perform the task.

[code]
public String[] GetNames()
{
String[] Result = new String[4];
Result[0] = “John”;
Result[1] = “Amy”;
Result[2] = “Jose”;
Result[3] = “Carla”;
return Result;
}
public void ShowNames()
{
String[] TheNames = GetNames();
foreach (String Name in TheNames)
{
Console.WriteLine(Name);
}
}
[/code]

The code in GetNames() creates an array of String, fills it with names, and returns those names to the caller, ShowNames(). At this point, ShowNames() uses a foreach loop to display each name individually. Now take a look at the same functionality written in IronPython.

[code]
def GetNames():
return “John”, “Amy”, “Jose”, “Carla”
def ShowNames():
for Name in GetNames():
print Name
[/code]

The code performs the same task in both cases, but as you can see, the IronPython code is significantly shorter. In addition, the IronPython code is actually easier to read.

Considering Scoping Issues

One of the most important differences between IronPython and static languages such as C# is that IronPython doesn’t have the concept of scope within classes. Everything in an IronPython class is public, so you always have access to every element. Of course, this presents a dilemma for languages that do support scope. When creating an IronPython extension, your static language scope declarations will change as follows:

  • Public members remain public.
  • Protected members become public.
  • Protected Internal members become public.
  • Private members remain private and don’t appear at all to IronPython.
  • Internal members become private and don’t appear at all to IronPython.

Creating the Simple C# Extension

The example in the following sections provides a simple set of calculations. Think of it as the basic four-function calculator with a bit extra added. The example doesn’t do anything fancy, but it does demonstrate techniques you need to build any C# extension for IronPython. The rest of the examples in this chapter build on this example, so you should at least scan the techniques presented in the sections that follow.

Creating the Project

A C# extension project in Visual Studio is nothing more than the typical class library. The following steps help you create the project for this example. You can use the same steps when working with the other examples — all you need to do is change the project name.

  1. Choose File ➪ New ➪ Project. You see the New Project dialog box shown in Figure 16-1.

    Create a new project to hold your C# extension.
    Figure 16-1: Create a new project to hold your C# extension.
  2. Choose the Visual C# folder in the Installed Templates list.
  3. Select .NET Framework 3.5 or an earlier version of the .NET Framework. Don’t select the .NET Framework 4.0 entry. The list of templates changes when you change the .NET Framework version.
  4. Select the Class Library template.
  5. Type Calcs in the Name field and click OK. Visual Studio creates a class library project for you.
  6. Right-click Class1.cs in Solution Explorer and choose Rename from the context menu. Visual Studio makes the filename editable.
  7. Type Calcs.CS for the new filename and press Enter. Visual Studio displays a dialog box that asks whether you’d like to rename all of the Class1.cs references to match the new filename.
  8. Click Yes. The project is ready for use.

At the time of this writing, IronPython doesn’t support extensions written using the .NET Framework 4.0. You must create your extensions using the .NET Framework 3.5 or earlier. Otherwise, the extension will simply fail to load and IronPython won’t provide anything in the way of an explanation (at least, nothing useable). If you suspect that you’ve targeted the wrong .NET Framework version, choose Project ➪ ProjectName Properties. Select the Application tab of the Properties window and change the entry in the Target Framework field to .NET Framework 3.5, as shown in Figure 16-2. The IDE may ask permission to modify features in your setup and require that you restart your project to see the effects of the change.

Modify the Target Framework field to a version of the .NET Framework that works with IronPython.
Fi gure 16-2: Modify the Target Framework field to a version of the .NET Framework that works with IronPython.

Developing the C# Extension

The C# extension does have a few tricks to it, but generally speaking, if you know how to create a class library, you already know how to create the code for a C# extension. Listing 16-1 shows the code for the example extension.

Listin g 16-1: A simple calculations extension

[code]
public class Calcs
{
private Int32 Data;
public Calcs(Int32 Value)
{
this.Data = Value;
}
public override string ToString()
{
return Data.ToString();
}
public static Calcs operator +(Calcs Value1, Calcs Value2)
{
return new Calcs(Value1.Data + Value2.Data);
}
public static Calcs operator -(Calcs Value1, Calcs Value2)
{
return new Calcs(Value1.Data – Value2.Data);
}
public static Calcs operator *(Calcs Value1, Calcs Value2)
{
return new Calcs(Value1.Data * Value2.Data);
}
public static Calcs operator /(Calcs Value1, Calcs Value2)
{
return new Calcs(Value1.Data / Value2.Data);
}
public Calcs Inc()
{
return new Calcs(this.Data + 1);
}
public Calcs Dec()
{
return new Calcs(this.Data – 1);
}
}
[/code]

In most cases, you want to create a constructor that accepts the kind of data you want to manipulate with the extension. In this case, the constructor accepts an Int32 value. Interestingly enough, the constructor is the only place where you normally reference the data type of the data directly. In all other cases, you work with the data type indirectly by using the extension class.

Another issue is displaying the data in IronPython. The default implementation of the ToString() method displays the class name, which isn’t helpful. Consequently, you must override the default implementation of ToString() and provide your own output. In this case, the method simply returns the current value of the private variable Data as a string.

This example deals with operators. Of course, there are two kinds of operators, unary and binary. The method you implement for each kind of operator is different.

To create a binary operator, you must consider that the operator will work with two instances of the Calcs class. In short, the operator works with the base class and you must declare it as static. In this example, the + operator is binary, so the code declares it as static. The method also accepts the two instances of the Calcs class as input. In order to return output, the method must create a new instance of the Calcs class with the sum of the two input values. Notice that the method never defines what kind of data it works on, simply that the data is contained in an instance of the Calcs class.

Creating a unary operator is different because you’re working with a single instance of the Calcs class in this instance. To create a unary operator, you simply declare the method as a non-static member of the class, as shown for the Inc() and Dec() methods. In this case, because you’re working with a single value, the code uses this.Data (the internal representation of the data value of the single value) to perform the math. You may wonder why the code simply doesn’t create a ++ operator method. A ++ operator method would look like this and wouldn’t work in a unary manner within IronPython.

[code]
public static Calcs operator ++(Calcs Value1)
{
return new Calcs(Value1.Data + 1);
}
[/code]

If you compiled the class now, you could view it in the IronPython console. The following code provides the steps for loading the extension into memory.

[code]
import clr
clr.AddReferenceToFile(‘Calcs.DLL’)
import Calcs
dir(Calcs.Calcs)
[/code]

Figure 16-3 shows the output of the dir(Calcs.Calcs) call. Notice that Inc() and Dec() appear as you expect. However, there aren’t any entries for +, -, *, and / methods. These operators still work as you expect, but IronPython shows a Python equivalent for the operators in the form of the __add__(), __radd__(), __sub__(), __rsub__(), __mul__(), __rmul__(), __div__(), and __rdiv__(). These methods don’t appear unless you define the operators in your class.

If you’re looking at the class in the IronPython console, you might want to give it a quick try before you close up the console and move on to the next part of the example. Try this code and you’ll see an output of 15 from the __add__() method.

[code]
Value1 = Calcs.Calcs(10)
Value2 = Calcs.Calcs(5)
print Value1.__add__(Value2)
[/code]

The dir() function shows the content of the Calcs class.
Figure 16-3: The dir() function shows the content of the Calcs class.

Adding the IronPython Project

At this point, you have a C# extension (or module) to use with IronPython. Of course, you’ll want to test it. The easiest way to do this is to add the IronPython project directly to the current solution. The following steps describe how to perform this task.

  1. Right-click the solution entry in Solution Explorer and choose Add ➪ Existing Project from the context menu. You see the Add Existing Project dialog box shown in Figure 16-4.

    Locate IPY.EXE and add it to your solution.
    Figure 16-4: Locate IPY.EXE and add it to your solution.
  2. Locate IPY.EXE on your hard drive and highlight it. Click Open. You see a new project entry added to the solution.
  3. Right-click the ipy entry in Solution Explorer and choose Set as Startup Project from the context menu. This step ensures that choosing one of the startup options from the Debug menu starts the IronPython application.
  4. Right-click the ipy entry in Solution Explorer and choose Properties from the context menu. You’ll see the General tab of the ipy Properties window shown in Figure 16-5.

    Configure the IronPython application to work with Calcs.DLL.
    Figure 16-5: Configure the IronPython application to work with Calcs.DLL.
  5. Type -D TestCalcs.py in the Arguments field.
  6. Click the ellipses in the Working Directory field to display the Browse for Folder dialog box. Locate the output folder of the Calcs.DLL (or other extension) file. Click OK. The IDE adds the correct directory information to the Working Directory field.
  7. Open Windows Explorer. Locate the CalcsCalcsbinDebug folder. Right-click in the right pane and choose New ➪ Text Document from the context menu. Name the file TestCalcs.py and press Enter. Click Yes if asked if you want to rename the file extension.
  8. Right-click the solution item in Solution Explorer and choose Add ➪ Existing Item from the context menu to display the Add Existing Item dialog box shown in Figure 16-6.
  9. Locate the TestCalcs.py file in the solution and click Add. Visual Studio adds TestCalcs.py to the Solution Items folder in Solution Explorer and automatically opens the file for you. You’re ready to add test code for the application.
Add the TestCalcs.py file to the solution.
Figure 16-6: Add the TestCalcs.py file to the solution.

Creating the IronPython Application

Now that you have a file to use for the IronPython application, it’s time to add some code to it. The example code fully exercises everything you can do with the C# extension. Listing 16-2 shows the code you add to the TestCalcs.py file.

Listin g 16-2: Testing the extension using IronPython

[code]
# Add a reference to the CLR
import clr
# Obtain access to the extension.
clr.AddReferenceToFile(‘Calcs.DLL’)
import Calcs
# Create an instance of the class and fill it with data.
Value1 = Calcs.Calcs(10)
# Print the original value, then decrement and increment it.
print ‘Original Value1 Content: ‘, Value1
print ‘Value1 + 1: ‘, Value1.Inc()
print ‘Value1 – 1: ‘, Value1.Dec()
# Create a second value and display it.
Value2 = Calcs.Calcs(5)
print ‘nOriginal Value2 Content: ‘, Value2
# Use the two values together in different ways.
print ‘nValue1 + Value2 = ‘, Value1 + Value2
print ‘Value1 – Value2 = ‘, Value1 – Value2
print ‘Value1 * Value2 = ‘, Value1 * Value2
print ‘Value1 / Value2 = ‘, Value1 / Value2
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)

[/code]

The example begins by importing support for the Common Language Runtime (CLR). It then uses the AddReferenceToFile() method to reference the Calcs.DLL file and imports the code into IronPython. These steps are similar to those that you used to test the DLL initially.

The next step is to create an instance of the Calcs class, Value1. The code references Calcs twice — once for the namespace and a second time for the class itself. The next few code steps display the value of Value1 and show how to use the Inc() and Dec() methods. If you set Value1 equal to the output of Inc() or Dec(), it truly would increment or decrement the value of Value1. Because IronPython doesn’t support the ++ operator, however, you can’t use the ++ operator in your extension. On the other hand, you could implement the += and -= operators.

You can’t really test binary operators without a second variable, so the code creates a second instance of Calcs, Value2. The example then shows how the +, -, *, and / operators work. Figure 16-7 shows the output from this example.

Here are the results of using the C# extension within IronPython.
Figure 16-7: Here are the results of using the C# extension within IronPython.

Using C# for User Interface Support

It’s a painful process because you don’t have access to any designers, but the process is definitely doable. You may very well decide to use IronPython directly for all your graphics needs, simply to avoid using another language. However, C# or Visual Basic.NET make better choices for creating a user interface because you do get access to the designer support that these languages provide. With this in mind, the following sections describe how you can add graphic support to IronPython using a C# extension.

Defining a Library of Dialog Boxes

If you’re using IronPython as your main application language and relying on a static language for ancillary support, such as the user interface requirements, it makes sense to create all the dialog boxes you require and place them in a library. Of course, if the application is relatively complex, you might use several physical DLLs to perform the task or rely on a single DLL, but rely on multiple projects to accommodate a team of developers The point is that you need to plan how to store the dialog boxes in a manner that makes it efficient to work on the project.

There’s a tendency by some developers to create generic dialog boxes and then manipulate them in code. This technique does work well when you use the dialog boxes in the static language. However, the approach can become counterproductive when using the dialog boxes in IronPython. The IronPython code can become so complicated that it becomes unreliable and hard to maintain. In general, use specific dialog boxes whenever possible, which won’t require many (or any) changes.

IronPython doesn’t have a representation of every C# or Visual Basic.NET feature. For example, in the section “Developing the C# Extension” section earlier in this chapter, you’ll discover that IronPython doesn’t support the C# ++ operator, but it does support the += operator. It’s best to perform data manipulation in the static language environment when possible or pass the raw data to IronPython in a form it can readily use. For example, you might pass a list of field values to IronPython as a dictionary.

Marshaling data between languages can reduce application performance. You may find situations where you need to process data in a thread to maintain acceptable performance for the user. However, before you take time to create a complex threading solution, ask users to try the application in a test environment to determine whether the performance is acceptable.

Creating the Dialog Box Library in C#

Your dialog box library can support dialog boxes at two levels. It’s possible to meet some IronPython needs using a simple message box or prompt box. Because these solutions are already programmed for you, supporting them through the static language, where the features are easily accessed, is a good way to save on development and debugging time. You can customize the implementation of these standardized features to make them easy to use within IronPython — reducing the need to import a lot of managed assemblies into IronPython.

Of course, many user-interface needs require something more advanced than a simple message box. The following sections describe how to create simple message boxes and complex Windows Forms in C# that you can use in your IronPython application. The goal is to use the right kind of interface element for a given task and to make the interface element easy to access and process from within IronPython. The section “Creating the Simple C# Extension” earlier in this chapter describes how to set up the solution used for this example.

Defining Simple Message Boxes

This example is interesting because it shows how you can create overrides of your methods. The MessageBox.Show() method has 21 overrides in C#. Of course, you might not need all those overrides and the example shows only five of them. Before you can work with message boxes in a C# class, you need to add a reference to the System.Windows.Forms.DLL and add the following using statement.

[code]
using System.Windows.Forms;
[/code]

Now that you have the prerequisites in place, it’s time to look at some code. Listing 16-3 shows the code used to create this example.

Listin g 16-3: Creating a simple message box class

[code]
public class Dialogs
{
public Dialogs()
{
}
public String ShowMessage(String Msg)
{
return MessageBox.Show(Msg).ToString();
}
public String ShowMessage(String Msg, String Title)
{
return MessageBox.Show(Msg, Title).ToString();
}
public String ShowMessage(String Msg, String Title, Int16 Buttons)
{
return MessageBox.Show(Msg, Title,
(MessageBoxButtons)Buttons).ToString();
}
public String ShowMessage(String Msg, String Title, Int16 Buttons,
Int16 Icon)
{
return MessageBox.Show(Msg, Title,
(MessageBoxButtons)Buttons, (MessageBoxIcon)Icon).ToString();
}
public String ShowMessage(String Msg, String Title, Int16 Buttons,
Int16 Icon, Int16 DefaultButton)
{
return MessageBox.Show(Msg, Title,
(MessageBoxButtons)Buttons, (MessageBoxIcon)Icon,
(MessageBoxDefaultButton)DefaultButton).ToString();
}
}
[/code]

The code begins with the usual constructor. The constructor doesn’t really need to do anything in this case. Of course, you could set up the constructor to accept some of the required inputs, such as the message and message box title, but sending the information with the ShowMessage() method works just fine, too. The constructor could also set up default settings, if desired, that the developer could override with specific versions of ShowMessage().

The ShowMessage() method declarations come next. The methods are relatively simple. Each one calls a different override of the MessageBox.Show() method. Notice that you must coerce the MessageBoxButtons, MessageBoxIcon, MessageBoxDefaultButton values from the inputs. You could ask the caller to provide the actual enumerated values, but that approach would reduce the benefit of using this approach for working with message boxes, because the developer would need to load the required .NET assemblies anyway.

Even when working with simple message boxes, you can encounter a few problems. For example, the enumerations provided in the static environment make it simple to select a particular button combination or icon. IntelliSense displays the list of values from which you can choose. However, IronPython doesn’t provide IntelliSense, so there isn’t any simple method of selecting a button combination or icon from a list. The example uses numbers, which works fine for the button combinations because they’re numbered 0 through 5. However, the icons have values of 0, 16, 32, 48, and 64, which are hardly easy to remember. The default button values are equally odd at 0, 256, and 512. Tables 16-1 through 16-3 show the values for the message box enumerations. In a production environment, you’d probably create text equivalents for the developer, which you could translate in the extension, or provide some type of enumeration for the developer.

Message Box Button Combinations
Table 16-1: Message Box Button Combinations

 

Table 16-2: Message Box Icon Combinations
Table 16-2: Message Box Icon Combinations
Table 16-2: Message Box Icon Combinations
Table 16-2: Message Box Icon Combinations (continued)
Message Box Default Button Options
Table 16-3: Message Box Default Button Options

Using Enumerations with IronPython

There’s a way around the issue of enumerated values in .NET calls. You can simply choose to create your own enumeration. For example, let’s say you want to overcome the problem of working with the MessageBoxButtons enumeration. In this case, you create an enumeration and a new override of the ShowMessage() method as shown here.

[code]
public enum ButtonTypes
{
OK,
OKCancel,
AbortRetryIgnore,
YesNoCancel,
YesNo,
RetryCancel
}
public String ShowMessage(String Msg, String Title, ButtonTypes Buttons)
{
return MessageBox.Show(Msg, Title,
(MessageBoxButtons)Buttons).ToString();
}
[/code]

Notice that you must still use coercion to make the MessageBox.Show() call. However, the IronPython developer now has an enumeration to use when making the call. Here’s a typical call from within IronPython.

[code]
MyDialog.ShowMessage(‘Hello’, ‘Title’, MyDialog.ButtonTypes.OKCancel)
[/code]

The resulting message box would contain ‘Hello‘ as the message, ‘Title‘ as the message box title, and two buttons, OK and Cancel.

Considering Developer Help

As your extensions gain in complexity, you need to start providing some help to the IronPython developer. Most IronPython developers will spend part of their time in the interpreter trying things out. The developer will look to your documentation for help in using the extension you create. There are two forms of help, as shown here.

[code]
help(MyDialog.ShowMessage)
MyDialog.ShowMessage.__doc__()
[/code]

It turns out that IronPython automatically provides a form of the help() function help for you as shown in Figure 16-8. In this case

IronPython provides a kind of help for you automatically.
Figure 16-8: IronPython provides a kind of help for you automatically.

Unfortunately, IronPython doesn’t provide the __doc__() method by default. You must define it for yourself as part of the class you create. Here’s a simple __doc__() method you can use with the example. Of course, a production version would contain far more information.

[code]
public String __doc__()
{
return “This is a help string”;
}
[/code]

When you try this method out at the Python prompt, you see the outline shown in Figure 16-9. You can use all of the normal formatting characters to make the help provided by the __doc__() method look nice. For that matter, you could store the information externally and simply read it in as needed.

 You must define your own version of the __doc__() method.
Figure 16-9: You must define your own version of the __doc__() method.

Defining Complex Forms

At some point, simple message boxes simply won’t do the job for you. After all, you’ll want forms that contain a number of fields that you can use to process complex information from the user. In this case, you must create a standard Windows form for your extension. To accomplish this task, you begin by adding the form using the following steps.

  1. Right-click Dialogs in Solution Explorer and choose Add ➪ New Item. Select the Windows Forms entry in the Installed Templates list. You see the Add New Item dialog box shown in Figure 16-10.

    Add a Windows Form to your project
    Figure 16-10: Add a Windows Form to your project
  2. Highlight the Windows Form entry. Type TestForm.CS in the Name field and click Add. Visual Studio adds the new form to your project and automatically opens it for editing.

At this point, you can create the form just as you normally would for any static application. Figure 16-11 shows the form used for this example. It’s simple, but it contains multiple dataentry fields and multiple exit options.

The Windows Form can contain any level of complexity you desire.
Figure 16-11: The Windows Form can contain any level of complexity you desire.

Before you assume anything about this form, note that it does differ in a few ways from the forms you’ve created for your static applications. The first difference is that the buttons that close the form, rather than do something within the form, must have the DialogResult property set to a unique value or you won’t be able to tell which button the user clicked. For this example, the DialogResult for btnOK is OK, while the DialogResult for btnCancel is Cancel.

The second difference involves a problem with getting information from the form you create to the IronPython application. You could contrive all sorts of odd methods for accomplishing the task, but the simplest method is to set the Modifiers property for the individual controls (txtName and txtColor) to Public. In this case, using Public doesn’t create a problem because IronPython sets everything to public. In all other respects, there’s no difference between this form and any other form you’ve created in the past.

To make things simple, this example doesn’t use any code-behind for the form itself. Any codebehind works as you’d expect. There isn’t any difference between calling the form from IronPython than calling it from within your C# application.

Accessing the Dialog Box Library from IronPython

At this point, you have a nice collection of dialog box and form classes to use in an IronPython application. Of course, a production application would probably have quite a few more forms in it, but you have enough for testing and experimentation purposes. The following sections describe how to use these classes.

An Alternative Method for Adding the IronPython Project

There are a number of ways to configure a test setup for your extensions. The section “Adding the IronPython Project” earlier in this chapter shows one technique. The technique works well when you want to maintain separate builds of your extension. However, you might want to maintain just one build — the build you’re currently using for debugging, testing, or experimentation. Use the following steps to create a centralized test configuration.

  1. Right-click Dialogs in Solution Explorer and choose Properties from the context menu. Select the Build tab. You see the Properties window shown in Figure 16-12.
  2. Click Browse next to the Output Path field to display the Select Output Path dialog box shown in Figure 16-13. Because you’ll add the IronPython test file at the solution level, you need to send the output to the solution level as well.

    Configure the build to use a central output location.
    Figure 16-12: Configure the build to use a central output location.
  3. Select the first Dialogs entry in the list and click OK. Visual Studio adds an absolute path to the Output Path field that you must change for every machine that uses the application. As an alternative, you could type ..(two periods and a backslash) in the field to place the output in the solution folder.
  4. Select the next configuration in the Configuration field.
  5. Perform Steps 2 through 4 for each configuration. Make sure each configuration uses the same output directory. Normally, your project will only contain Debug and Release configurations.

    Modify the output path as required for your application.
    Figure 16-13: Modify the output path as required for your application.
  6. Right-click the solution entry in Solution Explorer and choose Add ➪ Existing Project from the context menu. You see the Add Existing Project dialog box shown in Figure 16-4.
  7. Locate IPY.EXE on your hard drive and highlight it. Click Open. You’ll see a new project entry added to the solution.
  8. Right-click the ipy entry in Solution Explorer and choose Set as Startup Project from the context menu.
  9. Right-click the ipy entry in Solution Explorer and choose Properties from the context menu. You see the General tab of the ipy Properties window shown in Figure 16-5.
  10. Type -D DialogTest.py in the Arguments field.
  11. Click the ellipses in the Working Directory field to display the Browse for Folder dialog box. Locate the solution folder for the project (the first Dialogs folder). Click OK. The IDE adds the correct directory information to the Working Directory field.
  12. Right-click the solution entry in Solution Explorer and choose Add ➪ New Item from the context menu. You see the Add New Item dialog box shown in Figure 16-14.

    Add the IronPython test file to your project.
    Figure 16-14: Add the IronPython test file to your project.
  13. Type DialogTest.py in the Name field and click Add. Visual Studio adds the new file to the Solution Items folder in Solution Explorer and opens the file automatically for editing.

Performing the Message Box and Form Tests

It’s finally time to test the message boxes and forms you’ve created. The code in this section performs a few simple tests and demonstrates how to obtain output from the message boxes and forms you’ve created. You can use this code as a starting point for more complex processing in your own application. Listing 16-4 shows the test code for this application.

Listin g 16-4: Testing the extension using IronPython

[code]
# Define the message box tests.
def TestMessages():
# Create a message box object.
MyDialog = Dialogs.Dialogs()
# Test a simple message box.
print ‘Testing a simple message box.’
print ‘Simple message box output: ‘,
print MyDialog.ShowMessage(‘Hello’)
# Perform a more complex test.
print ‘nA more complex message box.’
print ‘Complex message box output: ‘,
print MyDialog.ShowMessage(‘Hello Again’, ‘Title 2’, 3, 64, 256)
# Define the form test.
def TestForm():
# Create the form instance.
MyForm = Dialogs.TestForm()
# Display the form and test the dialog result.
print ‘nThe form example.’
if MyForm.ShowDialog().ToString() == ‘OK’:
# Display the results.
print ‘The user clicked OK.’
print ‘User Name: ‘, MyForm.txtName.Text
print ‘Favorite Color: ‘, MyForm.txtColor.Text
# Display an alternate result.
else:
print ‘The user clicked cancel.’
# Import the Common Language Runtime.
import clr
# Access the extension.
clr.AddReferenceToFile(‘Dialogs.DLL’)
import Dialogs
# Test the message box code.
TestMessages()
# Test the form code.
TestForm()
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The test code begins by importing CLR and gaining access to the Dialogs namespace. This example demonstrates one of the benefits of using a namespace, easy access to multiple classes. It’s a good way to organize a library of forms to make them easy to access and to avoid naming conflicts.

The TestMessages() function contains the code to test the Dialogs.Dialogs class. This code begins by creating a Dialogs.Dialogs instance, MyDialog. In this case, the application begins by creating a simple message box and displaying it onscreen. This message box lacks a title and contains only an OK button. When the user clicks OK, the program prints the dialog result to screen.

The second test is a little more complex. This time the code relies on the most complex form of the ShowMessage() method to display a dialog box that contains a message, title, icon, and multiple buttons as shown in Figure 16-15. Notice that the figure shows that the message box also has the middle button selected by default. Pressing Enter will automatically select this default option. Normally, message boxes select the first button as the default. Depending on which button the user clicks, the application will display a message with the appropriate dialog result. You could also use this dialog result as part of an if…else statement to choose an appropriate course of action.

A more complex message box includes multiple buttons, a title, and an icon.
Figure 16-15: A more complex message box includes multiple buttons, a title, and an icon.

The TestForm() method begins by creating an instance of Dialogs. TestForm, MyForm. The dir() function will show you that MyForm now has access to all of the functionality normally associated with a Windows Forms class, but without importing any of the bulk associated with the System.Windows .Forms assembly. As with any Windows Form, you call ShowDialog() to display the form. However, the result of displaying the form is going to be something that IronPython can’t use directly. The way to overcome this problem is to call ShowDialog().ToString(). In this case, the output is a string that describes which button the user has clicked.

This portion of the example shows how to process the form data locally. When the user clicks OK, the dialog result is ‘OK‘ and the if statement succeeds. The code accesses the MyForm.txtName.Text and MyForm.txtColor.Text properties to determine what the user has typed. When the if statement fails, the code displays a message telling you that the user clicked Cancel. Figure 16-16 shows typical output from this example.

Here are the results of using the C# extension within IronPython.
Figure 16-16: Here are the results of using the C# extension within IronPython.

Using C# for Win32 Support

The Python language doesn’t really support much in the way of platform-specific functionality and that’s by design. One of the tenets of cross-platform compatibility is not to make an issue out of the platform on which the code runs. However, in some cases, you really do need to access the platform and discover things about it. For example, you might want to know more about the environment in which your application is executing, such as the size of the console window. You might even want to clear the console window (a feature that is missing from the IronPython console, without which your sessions can appear messy). An application may need to know something about the security in place for the current session. In short, you might have many reasons for wanting to know something more, but Python (and by extension, IronPython) largely lacks the functionality to provide this information.

The example in the following sections plays to a strength of C#, which is to interact with the Windows platform through a feature called Platform Invoke (P/Invoke). This example goes outside the managed .NET environment and relies on the Win32 API to access Windows functionality that you can’t access through .NET.

Creating the P/Invoke Code

Before you can write any P/Invoke code, you need to add the following using statement.

[code]
using System.Runtime.InteropServices;
[/code]

This statement provides access to the various special programming features that C# provides for accessing the Win32 API.

If you haven’t worked with the Win32 API in the past, you might find the use of structures, enumerations, and pointers confusing. In reality, all these events take place somewhere in the background when you execute any application. At some point, your managed code ends up interacting with the Win32 API to perform tasks because the basic Windows DLLs still rely on the Win32 API. Normally, CLR hides all these details from view so you don’t need to worry about them. Listing 16-5 shows the Win32 API access code — the lower-level code that does all the hard work for this example.

Listin g 16-5: Win32 API access code and structures

[code]
// This special class contains an enumeration of
// standard handles.
class StdHandleEnum
{
public const int STD_INPUT_HANDLE = -10;
public const int STD_OUTPUT_HANDLE = -11;
public const int STD_ERROR_HANDLE = -12;
};
// The GetStdHandle() function returns a handle to any
// standard input or output.
[DllImport(“kernel32.dll”, SetLastError=true)]
public static extern IntPtr GetStdHandle(int nStdHandle);
// This sructure contains a screen coordinate.
[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct COORD
{
public short X;
public short Y;
}
// Obtains the current display mode–fullscreen or fullscreen hardware.
[DllImport(“Kernel32.DLL”)]
public static extern bool GetConsoleDisplayMode(ref UInt32 lpModeFlags);
// An enumeration used to determine the current display mode.
public enum ConsoleDispMode
{
CONSOLE_WINDOWED = 0, // Only implied by function.
CONSOLE_FULLSCREEN = 1, // The console is fullscreen.
CONSOLE_FULLSCREEN_HARDWARE = 2 // The console owns the hardware.
}
// Obtains the size of the largest console window possible.
[DllImport(“Kernel32.DLL”)]
public static extern COORD
GetLargestConsoleWindowSize(IntPtr hConsoleOutput);
// Returns the console mode information.
[DllImport(“Kernel32.DLL”)]
public static extern bool GetConsoleMode(
IntPtr hConsoleHandle,
ref UInt32 lpMode);
public enum ModeFlags
{
// Input mode flags
ENABLE_PROCESSED_INPUT = 0x0001,
ENABLE_LINE_INPUT = 0x0002,
ENABLE_ECHO_INPUT = 0x0004,
ENABLE_WINDOW_INPUT = 0x0008,
ENABLE_MOUSE_INPUT = 0x0010,
// Output mode flags
ENABLE_PROCESSED_OUTPUT = 0x0001,
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
}
[/code]

Many of the Win32 API functions require you to know specific integer or hexadecimal values. Even C++ developers can’t remember these numbers. Normally, a C++ developer relies on define statements that put the numbers into human-readable form. The P/Invoke code used in this chapter does the same thing, but sometimes it places the numbers in an enumeration to make them even easier to use. The StdHandleEnum class provides a list of standard handles (pointers) for Windows devices: input, output, and error. However, these aren’t the actual handles.

In order to get the standard Windows handle, an application must call the GetStdHandle() function. This function is in kernel32.dll. The [DllImport()] attribute tells the compiler where to look for an external Win32 API function that you want to use in your code. In this case, the attribute also tells the compiler that you want any error information that the Win32 API can provide. The use of extern before the function name tells the compiler that the requested DLL contains a function of the name that follows. You can now call this function directly and CLR will automatically perform any required marshaling for you.

Many of the Win32 API calls provide coordinates — x and y locations that tell where something is or how large it is. The COORD structure provides a means of transferring this kind of information between the .NET environment and the Win32 API environment. Windows uses a very basic view of structures. Unfortunately, .NET often causes problems by trying to optimize the data structures and causes P/Invoke calls to fail even though they should succeed. The [StructLayout()] attribute tells the compiler how to create a data structure in memory, which overrides the normal optimization process.

You may create applications that need to run in full-screen mode, if for no other reason than that they require the additional screen real estate to present information to the user. The GetConsoleDisplayMode() function tells you what mode the console is currently in. If the console is in the wrong mode, you can ask the user to change the mode or simply stop the application before the screen mode causes any problems. This function returns flags, not an enumerated value. At least one of the flags is always set, but the return value can have multiple flags set. The ConsoleDispMode enumeration makes it easier to work through the flag settings and provide usable output. The section “Defining the GetCurrentDisplayMode() Method” later in this chapter provides more information about this function.

In some cases, you need to know the largest size console window that the system will support. The GetLargestConsoleWindowSize() function provides this information. You can use other Win32 API functions to adjust the size of the window to meet application requirements (which is a topic for another book). The section “Defining the GetConsoleWindowSize() Method” provides more information about this function.

It’s also handy to know what kinds of operations the console window will support. For example, it’s good to know whether the console window will respond to the mouse. The GetConsoleMode() function provides this kind of information. The output is in the form of flags that you must interpret in your code. The GetConsoleMode() function is special in that the output you receive depends on the kind of device handle you provide. The output differs when you provide an input handle, versus an output handle. The section “Defining the GetConsoleInfo() Method” provides additional information about how this technique works.

Developing the IronPython Callable Methods

The P/Invoke code shown in Listing 16-5 does expose the Win32 API calls needed to perform certain tasks with IronPython. Theoretically, you could rely on just the code in Listing 16-5 to gain the access you require in IronPython. However, the task would be difficult because you’d need to work through the required bit manipulations. It’s better to place the code you need to access the Win32 API in easily called methods, which is the purpose of the code in the following sections.

Defining Common Variables and the Constructor

Win32 API calls often reuse information. It’s not uncommon for functions to ask for the same information over and over. For example, any function that works with a window will probably need the handle for that window. With this requirement in mind, Listing 16-6 shows the common variables and the constructor used for this example.

Listin g 16-6: Common variables and constructor

[code]
UInt32 DisplayMode = 0; // The current display mode.
IntPtr hOut; // Handle to the output device.
IntPtr hIn; // Handle to the input device.
UInt32 ConsoleMode = 0; // The console mode information.
public ConMode()
{
// Obtain a handle to the console screen and console input.
hIn = GetStdHandle(StdHandleEnum.STD_INPUT_HANDLE);
hOut = GetStdHandle(StdHandleEnum.STD_OUTPUT_HANDLE);
}
[/code]

The common variables include the current display mode (such as windowed), the console mode information (such as whether it accepts mouse input), and the handles for the input and output devices. These variables represent common pieces of information that the developer requires for multiple calls.

The constructor initializes the input and output handles using the GetStdHandle() function. The input argument simply tells Windows which handle you want. The output is an IntPtr, a special kind of variable that points to something. An IntPtr is a safe pointer, meaning you can use it without problems in a managed language. C# also supports unsafe pointers that you should use only as a last resort.

Defining the GetCurrentDisplayMode() Method

Sometimes you need to know whether the console is presented in a windowed or full-screen mode. A windowed console can get covered up and needs to share resources with other windows. In addition, the text in a windowed console can be small and hard to read. On a positive note, using a windowed console makes it easier to share data between applications. In most cases, the user will prefer that you use a windowed console to make it easier to multitask between applications. Listing 16-7 shows how to detect the current console display mode.

Listin g 16-7: Obtaining the current display mode

[code]
public OutputMode GetCurrentDisplayMode()
{
// Get the current display mode.
if (GetConsoleDisplayMode(ref DisplayMode))
// Determine if the console is in windowed mode.
if (DisplayMode == (UInt32)ConsoleDispMode.CONSOLE_WINDOWED)
return OutputMode.Windowed;
else
{
// If the console is fullscreen mode, determine which
// of the potential conditions are true.
switch (DisplayMode)
{
case (UInt32)ConsoleDispMode.CONSOLE_FULLSCREEN:
return OutputMode.Fullscreen;
case (UInt32)ConsoleDispMode.CONSOLE_FULLSCREEN_HARDWARE:
return OutputMode.HadwareAccess;
case (UInt32)ConsoleDispMode.CONSOLE_FULLSCREEN +
(UInt32)ConsoleDispMode.CONSOLE_FULLSCREEN_HARDWARE:
return OutputMode.FullscreenHardwareAccess;
}
}
// Return a default value.
return OutputMode.Unknown;
}
[/code]

The code begins by calling GetConsoleDisplayMode() to obtain the display mode as a numeric value. The information is returned in DisplayMode, not as a return value from the function call. The function itself returns a success value that indicates the call was successful. The first if statement says that if the call is successful, then DisplayMode will contain the console display mode, and that the application should proceed to process it. Because DisplayMode provides a return value, you must include the ref keyword when passing it to the Win32 API.

Now that the code has a display mode value, it needs to process it. If a console is in windowed mode, all the code has to do is return a value that says it’s windowed. However, full-screen mode requires some additional processing. When a console is in full-screen mode, it can also have access to the hardware. This is virtual hardware access, but it still feels to the application as if the access is direct. Consequently, the code must now determine whether the console is simply in full-screen mode or it’s in full-screen mode with hardware access.

The call could fail, but it’s unlikely to. Even so, the GetCurrentDisplayMode() handles the potential problem by providing the OutputMode.Unknown return value. This value simply says that the method couldn’t determine the current console display mode.

Defining the GetConsoleWindowSize() Method

Sometimes an application needs to know the maximum windowed console that a machine can accommodate. You might need additional room to display complex textual information. The Win32 API returns this information in a COORD structure that simply states the number of rows and columns of text that a console can support at maximum size. The following code shows the GetConsoleWindowSize() method used to obtain this information.

[code]
public COORD GetConsoleWindowSize()
{
// Determine the largest screen size possible.
return GetLargestConsoleWindowSize(hOut);
}
[/code]

This method is easy. All it does is call the GetLargestConsoleWindowSize() function with the output handle. Make sure you provide the output handle, and not the input handle, when making this call. The X and Y members of COORD contain the maximum screen size on return from the call.

Defining the GetConsoleInfo() Method

Consoles can support a number of input and output methods. For example, a console can support the mouse, which may make it easier for the user to interact with your character-mode application. If a console provides support for echo, it re-displays commands sent to it from batch files and other forms of automation. Consequently, you might find it useful to know just what the console will do for you. Listing 16-8 shows how to determine the input and output handling that a console provides.

Listin g 16-8: Obtaining the console characteristics

[code]
public struct ConsoleData
{
public Boolean Echo;
public Boolean LineInput;
public Boolean MouseInput;
public Boolean ProcessedInput;
public Boolean WindowInput;
public Boolean ProcessedOutput;
public Boolean LineWrap;
}
public ConsoleData GetConsoleInfo()
{
// Create the required structure.
ConsoleData Output = new ConsoleData();
// Retrieve the input information.
if (GetConsoleMode(hIn, ref ConsoleMode))
{
if ((ConsoleMode & (UInt32)ModeFlags.ENABLE_ECHO_INPUT) ==
(UInt32)ModeFlags.ENABLE_ECHO_INPUT)
Output.Echo = true;
else
Output.Echo = false;
if ((ConsoleMode & (UInt32)ModeFlags.ENABLE_LINE_INPUT) ==
(UInt32)ModeFlags.ENABLE_LINE_INPUT)
Output.LineInput = true;
else
Output.LineInput = false;
if ((ConsoleMode & (UInt32)ModeFlags.ENABLE_MOUSE_INPUT) ==
(UInt32)ModeFlags.ENABLE_MOUSE_INPUT)
Output.MouseInput = true;
else
Output.MouseInput = false;
if ((ConsoleMode & (UInt32)ModeFlags.ENABLE_PROCESSED_INPUT) ==
(UInt32)ModeFlags.ENABLE_PROCESSED_INPUT)
Output.ProcessedInput = true;
else
Output.ProcessedInput = false;
if ((ConsoleMode & (UInt32)ModeFlags.ENABLE_WINDOW_INPUT) ==
(UInt32)ModeFlags.ENABLE_WINDOW_INPUT)
Output.WindowInput = true;
else
Output.WindowInput = false;
}
// Retrieve the output information.
if (GetConsoleMode(hOut, ref ConsoleMode))
{
if ((ConsoleMode & (UInt32)ModeFlags.ENABLE_PROCESSED_OUTPUT) ==
(UInt32)ModeFlags.ENABLE_PROCESSED_OUTPUT)
Output.ProcessedOutput = true;
else
Output.ProcessedOutput = false;
if ((ConsoleMode & (UInt32)ModeFlags.ENABLE_WRAP_AT_EOL_OUTPUT)
== (UInt32)ModeFlags.ENABLE_WRAP_AT_EOL_OUTPUT)
Output.LineWrap = true;
else
Output.LineWrap = false;
}
// Return the results.
return Output;
}
[/code]

This is one of the few situations in the chapter where you need to send a number of pieces of information back to IronPython. The ConsoleData structure contains an entry of each piece of information that the GetConsoleInfo() provides. An IronPython application can set the output of the call to a variable and then use the variable content to determine precisely how the console is configured.

The GetConsoleInfo() method is a little more complicated than the other calls in the extension. This method relies on the GetConsoleMode() function to obtain console information. However, notice that the method calls the GetConsoleMode() function twice, once with the input handle and again with the output handle. This method demonstrates how the use of the wrong handle could cause problems because the output from the GetConsoleMode() function differs with the handle you provide as input.

The return value from the GetConsoleMode() function is a series of flags. Notice how the code uses if statements to determine whether each flag is set. When a flag is set, the feature is enabled and the code sets that value in the ConsoleData data structure, Output, to true. The method ends by returning the fully completed ConsoleData data structure to the caller.

Writing an IronPython Application to Use P/Invoke

If you’ve been following along with the example, you know it’s finally time to use the ConMode class with IronPython. It’s now possible to determine the display mode, the size of the console window, and the capabilities it provides. Listing 16-9 shows the code used for testing this extension.

Listin g 16-9: Testing the Win32 API extension

[code]
# Import the Common Language Runtime.
import clr
# Access the extension.
clr.AddReferenceToFile(‘Win32API.DLL’)
import Win32API
# Create an instance of the class.
TestWin32 = Win32API.ConMode()
# Check the display mode.
print ‘The display mode is: ‘,
print TestWin32.GetCurrentDisplayMode()
# Obtain the largest possible window size.
print ‘nThe largest possible window size is: ‘
Size = TestWin32.GetConsoleWindowSize()
print ‘tColumns: ‘, Size.X
print ‘tRows: ‘, Size.Y
# Display the console characteristics.
print ‘nThe console has these characteristics:’
Chars = TestWin32.GetConsoleInfo()
print ‘tEcho Enabled: ‘, Chars.Echo
print ‘tLine Input Enabled: ‘, Chars.LineInput
print ‘tMouse Input Enabled: ‘, Chars.MouseInput
print ‘tProcessed Input Enabled: ‘, Chars.ProcessedInput
print ‘tWindow Input Enabled: ‘, Chars.WindowInput
print ‘tConsole Can Produce Processed Output:’, Chars.ProcessedOutput
print ‘tConsole Uses Line Wrap: ‘, Chars.LineWrap
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The code begins by importing CLR support. It then creates a reference to Win32API.DLL and imports the Win32API namespace into the IronPython environment. The next step is to create an instance of the Win32API.ConMode class, TestWin32.

At this point, the code begins checking each console feature in turn, beginning with the console display mode, which doesn’t require any additional processing. The GetConsoleWindowSize() method call requires that the code display the Size.X (columns) and Size.Y (rows) values separately.

The GetConsoleInfo() method call comes next. This particular call requires a little more processing because it returns more information. The output of the call appears in Chars as a ConsoleData data structure. As you can see, the code simply displays the true or false value of each of the data structure members. Figure 16-17 shows the output from this example.

One of the most important issues when making Win32 API calls from IronPython is to ensure that the C# extension processes the data in an easy-to-use manner. In addition, you should provide a consistent method for returning the data from the C# extension to IronPython, such as using data structures (as shown in the example).

 

 

Using IronPython from Other .NET Languages

Understanding the Relationship between Dynamic and Static Languages

Something that most developers fail to consider is that, at some point, all languages generate the same thing — machine code. Without machine code, the software doesn’t execute. Your computer cares nothing at all about the idiosyncrasies of human language and it doesn’t care about communicating with you at all. Computers are quite selfish when you think about it. The circuitry that makes up your computer relies on software to change the position of switches — trillions of them in some cases. So computers use machine code and only machine code; languages are for humans.

When it comes to dynamic and static languages, it’s the way that humans view the languages that make them useful. A dynamic language offers the developer freedom of choice, call it the creative solution. A static language offers a reliable and stable paradigm — call it the comfort solution, the one that everyone’s used. How you feel about the languages partly affects your use of them. In the end, both dynamic and static language output ends up as machine code. Dynamic and static languages end up being tools that help you create applications faster and with fewer errors. If you really wanted to do so, you could write any application today using assembler (a low-level language just above machine code, see http://www.bing.com/reference/semhtml/Assembly_language for more information), but assembler is hardly the correct tool any longer — humans need a better tool to put applications together. The point is that you should use the tool that works best for a particular development process and not think that the tool is doing anything for your computer.

Anytime you use multiple languages, you must consider issues that have nothing to do with the dynamic or static nature of that language. For example, you must consider the data types that the languages support and provide a method for marshaling data from one language to the other. In fact, marshaling data is an important element in many areas of coding. If you want to communicate with the Win32 API from a .NET-managed language such as C# or Visual Basic.NET, you must marshal the data between the two environments. It’s important not to confuse communication and infrastructure requirements with differences between dynamic and static languages. Many resources you find do confuse these issues, which makes it hard for anyone to truly understand how dynamic and static languages differ.

Before you can use IronPython from other languages, it’s important to consider the way in which IronPython performs tasks. When an IronPython session starts, nothing exists — the environment begins with an empty slate. You’ve discovered throughout this book that IronPython calls upon certain script files as it starts to configure the environment automatically. These configuration tasks aren’t part of the startup; they are part of the configuration — something that occurs after the startup. The dynamic nature of IronPython means that all activity begins and ends with adding, changing, and removing environment features. There aren’t any compiled bits that you can examine statically. Everything in IronPython is dynamic.

When a static language such as C# or Visual Basic.NET attempts to access IronPython, it must accommodate the constant change. If you got nothing else out of Chapter 14 but this one fact, then the chapter was worth reading. In order to do this, C# and Visual Basic.NET rely upon events because they can’t actually accommodate change as part of the language. An event signals a change — an IronPython application has modified a class to contain a new method or property. It isn’t just the idea that the output or value has changed, but the method or property itself is new. In some cases, C# or Visual Basic.NET will also need to deal with the situation where a method or property simply goes away as well. The underlying mechanism of events, delegates, and caches is inspired and all but invisible, but to be successful at using the languages together, you must know they’re present.

The differences between dynamic and static languages go further than simply not knowing what code will execute next in a dynamic language. There’s also the matter of data typing. A static language assigns a type to the data it manages, which means that the compiler can make assumptions about the data and optimize access to it. A dynamic language also assigns types to the data it manages, but only does so at run time and even then the data type can change. Now, consider how this changeability complicates the matter of marshaling data from one language to the other. Because the data no longer has a stable type, the marshaling code can’t assume anything about it and must constantly check type to ensure the data it marshals appears in the right form in the target language.

The difference between dynamic and static languages, at least from a programming perspective, comes down to flexible coding and data typing. Everything else you may have heard either relates to differences between any two languages (such as the need to marshal data) or the political drama of which tool works best. This book won’t endeavor to tell you what tool to use. Certainly, I don’t tell anyone that a hammer works best for driving screws or that screwdrivers make wonderful ice picks (not that I believe either of these statements myself). The tool you use for a particular task is the one you can use best or the one called for by a particular job requirement. The point of this chapter and the rest of the book is to demonstrate that dynamic and static languages can work together successfully and in more than one way. The tool you use is up to you.

Creating an Externally Accessible IronPython Module

The first requirement for building an application that allows external access is to create the IronPython script you want to use. Ideally, this script will contain code that is fully debugged. You also want to test the code before you try to use it within C# or Visual Basic.NET. The following sections provide you with the techniques you use to create an IronPython script that you access from C# or Visual Basic .NET.

Considering Requirements for Externally Accessible Modules

The mistake that many developers will make is to think they must do something special in IronPython to make the code accessible. What you really need to do is create an IronPython script using the same techniques as always, and then test it directly. After you test the script using IronPython code, work with the target static language to gain the required access. This pretesting process is important to ensure that you aren’t fighting with a bad script in addition to potential problems marshaling data or interacting with methods that change.

Creating the IronPython Script

The IronPython script used for this example is quite simple in approach. All that the example call really does is add two numbers together. You could perform the task with far less code, but the point of this class is to demonstrate access techniques, so it’s purposely simple. Listing 15-1 shows the external module code and the code used to test it. As previously mentioned, testing your IronPython script is essential if you want the application to work properly.

Listin g 15-1: A test IronPython class for use in the examples

[code]
# The class you want to access externally.
class DoCalculations():
# A method within the class that adds two numbers.
def DoAdd(self, First, Second):
# Provide a result.
return First + Second
# A test suite in IronPython.
def __test__():
# Create the object.
MyCalc = DoCalculations()
# Perform the test.
print MyCalc.DoAdd(5, 10)
# Pause after the test session.
raw_input(‘nPress any key to continue…’)
# Execute the test.
# Comment this call out when you finish testing the code.
__test__()
[/code]

The class used for this example is DoCalculations(). It contains a single method, DoAdd(), that returns the sum of two numbers, First and Second. Overall, the class is simple.

The TestClass.py file also contains a __test__() function. This function creates an instance of DoCalculations(), MyCalc. It then prints the result of calling the DoAdd() method with values of 5 and 10. The example waits until you press Enter to exit.

In __main__(), you see a call to __test__(). You can execute the example at the command line, as shown in Figure 15-1. Make sure you use the –D command line switch to place the interpreter in debug mode. You could also open IPY.EXE interactively, load the file, and execute it inside the interpreter. When you know that the code works properly, be sure to comment out the call to __test__() in __main__().

Test the external module before you use it with your application.
Figure 15-1: Test the external module before you use it with your application.

Accessing the Module from C#

Now that you have an external module to use, you’ll probably want to access from an application. This section considers the requirements for accessing IronPython from C#. Don’t worry; the section “Accessing the Module from Visual Basic.NET” later in this chapter discusses access from Visual Basic. NET as well. If you follow these steps, you’ll find that access is relatively straightforward, even if it does get a bit convoluted at times. Microsoft promises the future versions of C# will make dynamic language access even easier.

Adding the Required C# References

Any application you create requires access to the dynamic language assemblies. The IronPython assemblies appear in the Program FilesIronPython 2.6 folder on your machine. Right-click References and choose Add Reference from the content menu to display the Add Reference dialog box. Select the Browse tab. In most cases, you only need the three DLLs shown in Figure 15-2 to access any IronPython script. (You may also need to add the IronPython .Modules.DLL file to the list in some cases.)

Add the required references from your IronPython setup.
Figure 15-2: Add the required references from your IronPython setup.

Select the assemblies you require by Ctrlclicking them in the Add Reference dialog box. Click OK when you’re finished. You’ll see the assemblies added to the References folder in Solution Explorer.

Adding the Required References to the Host Language

You can perform a multitude of tasks with IronPython. In fact, later chapters in the book show how to perform tasks such as testing your static application code. IronPython really is quite flexible. However, most people will start by executing external scripts and only need a few of the namespaces in the IronPython assemblies to do it. The following using statements provide everything needed to execute and manage most IronPython scripts.

[code]
using System;
using IronPython.Hosting;
using IronPython.Runtime;
using Microsoft.Scripting.Hosting;
[/code]

Understanding the Use of ScriptEngine

You have many options for working with IronPython scripts. This first example takes an approach that works fine for Visual Studio 2008 developers, as well as those using Visual Studio 2010. It doesn’t require anything fancy and it works reliably for most scripts. Ease and flexibility concerns aside, this isn’t the shortest technique for working with IronPython scripts. This is the Method1 approach to working with IronPython scripts — the technique that nearly everyone can use and it appears in Listing 15-2.

Listin g 15-2: Using the script engine to access the script

[code]
static void Main(string[] args)
{
// Create an engine to access IronPython.
ScriptEngine Eng = Python.CreateEngine();
// Describe where to load the script.
ScriptSource Source = Eng.CreateScriptSourceFromFile(“TestClass.py”);
// Obtain the default scope for executing the script.
ScriptScope Scope = Eng.CreateScope();
// Create an object for performing tasks with the script.
ObjectOperations Ops = Eng.CreateOperations();
// Create the class object.
Source.Execute(Scope);
// Obtain the class object.
Object CalcClass = Scope.GetVariable(“DoCalculations”);
// Create an instance of the class.
Object CalcObj = Ops.Invoke(CalcClass);
// Get the method you want to use from the class instance.
Object AddMe = Ops.GetMember(CalcObj, “DoAdd”);
// Perform the add.
Int32 Result = (Int32)Ops.Invoke(AddMe, 5, 10);
// Display the result.
Console.WriteLine(“5 + 10 = {0}“, Result);
// Pause after running the test.
Console.WriteLine(“rnPress any key when ready…”);
Console.ReadKey();
}
[/code]

Now that you have access to Eng, you can use it to perform various tasks. For example, you must tell Eng what scope to use when executing code, so the example creates a ScriptScope object, Scope. In order to perform tasks, you must also have an ObjectOperations object, Ops. The example uses the defaults provided for each of these objects. However, in a production application, you might decide to change some properties to make the application execute faster or with better security.

At this point, you can execute the script. The act of executing the script using Source.Execute() loads the script into memory and compiles it in a form that the static application can use. The Source.Execute() method associates Scope with the execution environment. At this point, the parameters for executing the script are set in stone — you can’t change them.

The script is in memory, but you can’t access any of its features just yet. The script contains a DoCalculations class that you access by calling Scope.GetVariable() to create CalcObj. The code gains access to the class by creating an instance of it, CalcObj, using Ops.Invoke(). At this point, CalcObj contains an instance of DoCalculations() in the IronPython module, but you can’t use it directly. Remember that you must marshal data between C# and IronPython. In addition, C# has to have a way to deal with the potential changes in the IronPython script.

This seems like a lot of work just to gain access to DoAdd, but you can finally use AddMe to perform the addition. A call to Ops.Invoke() with AddMe and the arguments you want to use performs all of the required marshaling for you. You must coerce the output to an Int32 (something that C# understands). Finally, the application outputs the result, as shown in Figure 15-3.

FThe example application calls the DoAdd() method and displays the result onscreen.
Figure 15-3: The example application calls the DoAdd() method and displays the result onscreen.

Using the dynamic Keyword

One of the new ways in which you can access IronPython in C# 4.0 is to use the dynamic keyword. This keyword makes it possible for you to cut out a lot of the code shown in Listing 15-2 to perform tasks with IronPython. It’s still not perfect, but you’ll do a lot less work. Listing 15-3 shows a short example that accesses the __test__() function found in Listing 15-1.

Listin g 15-3: Accessing IronPython using the dynamic keyword

[code]
static void Main(string[] args)
{
// Obtain the runtime.
var IPY = Python.CreateRuntime();
// Create a dynamic object containing the script.
dynamic TestPy = IPY.UseFile(“TestClass.py”);
// Execute the __test__() method.
TestPy.__test__();
}
[/code]

The next step is to load the script. The dynamic type, TestPy, contains all the features of the TestClass.py script after you load it using IPY.UseFile(). Figure 15-4 shows how TestPy appears after the script loads. Notice that the Locals window correctly identifies all the IronPython types in the file.  (Visual Basic.NET developers will have to wait for an update).

In this case, the example calls the __test__() function. This function outputs the same information shown in Figure 15-1.

Loading the script provides access to all of the features it contains.
Figure 15-4: Loading the script provides access to all of the features it contains.

Working with the App.CONFIG File

In some cases, you might want to configure your application using an App.CONFIG file. Using the App.CONFIG file tends to ensure that your application works better between development machines. In addition, using the App.CONFIG file can make it easier to work with DLR using older versions of Visual Studio. Most important of all, using the App.CONFIG file ensures that anyone working with the application uses the correct version of the DLLs so that any DLL differences aren’t a problem.

Your project won’t contain an App.CONFIG file at the outset. To add this file, right-click the project entry in Solution Explorer and choose Add ➪ New Item from the context menu. You see the Add New Item dialog box shown in Figure 15-5. Highlight the Application Configuration File entry as shown and click Add. Visual Studio automatically opens the file for you.

The App.CONFIG file contains entries that describe the Microsoft scripting configuration. In most cases, you begin by defining a <section> element, which describes a <microsoft.scripting> element. The <microsoft.scripting> element contains a list of languages you want to use in a <languages> element, as shown in Listing 15-4.

Listin g 15-4: Defining the App.CONFIG file content

[code]
<?xml version=”1.0” encoding=”utf-8” ?>
<configuration>
<configSections>
<section name=”microsoft.scripting”
type=”Microsoft.Scripting.Hosting.Configuration.Section,
Microsoft.Scripting, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35”
requirePermission=”false” />
</configSections>
<microsoft.scripting>
<languages>
<language names=”IronPython,Python,py”
extensions=”.py”
displayName=”IronPython 2.0 Beta”
type=”IronPython.Runtime.PythonContext,IronPython,
Version=2.6.10920.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35” />
</languages>
</microsoft.scripting>
</configuration>
[/code]

Use an App.CONFIG file to hold DLR configuration information.
Figure 15-5: Use an App.CONFIG file to hold DLR configuration information.

The <section> element includes attributes for name, type, and requirePermission. The type attribute should appear on one line, even though it appears on multiple lines in the book. This attribute describes the Microsoft.Scripting.DLL attributes. Especially important is the Version and PublicKeyToken entries.

The <microsoft.scripting> element contains a <languages> element at a minimum. Within the <languages> element you find individual <language> elements that are descriptions of the languages you want to use in your application.

For this example, you create a <language> element for IronPython that starts with a names attribute. It’s important to define all the names you plan to use to access the language — the example defines three of them. The extensions attribute describes the file extensions associated with the language, which is .py in this case. The displayName attribute simply tells how to display the language. Finally, the type attribute contains a description of the IronPython.DLL file. As with the type element for Microsoft.Scripting.DLL. Again, you need to exercise special care with the Version and PublicKeyToken entries.

Now that you have the App.CONFIG file created, it’s time to look at the application code. Listing 15-5 contains the source for this example.

Listin g 15-5: Using the App.CONFIG file in an application

[code]
static void Main(string[] args)
{
// Read the configuration information from App.CONFIG.
ScriptRuntimeSetup srs = ScriptRuntimeSetup.ReadConfiguration();
// Create a ScriptRuntime object from the configuration
// information.
ScriptRuntime runtime = new ScriptRuntime(srs);
// Create an engine to access IronPython.
ScriptEngine Eng = runtime.GetEngine(“Python”);
// Describe where to load the script.
ScriptSource Source = Eng.CreateScriptSourceFromFile(“TestClass.py”);
// Obtain the default scope for executing the script.
ScriptScope Scope = Eng.CreateScope();
// Create an object for performing tasks with the script.
ObjectOperations Ops = Eng.CreateOperations();
// Create the class object.
Source.Execute(Scope);
// Obtain the class object.
Object CalcClass = Scope.GetVariable(“DoCalculations”);
// Create an instance of the class.
Object CalcObj = Ops.Invoke(CalcClass);
// Get the method you want to use from the class instance.
Object AddMe = Ops.GetMember(CalcObj, “DoAdd”);
// Perform the add.
Int32 Result = (Int32)Ops.Invoke(AddMe, 5, 10);
// Display the result.
Console.WriteLine(“5 + 10 = {0}“, Result);
// Pause after running the test.
Console.WriteLine(“rnPress any key when ready…”);
Console.ReadKey();
}
[/code]

The biggest difference between this example and the one shown in Listing 15-2 is that you don’t create the script engine immediately. Rather, the code begins by reading the configuration from the App.CONFIG file using ScriptRuntimeSetup.ReadConfiguration(). This information appears in srs and is used to create a ScriptRuntime object, runtime.

At this point, the code finally creates the ScriptEngine, Eng, as in the previous example. However, instead of using Python.CreateEngine(), this example relies on the runtime.GetEngine() method. For this example, the result is the same, except that you’ve had better control over how the ScriptEngine is created, which is the entire point of the example — exercising control over the IronPython environment. The rest of the example works the same as the example shown in Listing 15-2. The output is the same, as shown in Figure 15-3.

Accessing the Module from Visual Basic.NET

You might get the idea from the lack of Visual Basic.NET examples online that Microsoft has somehow forgotten Visual Basic.NET when it comes to DLR. Surprise! Just because the examples are nowhere to be seen (send me an e‑mail at [email protected] if you find a stash of Visual Basic.NET examples somewhere) doesn’t mean that you can’t work with IronPython from Visual Basic. In fact, the requirements for working with Visual Basic.NET are much the same as those for working with C#, as shown in the following sections.

Adding the Required Visual Basic.NET References

Visual Basic requires the same DLL references as C# does to work with IronPython. Figure 15-2 shows the assemblies you should add to your application to make it work properly. In this case, you right-click the project entry and choose Add Reference from the context menu to display an Add Reference dialog box similar to the one shown in Figure 15-2. Select the Browse tab and add the IronPython assemblies shown in Figure 15-2 by Ctrl-clicking on each of the assembly entries. Click OK. Visual Basic will add the references, but you won’t see them in Solution Explorer unless you click Show All Files at the top of the Solution Explorer window.

As with C#, you need to add some Imports statements to your code to access the various IronPython assemblies with ease. Most applications will require the following Imports statements at a minimum.

[code]
Imports System
Imports IronPython.Hosting
Imports IronPython.Runtime
Imports Microsoft.Scripting.Hosting
[/code]

Creating the Visual Basic.NET Code

As with all the other examples, you shouldn’t let the IronPython example dictate what you do in your own applications. You can obtain full access to any IronPython script from Visual Basic.NET and fully use every feature it provides.

Accessing IronPython scripts from Visual Basic.NET is much the same as accessing them from C# using the ScriptEngine object. Listing 15-6 shows the code you need to access the IronPython script used for all the examples

Listin g 15-6: Accessing IronPython from Visual Basic.NET

[code]
Sub Main()
‘ Create an engine to access IronPython.
Dim Eng As ScriptEngine = Python.CreateEngine()
‘ Describe where to load the script.
Dim Source As ScriptSource = Eng.CreateScriptSourceFromFile(“TestClass.py”)
‘ Obtain the default scope for executing the script.
Dim Scope As ScriptScope = Eng.CreateScope()
‘ Create an object for performing tasks with the script.
Dim Ops As ObjectOperations = Eng.CreateOperations()
‘ Create the class object.
Source.Execute(Scope)
‘ Obtain the class object.
Dim CalcClass As Object = Scope.GetVariable(“DoCalculations”)
‘ Create an instance of the class.
Dim CalcObj As Object = Ops.Invoke(CalcClass)
‘ Get the method you want to use from the class instance.
Dim AddMe As Object = Ops.GetMember(CalcObj, “DoAdd”)
‘ Perform the add.
Dim Result As Int32 = Ops.Invoke(AddMe, 5, 10)
‘ Display the result.
Console.WriteLine(“5 + 10 = {0}“, Result)
‘ Pause after running the test.
Console.WriteLine(vbCrLf + “Press any key when ready…”)
Console.ReadKey()
End Sub
[/code]

As you can see from the listing, Visual Basic.NET code uses precisely the same process as C# does to access IronPython scripts. In fact, you should compare this listing to the content of Listing 15-2. The two examples are similar so that you can compare them. The output is also precisely the same. You’ll see the output shown in Figure 15-3 when you execute this example.

Developing Test Procedures for External Modules

Many developers are beginning to realize the benefits of extensive application testing. There are entire product categories devoted to the testing process now because testing is so important. Most, if not all, developer tools now include some idea of application testing with them. In short, you should have all the testing tools you need to test the static portion of your IronPython application.

Unfortunately, the testing tools might not work particularly well with the dynamic portion of the application. Creating a test that goes from the static portion of the application to the dynamic portion of the application is hard. (Consequently, you need to include a test harness with your dynamic code and perform thorough testing of the dynamic code before you use it with the static application. (When you think about a test harness, think about a horse, your application that has a harness added externally for testing purposes. You add the harness for testing and remove it for production work without modifying the application.) Listing 15-1 shows an example of how you might perform this task.

The test harness you create has to test everything, which is a daunting task to say the least. In addition, you need to expend extra effort to make the test harness error free — nothing would be worse than to chase an error through your code, only to find out that the error is in the test harness. At a minimum, your test harness should perform the following checks on your dynamic code:

  • Outputs with good inputs
  • Outputs with erroneous inputs
  • Exception handling within methods
  • Property value handling
  • Exceptions that occur on public members that would normally be private

Of course, you want to check every method and property of every class within the dynamic code. To ensure you actually test everything, make sure you create a checklist to use to verify your test harness. Because IronPython isn’t compiled, you’ll find that you must manually perform some checks to ensure the code works precisely as planned, but use as much automation as possible.

Debugging the External Module

Debugging isn’t hard, but it also isn’t as straightforward as you might think when working with IronPython. The debugger won’t take you directly to an error. You can’t test variables using the debugger from within the static language. In short, you have to poke and prod the external script to discover what ails it. Fortunately, you do have three tools at your disposal for discovering errors.

  • Exceptions
  • print Statements
  • An ErrorListener object

Let’s begin with the easiest of the three tools. The static language application won’t ignore outright errors in the script code. For example, you might have the following error in the script:

[code]
# Introduce an error.
print 1/0
[/code]

If your code has this error (and it really shouldn’t), you’ll see an exception dialog box like the one shown in Figure 15-6. Unfortunately, when you click View Detail, the content of the View Detail dialog box is nearly useless. The exception information won’t tell you where to find the error in your script. In fact, it may very well lead you on a wild goose chase that ends in frustration.

The static language application displays exceptions for your script.
Figure 15-6: The static language application displays exceptions for your script.

The name of the exception will provide clues as to where the error might exist, but you can’t confirm your suspicions without help. The only tool, besides vigorous script testing, is to include print statements such as these in your code.

[code]
# Display the values of First and Second.
print ‘Values in IronPython Script’
print ‘First = ‘, First
print ‘Second = ‘, Second
[/code]

When you run the script, you see the output shown in Figure 15-7. Most developers view print statements as a bit old school, but they do work if you use them correctly. Make sure you provide enough information to know where the script is failing to perform as expected. Even so, using print statements may feel a bit like wandering around in the dark, so you should place an emphasis on testing the script before you use it and after each change you make.

Using print statements may seem old school, but they work.
Figure 15-7: Using print statements may seem old school, but they work.

In some cases, you might make a small change to a script and it stops running completely — you might not see a script exception, just an indicator that something’s wrong because the application raises an unrelated exception. Syntax errors and other problems where the interpreter simply fails can cause the developer a lot of woe. For example, your application might have the following syntax error:

[code]
# Create a syntax error.
while True print ‘This is an error!’
[/code]

This code obviously won’t run. Because of the nature of the error, you might even pass it by while looking through your code. The answer to this problem is to create an ErrorListener class like the one shown in Listing 15-7.

Listin g 15-7: Create an ErrorListener to hear script semantic errors

[code]
class MyListener : ErrorListener
{
public override void ErrorReported(ScriptSource source,
string message,
SourceSpan span,
int errorCode,
Severity severity)
{
Console.WriteLine(“Script Error {0}: {1}“, errorCode, message);
Console.WriteLine(“Source: {0}“, source.GetCodeLine(span.Start.Line));
Console.WriteLine(“Severity: {0}“, severity.ToString());
}
}
[/code]

The ErrorListener contains just one method, ErrorReported(). This method can contain anything you need to diagnose errors. The example provides an adequate amount of information for most needs. However, you might decide to provide additional information based on the kind of script you’re using.

In order to use this approach, you must compile the script before you execute it. The compilation process must include the ErrorListener, as shown here.

[code]
// Compile the script.
Source.Compile(new MyListener());
[/code]

When you run the application now, you get some useful information about the syntax error, as shown in Figure 15-8.

The ErrorListener provides useful output on syntax errors.
Figure 15-8: The ErrorListener provides useful output on syntax errors.

 

Interacting with .NET Framework Elements

Now that you know how to get a .NET assembly loaded into IronPython, it’s time to consider where to go next. Of course, it’s not going to be possible to cover every potential destination in a single chapter of a book. However, the following sections provide some basics you can use to get started. For example, most developers need to know a little about the assemblies they have loaded, such as the version number. The following sections also show how to work with static methods and objects created from .NET classes. The final section provides a quick example that shows how to obtain a list of files and directories found in the root directory of your system. All these examples work together to help you get an idea of how .NET works within IronPython.

Obtaining Assembly Information

Developers will want to obtain information about the assemblies they load into IronPython. Part of the problem is going to be that you won’t see a list of these assemblies in Solution Explorer because Visual Studio doesn’t provide direct support for IronPython. Consequently, the application itself will need to have some level of assembly management support built into it. With this in mind, Listing 7-1 shows you how to perform some basic tasks with assemblies.

Listin g 7-1: Interacting with assemblies

[code]

# Add the .NET Framework 2.0 to the path.
import sys
sys.path.append(‘C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’)
# Make clr accessible.
import clr
# Import the System assembly.
import System
# Add a reference for System.Xml and import it.
clr.AddReference(‘System.Xml.DLL’)
import System.Xml
# Display a list of available assemblies.
print ‘List of Assemblies:’
for ThisRef in clr.References:
print ThisRef
# Access a specific assembly and display more information about it.
ThisRef = clr.References[2]
print ‘nFull Name of System.Xml:’
print ThisRef.FullName
# Display the referenced assemblies.
print ‘nReferenced Assemblies:’
for Reference in ThisRef.GetReferencedAssemblies():
print Reference.ToString()
# Display the assembly attributes.
print ‘nAttributes:’
for AnAttribute in ThisRef.GetCustomAttributes(type(ThisRef)):
print AnAttribute.ToString()
# Pause after the debug session.
raw_input(‘Press any key to continue…’)

[/code]

The code begins by importing sys and adding the .NET Framework 2.0 path to sys.path. Adding just the path you need tends to reduce the risk of importing the wrong assembly. Even so, you want to verify that you’re using the right assembly, especially when the code is running on another system.

The next step is to import clr. This module provides the features required to interact with the assemblies. The next few lines of code import System and System.Xml so that you can see a number of assemblies in IronPython. Notice that the System.Xml import requires use of the clr.AddReference() method.

At this point, the example obtains the list of loaded assemblies from the clr.References property and displays them onscreen using a for loop. Figure 7-11 shows the list of assemblies for this example. Notice that the first assembly is mscorlib, which always loads.

You can drill down into an assembly as far as you want. The example continues by working with the System.Xml assembly. As a first step, the code displays the full name of the assembly. Using string manipulation, you can obtain the assembly name, version, culture, and public key token.

When creating an installation program, you often need to know the list of referenced assemblies. The next portion of application code shows how to perform this task using the ThisRef .GetReferencedAssemblies() method. The output includes the same four pieces of information as the assembly information, so you know precisely which assemblies to include with your application.

Some developers also want to know about attributes assigned to an assembly. The ThisRef .GetCustomAttributes(type(ThisRef)) method call obtains this information. You must provide the type of the assembly you want to interact with. Notice that the example uses the Python type() function, rather than a .NET equivalent. In some situations, you mix .NET and Python code to obtain a desired result. The code shows how to use a loop to obtain the list of attributes. Most assemblies include a wealth of attributes (some inherited). A developer might need to know some of these attributes, such as System.Security.Permissions.SecurityPermissionAttribute (which provides security information about the assembly), to create a functional application.

Figure 7-11: IronPython doesn’t limit the information you receive about assemblies.
Figure 7-11: IronPython doesn’t limit the information you receive about assemblies.

Working with the attributes can prove a little tricky because they each contain different values. When working with the AssemblyCompanyAttribute, for example, you can access the Company property that contains the name of the company that created the attribute. Of course, nothing differs from any other .NET language in this case. You need to know precisely which attributes you want to query and the properties within those attributes that contain the values you need in order to interact with attributes successfully.

Making Static Method Calls

Many of the tasks you perform using .NET require use of static methods. Static methods work the same in IronPython as they do in any .NET language. Listing 7-2 shows some static method calls that work with the current date and time. The techniques shown work with any static method.

Listin g 7-2: Performing tasks using static methods

[code]

# Add the .NET Framework 2.0 to the path.
import sys
sys.path.append(‘C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’)
# Make clr accessible.
import clr
# Import the System assembly.
import System
# Get the system date and time.
CurrDateTime = System.DateTime.Now
# Display the date and time in a number of formats.
print ‘Short date and time:’
print System.DateTime.Now.ToShortDateString(),
print System.DateTime.Now.ToShortTimeString()
print ‘nLong date and time:’
print CurrDateTime.ToLongDateString(),
print CurrDateTime.ToLongTimeString()
# Display a few statistics.
print ‘n Date Statistics:’
print ‘Days in Month:’,
print CurrDateTime.DaysInMonth(CurrDateTime.Year, CurrDateTime.Month)
print ‘Daylight Savings?’, CurrDateTime.IsDaylightSavingTime()
print ‘Leap Year?’,
print CurrDateTime.IsLeapYear(CurrDateTime.Year)
# Manipulate the date.
print ‘nAdding a Day, Month, and Year:’
CurrDateTime = CurrDateTime.AddDays(1)
CurrDateTime = CurrDateTime.AddMonths(1)
CurrDateTime = CurrDateTime.AddYears(1)
print CurrDateTime.ToLongDateString()
# Pause after the debug session.
raw_input(‘Press any key to continue…’)

[/code]

The code begins by adding the required sys.path entry and importing the necessary modules and assemblies. It then creates a variable named CurrDateTime, which is only in place for convenience. The code sets CurrDateTime to reference System.DateTime.Now. You can do the same thing without relying on the variable.

The first outputs are the short and long date and time. Notice that the short date and time rely on System.DateTime.Now, while the long date and time rely on CurrDateTime. In both cases, the code calls on static methods to output the date and time in a specific format using ToShortDateString(), ToShortTimeString(), ToLongDateString(), and ToLongTimeString(). Figure 7-12 shows the output from these calls.

Figure 7-12: Static methods enable you to perform tasks without creating objects.
Figure 7-12: Static methods enable you to perform tasks without creating objects.

As with any date and time example, you can output statistics for the date and time in question. The example shows how to determine the number of days in the month, whether daylight savings time is in effect, and whether this is a leap year. Notice that some of these calls also require that you provide property values to obtain the output. You could just as easily replace CurrDateTime with System.DateTime.Now for the properties.

You do need a variable to hold date and time manipulations. The method calls are still static, but when you add date or time values, you need a place to store the result. The final portion of the example shows how you’d perform this task. The output is one day, one month, and one year later than the current date.

Creating .NET Objects

The .NET Framework provides access to a lot of objects and following chapters will explore many of them. However, one of the objects that developers need to know about first is the exception. The .NET code you run will generate exceptions at times, and Python does provide support for them as long as you have some idea of what to expect. In fact, you can even create and catch .NET exceptions in your IronPython code, as shown in Listing 7-3.

Listin g 7-3: Catching and handling .NET exceptions

[code]

# Add the .NET Framework 2.0 to the path.
import sys
sys.path.append(‘C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’)
# Make clr accessible.
import clr
# Import the System assembly.
import System
# Create an exception.
ThisException = System.OperationCanceledException(‘The User Said No!’)
# Ask the user a question.
try:
Answer = raw_input(‘Do you want to continue? (Y/N)‘)
# Check the response.
if Answer.upper() == ‘Y’:
print ‘Great!’
else:
raise(ThisException)
except SystemError as (SysErr):
print ‘nMessage:’, SysErr.message
print ‘nClass:’, SysErr.clsException.GetType()
print ‘nClass, Message, and Stack Trace:’, SysErr.clsException
# Pause after the debug session.
raw_input(‘Press any key to continue…’)

[/code]

The example starts out as most do in the chapter by making the appropriate references and importing the correct modules and assemblies. The first bit of code creates an exception, ThisException, by calling the System.OperationCanceledException() constructor. You can embed previous exceptions in the current exception by using the correct constructor, but the example uses just one level to keep things simple.

The code then asks the user a simple question. If the user answers N, the code raises an exception and then catches it as a SystemError. Notice that this exception handler provides a means of accessing the error through SysErr. The easiest way to obtain the error message is through the SysErr.message.

Of course, you’ll probably want more information. All of the .NET errors will appear as the SystemError type. Consequently, you need to consider how to detect the proper error class in your IronPython code. The SysErr.clsException.GetType() provides the answer. You can also display a complex message by displaying the SysErr.clsException attribute. Figure 7-13 shows the output from this example.

Figure 7-13: Use SysErr.clsException.GetType () to obtain an error class.
Figure 7-13: Use SysErr.clsException.GetType () to obtain an error class.

Creating the Directory Example

You’ll probably find that you need access to the user’s hard drive at some point. IronPython does provide the functionality that Python provides for working with drives, directories, and folders, but many developers will find using the .NET functionality easier — especially if that’s what they normally rely upon. Listing 7-4 shows a very simple example of gaining access to directories and files. Once you gain this access, you can perform any task that you’d normally perform with a directory or file. The rest of System.IO works amazingly like what you’d expect from any .NET language.

Listin g 7-4: Obtaining access to directories and files

[code]

# Add the .NET Framework 2.0 to the path.
import sys
sys.path.append(‘C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’)
# Make clr accessible.
import clr
# Import the required assemblies.
import System
import System.IO
# Get the starting directory.
Start = raw_input(‘Type a starting directory (such as C:\)‘)
# Create the DirectoryInfo object.
MyDir = System.IO.DirectoryInfo(Start)
# Display a list of subdirectories.
print ‘Subdirectories:’
for EachDir in MyDir.GetDirectories():
print EachDir, ‘t’,
# Display a list of files.
print ‘nnFiles:’
for EachFile in MyDir.GetFiles():
print EachFile, ‘t’,
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)

[/code]

The code begins with the usual imports. Notice that you must import the System.IO assembly to make this example work.

At this point, the code asks the user to provide a drive specification as a starting point. You can type any location on your hard drive that actually exists. The example doesn’t provide error-trapping code for the sake of clarity

Now that the code has a drive specification to use, it creates a System.IO.DirectoryInfo() object, MyDir, just as you would when using a .NET language. The code uses MyDir to access the list of directories using MyDir.GetDirectories() and a list of files using MyDir.GetFiles(). In this case, the code simply prints out the result, but you could go on to process the directories and files. Figure 7-14 shows typical output from this example (of course, the actual directory and filenames will match your system).

Figure 7-14: A list of directories and subdirectories is in the C: folder
Figure 7-14: A list of directories and subdirectories is in the C: folder

 

Using Visual Studio to Create IronPython Applications

You might have looked at the New Project dialog box after you installed IronPython, assuming that you’d find a series of new project templates. Unfortunately, you won’t find any new templates for IronPython. The current version of the product doesn’t include anything you can use directly. Fortunately, you can still create a project for IronPython projects and use Visual Studio to edit and debug it. The following sections take you through a simple configuration scenario and then show the resulting project in action.

Creating the Project

Before you do anything else, you must create a project to hold your IronPython application. The following steps show you how to perform this task.

  1. Open Visual Studio, but don’t open any project or template files.
  2. Choose File ➪ Open ➪ Project/Solution. You’ll see the Open Project dialog box shown in Figure 2-1.

    Use the Open Project dialog box to start the project.

  3. Locate and highlight IPY.EXE (normally found in the Program FilesIronPython 2.6 folder). Click Open. Visual Studio creates a solution based on IPY.EXE, as shown in Figure 2-2. You must still configure this solution.
    IPY.EXE becomes the focal point for a new solution.
  4. Right-click IPY in Solution Explorer and choose Properties from the context menu. You’ll see the General tab of the Properties page shown in Figure 2-3. (Your display may differ slightly from the one shown in Figure 2-3 based on your machine configuration and the Visual Studio 2010 edition you use.) At a minimum, you must change the Arguments and Working Directory fields to match your project.
    Modify the properties to match your project requirements.
  5. Select the Arguments field. Type -D NameOfProject, where NameOfProject is a Python (.py) file. For example, the example project uses MyFirst.py, so you’d type -D MyFirst.py. Remember that the -D command line switch turns on debugging. You can find other command line arguments listed in the “Understanding the IPY.EXE Command Line Syntax” section. Include any other command line switches you want to use in the Arguments field.
  6. Select the Working Directory field. Visual Studio will default to using the Program FilesIronPython 2.6 directory — a directory that you’re unlikely to use to hold your source code files. Change the Working Directory to match your source code directory. Click the ellipses to locate the directory on your hard drive using the Browse for Folder dialog box.
  7. Choose File ➪ Save All. You’ll see the Save File As dialog box shown in Figure 2-4.
    Save the resulting solution before you do anything else.
  8. Locate the folder you want to use to save the project in the Save In field.
  9. Type a name for the solution in the Object Name field. Click Save. Visual Studio will save the project to the folder you selected.

Adding Existing Files to the Project

At this point, you have a project without any files in it. Yes, you could run the project and you’d see what you’d expect, but you can’t debug the IronPython file or edit it. The following steps tell how to add a file to your project.

  1. Right-click the solution entry in Solution Explorer (not the IPY project entry) and choose Add ➪ Exiting Item from the context menu. You’ll see the Add Exiting Item dialog box shown in Figure 2-5.
    Add your existing Python files to the project.
  2. Locate the existing file you want to use and click Open. Visual Studio adds a Solution Items folder to Solution Explorer and places the file you selected in the Solution Items folder, as shown in Figure 2-6. In addition, Visual Studio automatically opens the file for you.
    The Solution Items folder holds the Python files you add.

Adding New Files to the Project

Once you get used to working with Visual Studio, you may decide to create files from scratch using the Visual Studio IDE. In this case, you need to add blank (new) files to the project. The following steps show you how to perform this task.

  1. Right-click the solution entry in Solution Explorer and choose Add ➪ New Item from the context menu. You’ll see the Add New Item dialog box shown in Figure 2-7.
    You can use the Visual Studio IDE to create new Python files.
  2. Highlight the Text File entry. Visual Studio will assume you want to create a text (.TXT) file, but you can change the extension to anything you want.
  3. Type the name of the Python file you want to create in the Name field. Make certain that your file has a .py extension or the IronPython interpreter may not work with it.
  4. Click Add. Visual Studio adds the file to Solution Explorer (similar to the addition shown in Figure 2-6) and automatically opens the file for editing.

IronPython Project Limitations

The project you create using this technique has some serious limitations. Here’s a partial list of the things that you won’t see in your IronPython project that you’ll normally see in other Visual Studio projects.

  • Color support for keywords or other special text
  • IntelliSense
  • New Items dialog support
  • Immediate window (debugging)
  • Command window (when working with variables during debugging)

Debugging the Project

This section assumes you’re using the MyFirst.py example found in Chapter 1 and that you’ve created a project for it. Start by placing a breakpoint on the first line of the application (print(‘5 * 10 =‘),); then place a second breakpoint at the beginning of the function (def mult(a, b):). You can do this by placing your cursor on the line and pressing F9 or choosing Debug ➪ Toggle Breakpoint. You should see the breakpoint shown in Figure 2-8.

Visual Studio helps you debug your IronPython applications.

At this point, you can begin debugging your application. The following steps get you started.

  1. Press F5 or click Start Debugging to begin debugging your application. Starting the debug process can take a while because Visual Studio has to start a copy of the IronPython interpreter. Visual Studio stops at the function definition. IronPython makes a list of function definitions when it starts the application.
  2. Click Step Over. You’ll move to the first line of the application. At this point, the debugger begins executing your application code.
  3. Click Step Over again. If you look at the command prompt at this point, you’ll see that it contains the expected output text, but not the answer, as shown in Figure 2-9. Now, if you clicked Step Over again, you’d see the output from the Mult() function, but you wouldn’t actually see the code in Mult() execute. The next step shows how to get inside a function so you can see how it works.
    The console screen will show the results of tasks performed in your application code.
  4. Press F5 or click Start Debugging. The application will stop within Mult(). Being able to stop within a function is the reason for setting the second breakpoint at the beginning of this procedure. Now you can use Step Over to execute the lines of code one at a time. Notice the Debug History window. You can select entries in this window to see what the IronPython interpreter has been doing in the background, as shown in Figure 2-10.
    Use the Debug History window to see what the interpreter is doing in the background.
  5. Press F5 or click Start Debugging. The application will end.

Visual Studio does provide you with access to many standard debugging features. For example, you can place variables in the Watch windows and see their values as shown in Figure 2-11. You also have access to the Call Stack and Output windows. The Immediate and Command windows don’t work as you might expect them to, so you need to inspect variables and perform other variable-related tasks using the Watch windows.

The Watch windows provide access to variable information.

Debugging Windows Phone 7 Applications

If you are working in Visual Studio or Visual Studio Express, you can use the standard debugging tools to break, step through, and profile a Windows Phone 7 application. You can also view variable values and the execution tree and processes.

A Windows Phone 7 application uses Silverlight and Windows Presentation Foundation (WPF) to define the interfaces, so any debugging techniques you use in Silverlight and WPF also apply to Windows Phone 7. For example, there are some specific issues related to WPF styling and data binding that can arise in an application. These include the following:

  • Errors in the auto-generated partial class for a XAML component that prevent the component from being available to code in the page or the application. In most cases, you can  resolve this by right-clicking the XAML file in Solution Explorer and then clicking Run Custom Tool to regenerate the partialclass.
  • Errors in the XAML style definitions for components. These may occur in the App.xaml file or in other files within your application. Typically, the exception message indicates only that a COM component error has occurred and provides an HRESULT value. The simplest approach to resolving these types of errors are to minimize the surface area by temporarily removing some or all of the XAML styling definitions until the error disappears, and then reading them one by one to locate the faulty definition.
  • Data binding errors, such as using the wrong property name in a binding statement. This does not generate a run-time error, but the control property (such as the text) is not set. You can see a list of data binding errors in the Output window of Visual Studio or Visual Studio Express as the application executes. For example, you will see the following message if you specify binding to a property named City that does not exist in the data context.System.Windows.Data Error: BindingExpression path error:
    ‘City’ property not found on [your data context component
    name]. BindingExpression: Path=’City’ DataItem=’[your
    data context item]’; target element is ‘System.Windows.
    Controls.TextBlock’ (Name=’ItemText’); target property
    is ‘Text’ (type ‘System.String’).

     

  • Remote service access errors. If your application cannot retrieve data from a remote service, try copying the URL of the service from your code into a web browser to see if it is accessible. You can also download and install a web debugging proxy utility such as Fiddler or a similar utility to view all the HTTP(S) traffic between your application and the service. Fiddler is available from the Fiddler website (http://www.fiddler2.com/fiddler2/).

One specific point to note when debugging Windows Phone 7 applications using the emulator is that certain actions within the phone may halt the debugger. For example, if the code opens a task or the launcher, or if it is tombstoned, execution may not continue automatically after the interruption. To continue debugging, press F5 in Visual Studio within 10 seconds of the task completing or the applications being reactivated.