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.

 

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 the DLR

The Dynamic Language Runtime (DLR) is a new feature of the .NET platform. Its intended purpose is to support dynamic languages, such as Python (through IronPython) and Ruby (through IronRuby). Without DLR, the .NET Framework can’t really run dynamic languages. In addition, DLR provides interoperability between dynamic languages, the .NET Framework, and static languages such as C# and Visual Basic.NET. Without DLR, dynamic and static languages can’t communicate. In order to meet these goals, DLR must provide basic functionality that marshals data and code calls between the dynamic and static environments. This functionality comes in a number of forms that are discussed in this chapter. You might be surprised to find that you’ve already used many of these features throughout the book. Here’s the list of features that DLR supports in order to accomplish its goals.

  • Hosting Application Programming Interfaces (APIs): In order to run dynamic language scripts, the host language must have access to the scripting engine. The Hosting APIs provide the support needed to host the dynamic language within the host environment through the scripting engine. This marshaling of code and data makes it possible to seamlessly integrate static and dynamic languages.
  • Extensions to Language Integrated Query (LINQ) ExpressionTree: Normally, a language would need to convert data, objects, and code into Microsoft Intermediate Language (MSIL) before it could translate anything into another language. Because all .NET languages eventually end up as MSIL, MSIL is the common language for all higher-level .NET languages. These extensions make it possible for language compilers to create higher-level constructs for communication purposes, rather than always relying on MSIL. The result is that the marshaling process takes less time and the application runs faster.
  • DynamicSite: This feature provides a call-site cache that dynamic languages use in place of making constant calls to other .NET languages. Because the call-site cache is already in a form that the dynamic language can use, the overall speed of the dynamic language application improves.
  • IDynamicObject: An interface used to interact with dynamic objects directly. If you create a class that implements IDynamicObject, DLR lets the class perform the required conversions, rather than rely on the built-in functionality. Essentially, you create an object that can have methods, properties, and events added dynamically during run time. You use IDynamicObject when you want to implement custom behaviors in your class.
  • ActionBinder: The ActionBinder is a utility that helps support .NET interoperability. The ActionBinder is language specific. It ensures that conversions of variable data, return values, and arguments all follow language-specific behaviors so that the host language sees the data in a form it understands.

These are the main tasks that DLR performs. Of course, it also provides other compiler utilities that you need to know about. The final section in this chapter provides an overview of these other features.

DLR is a constantly changing technology today, so you’ll want to keep up with the additions and changes to DLR. One of the better places to find general DLR resources online is at http://blogs.msdn.com/ironpython/ archive/2008/03/16/dlr-resources.aspx. This chapter also provides a number of specific resources you can use to discover more about DLR. The point is to keep track of what’s going on with this exciting technology and review your code as things change.

Obtaining DLR

It’s important to remember that IronPython relies on DLR to perform just about every task that IronPython executes. Therefore, you already have access to a certain level of DLR, even if you don’t install anything or do anything special. In fact, you’re using DLR in the background every time you use IronPython. However, you’re using DLR without really knowing it exists and without understanding what DLR itself can do for your application. So while you can use the direct approach to DLR, it can prove frustrating and less than friendly.

In order to truly understand DLR, you at least need documentation. Better yet, you can download the entire DLR package and begin to understand the true impact of this product. If nothing else, spend some time viewing the available components at http://www.codeplex.com/dlr. The following sections describe various methods of gaining access to DLR so you can use it to perform some custom tasks.

Using the Direct Method

The direct method is the easiest way to obtain the benefits of DLR, but it’s also the most limited. You simply add a reference to the IronPython.DLL file located in the Program FilesIronPython 2.6 folder of your hard drive. This technique works fine for embedding IronPython scripts in your C# or Visual Basic.NET application. In fact, you gain access to the following classes:

  • IronPython
  • IronPython.Compiler
  • IronPython.Compiler.Ast
  • IronPython.Hosting
  • IronPython.Modules
  • IronPython.Runtime
  • IronPython.Runtime.Binding
  • IronPython.Runtime.Exceptions
  • IronPython.Runtime.Operations
  • IronPython.Runtime.Types

For many developers, this is all the DLR support you need, especially if your application only requires cross-language support through the Hosting APIs. (You’ll still want to download the documentation that’s available on the main DLR Web site — the section “Downloading the Documentation” later in this chapter explains how to perform this task.) The following steps describe how to add the required reference to gain access to these classes.

  1. Create the .NET project.
  2. Right-click References in Solution Explorer and choose Add Reference from the context menu. You see the Add Reference dialog box.
  3. Select the Browse tab and locate the IronPython.DLL file, as shown in Figure 14-1.
  4. Click OK. Visual Studio adds the required reference to your project.
Add the IronPython.DLL file to your project.
Figure 14-1: Add the IronPython.DLL file to your project.

You make use of IronPython.DLL as you would any other .NET assembly. Simply add the required use or Imports statement to your code. The examples throughout the book tell you about these requirements for the individual example.

Downloading the Full DLR

If you really want to experience DLR, you need the complete package. The full DLR consists of a number of components and even the source code weighs in at a hefty 10.5 MB.

Before you begin the download, check out the release notes at http://dlr.codeplex.com/ wikipage?title=0.92_release_notes for additional information about DLR. For example, you might decide to get an IronPython- or IronRuby-specific download. The full release includes both language products (which can be helpful, even if you use only one of them).

You obtain the full DLR from http://dlr.codeplex.com/Release/ProjectReleases .aspx?ReleaseId=34834. When you click the DLR-0.92-Src.zip link, you see a licensing dialog box. Click I Agree to begin the download process.

After the download completes, extract the resulting DLR-0.92-Src.ZIP file into its own folder. The resulting Codeplex-DLR-0.92 folder contains the following items.

  • License.HTML and License.RTF: You can read the same licensing information in two different formats. Use whichever form works best for you.
  • Docs: A folder containing the complete documentation for DLR. The best place to begin is the DLR-Overview.DOC file.
  • Samples: A folder containing a number of sample applications that demonstrate DLR features. There’s only one IronPython sample in the whole batch — you’ll find it in the Codeplex-DLR-0.92SamplesSilverlightAppPythonpython folder.
  • Source: A folder that contains the complete DLR source code that you need to compile in order to use DLR to create applications. This folder should be your first stop after you read the DLR-Overview.DOC file.

Building the Full DLR

Before you can use DLR, you must build it. The previous section explains how to download a copy of the DLR source. The following sections describe three methods you can use to build DLR. For most developers, the easiest and fastest method is the command line build. However, if you want to review the code before you use it, you might want to load the solution in Visual Studio and take a peek.

Performing a Command Line Build

The command line build option requires that you use the Visual Studio command line, not a standard command line (which doesn’t contain a path to the utilities you need). The following steps describe how to perform the command line build:

  1. Choose Start ➪ Programs ➪ Microsoft Visual Studio 2008 ➪ Visual Studio Tools ➪ Visual Studio 2008 Command Prompt or Start ➪ Programs ➪ Microsoft Visual Studio 2010 ➪ ➤ Visual Studio Tools ➪ Visual Studio Command Prompt (2010). You’ll see a command prompt.
  2. Type CD Codeplex-DLR-0.92Src and press Enter. This command places you in the DLR source code directory.
  3. Type MSBuild Codeplex-DLR.SLN (when using Visual Studio 2008) or MSBuild Codeplex-DLR-Dev10.SLN (when using Visual Studio 2010) and press Enter. By default, you get a debug build. Use the /p:Configuration=Release command line switch (as in MSBuild Codeplex-DLR.SLN /p:Configuration=Release or MSBuild Codeplex-DLR-Dev10.SLN /p:Configuration=Release) to obtain a release build. You see a lot of text appear onscreen as MSBuild creates the DLR DLLs for you. Some of the text will appear unreadable (Microsoft uses some odd color combinations). When the build process is complete, you should see 0 Error(s) as the output, along with a build time, as shown in Figure 14-2. (If you don’t see a 0 error output, you should probably download the files again because there is probably an error in the files you downloaded.)
The build process should show 0 Error(s) as the output message.
Figure 14-2: The build process should show 0 Error(s) as the output message.

Don’t look for the output in the source code folders. The output from the build process appears in the Codeplex-DLR-0.92Bin40 folder when working with Visual Studio 2010, no matter which technique you use to build DLR. Visual Studio 2008 developers will find their output in the Codeplex-DLR-0.92 BinDebug or Codeplex-DLR-0.92BinRelease folders, depending on the kind of build created. Visual Studio 2008 developers will also find a separate Codeplex-DLR-0.92BinSilverlight Debug or Codeplex-DLR-0.92Bin Silverlight Release folder for Silverlight use.

Performing a Visual Studio 2008 Build

Some developers will want to perform a build from within Visual Studio 2008. To perform this task, simply double-click the Codeplex-DLR.SLN icon in the Codeplex-DLR-0.92Src folder. Choose Build ➪ Build Solution or press Ctrl+Shift+B. You’ll see a series of messages in the Output window. When the process is complete, you should see, “Build: 23 succeeded or up-to-date, 0 failed, 1 skipped” as the output.

You must select each of the options in the Solution Configurations combo box in turn and perform a build to create a complete setup. Otherwise, you’ll end up with just the Release build or just the Debug build. If you need Silverlight or FxCop support, you must also create these builds individually.

Don’t worry if you see a number of messages stating

[code]
Project file contains ToolsVersion=”4.0”, which is not supported by this
version of MSBuild. Treating the project as if it had ToolsVersion=”3.5”.
[/code]

because this is normal when using Visual Studio 2008. You’ll also see a number of warning messages (a total of 59 for the current DLR build) in the Errors window, which you can ignore when using the current release.

Performing a Visual Studio 2010 Build

A release version of DLR will build better if you have a copy of Visual Studio 2010 on your system. To perform this task, simply double-click the Codeplex-DLR-Dev10.SLN icon in the Codeplex-DLR-0.92Src folder. Set the Solution Configurations option to Release or Debug as needed (there aren’t any options to build Silverlight or FxCop output). Choose Build ➪ Build Solution or press Ctrl+Shift+B. You’ll see a series of messages in the Output window. When the process is complete, you should see, “Build: 15 succeeded or up-to-date, 0 failed, 2 skipped” as the output. The Warnings tab of the Error List window should show 24 warnings.

Downloading the Documentation

The download you performed earlier provides code and documentation, but you might find that the documentation is outdated. As with everything else about DLR, the documentation is in a constant state of flux. If you want to use DLR directly, then you need the documentation found at http://dlr.codeplex.com/wikipage?title=Docs and specs&referringTitle=Home. Unfortunately, you have to download each document separately

Reporting Bugs and Other Issues

At some point, you’ll run into something that doesn’t work as expected. Of course, this problem even occurs with production code, but you’ll definitely run into problems when using the current release of DLR. In this case, check out the listing of issues at http://www.codeplex.com/dlr/WorkItem/List.aspx. If you don’t find an issue entry that matches the problem you’re experiencing, make sure you report the bug online so it gets fixed. Of course, reporting applies equally to code and documentation. Documentation errors are often harder to find and fix than coding errors — at least where developers are concerned — because it’s easier to see the coding error in many cases.

Working with Hosting APIs

In fact, you may have even wondered whether it’s possible to use IronPython as a scripting language for your next application. Fortunately, you can use IronPython as the scripting language for your next application by relying on the Hosting APIs. It turns out that a lot of people have considered IronPython an optimal language for the task. The following sections consider a number of Hosting API questions, such as how you can use it in an actual application, what the host application needs in order to use the Hosting APIs, and what you’d need to do to embed IronPython as a scripting language in an application.

Using the Hosting APIs

The DLR specification lists a number of hosting scenarios, such as operating on dynamic objects you create within C# or Visual Basic.NET applications. (See the section “Working with IDynamicObject” later in this chapter for details on dynamic objects in C# and Visual Basic.NET.) It’s also possible to use the Hosting APIs to create a scripting environment within Silverlight or other types of Web applications.

Whatever sort of host environment you create, you can use it to execute code snippets or entire applications found in files. The script run time can appear locally or within a remote application so you can use this functionality to create agent applications or scripting that relies on server support. The Hosting APIs make it possible to choose a specific scripting engine to execute the code or to let DLR choose the most appropriate scripting engine for the task. This second option might seem foolhardy, but it can actually let your code use the most recent scripting engine, even if that engine wasn’t available at the time you wrote the host environment code.

Chaos could result if you couldn’t control the extent (range) of the script execution in some way. For example, two developers could create variables with the same name in different areas of the application. The Hosting APIs make it possible to add scope to script execution. The scope acts much like a namespace does when writing code. Just as a namespace eliminates naming conflicts in assemblies, scoping eliminates them in the scripting environment. Executing the code within a scope also provides it with an execution context (controlled using a ScriptScope). Scopes are either public or private, with private scopes providing a measure of protection for the scripting environment. A script can also import scopes for use within the environment or require the host to support a certain scope to execute the script.

The Hosting APIs also provide support for other functionality. For example, you can employ reflection to obtain information about object members, obtain parameter information, and view documentation. You can also control how the scripting engine resolves file content when dynamic languages import code files.

Understanding the Hosting APIs Usage Levels

The DLR documentation specifies that most developers will use the Hosting APIs at one of three levels that are dictated by application requirements. Here are the three basic levels.

  • Basic code: The basic code level (Level 1 in the documentation) relies on a few basic types to execute code within scopes. The code can interact with variable bindings within those scopes.
  • Advanced code execution: The next level (Level 2 in the documentation) adds intermediate types that provide additional control over how code executes. In addition, this level supports using compiled code in various scopes and permits use of various code sources.
  • Support overrides: The final level (Level 3 in the documentation) provides methods to override how DLR resolves filenames. The application can also use custom source content readers, reflect over objects for design-time tool support, provide late bound variable values from the host, and use remote ScriptRuntime objects.

The concept of a ScriptRuntime object is central to working with the Hosting APIs. A host always begins a session by creating the ScriptRuntime object and then using that object to perform tasks. You can create a ScriptRuntime object using several methods. Of course, the easiest method is to use the standard constructor, which requires a ScriptRuntimeSetup object as input. It’s also possible to create a ScriptRuntime object using these methods

  • ScriptRuntime.CreateFromConfiguration(): A factory method that lets you use a preconfigured scope to create the ScriptRuntime object. In fact, this factor method is just short for new ScriptRuntime(ScriptRuntimeSetup.ReadConfiguration()).
  • ScriptRuntime.CreateRemote(): A factory method that helps you to create a ScriptRuntime object in another domain. The code must meet strict requirements to perform remote execution. See Section 4.1.3, “Create* Methods,” in the Hosting APIs specification for details.

At its name implies, a ScriptRuntimeSetup object gives a host full control over the ScriptRuntime object configuration. The ScriptRuntimeSetup object contains settings for debug mode, private execution, the host type, host arguments, and other setup features. Simply creating a ScriptRuntimeSetup object sets the defaults for executing a script. Once you use a ScriptRuntimeSetup object to create a ScriptRuntime object, you can’t change the settings — doing so will raise an exception.

The Hosting APIs actually support a number of objects that you use to create a scripting environment, load the code you want to execute, and control the execution process. The figure at http:// www.flickr.com/photos/john_lam/2220796647/ provides an overview of these objects and how you normally use them within the hosting session.

It’s important to isolate code during execution. The Hosting APIs provide three levels of isolation.

  • AppDomain: The highest isolation level, which affects the entire application. The AppDomain lets you execute code at different trust levels, and load and unload code as needed.
  • ScriptRuntime: Every AppDomain can have multiple ScriptRuntimes within it. Each ScriptRuntime object can have different name bindings, use different .NET assemblies, have different settings (one can be in debug mode, while another might not), and provide other settings and options support.
  • ScriptScope: Every ScriptRuntime can contain multiple ScriptScopes. A ScriptScope can provide variable binding isolation. In addition, you can use a ScriptScope to give executable code specific permissions.

Now that you have a better idea of how the pieces fit together, it’s important to consider which pieces you use to embed scripting support within an application. Generally, if you want basic code (Level 1) support, all you need are the objects shown in green at http://www.flickr.com/photos/ john_lam/2220796647/. In fact, if you want to use the default ScriptScope settings, all you really need to do is create the ScriptRuntime and then use the default ScriptScope.

Considering the Host Application

A host has to meet specific requirements before it can run IronPython as a scripting language. Chapter 15 discusses more of the details for C# and Visual Basic.NET developers. You’ll find that C# and Visual Basic.NET provide everything you need. However, it’s interesting to see just what the requirements are, especially if you’re using an older version of these languages. Section 3 of the DLR-Spec-Hosting.DOC file found in the Codeplex-DLR-0.92Docs folder contains complete information about the hosting requirements. Section 3.3 (and associated subsections) are especially important for most developers to read if they plan to use the Hosting APIs for anything special.

Embedding IronPython as a Scripting Language

Imagine that you’ve created a custom editor in your application where users can write IronPython scripts. They then save the script to disk (or you could read it from memory), and then they assign the script to a button or menu in your application. When the user selects the button or menu, your application executes the script. Creating this scenario isn’t as hard as you might imagine. DLR comes with most of the functionality you need built in.

Of course, you need a test script to start. Listing 14-1 shows the test script for this example. The example is purposely simple so that the example doesn’t become more focused on the IronPython code than the code that executes it. However, you could easily use any script you want as long as it’s a legitimate IronPython script.

Listin g 14-1: A simple IronPython script to execute

[code]
# A simple function call.
def mult(a, b):
return a * b
# Create a variable to hold the output.
Output = mult(5,10)
# Display the output.
print(‘5 * 10 =’),
print(Output)
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

In this case, the example has a simple function, mult(), that multiplies two numbers together. The __main__() part of the script multiplies two numbers and displays the result using the print() function. In short, the script isn’t very complicated.

Now that you have a script, you need to create an application to execute it. The example is a simple console application. In order to create the IronPython ScriptRuntime object, you need access to some of the IronPython assemblies. Right-click References in Solution Explorer and choose Add Reference from the context menu. You see the Add Reference dialog box shown in Figure 14-3. Ctrl+click each of the entries shown in Figure 14-3, then click OK to add them to your project.

Add the required references from your IronPython setup.
Figure 14-3: Add the required references from your IronPython setup.

The example also requires that you add using statements for a number of the assemblies. Here are the using statements that you must add for this example.

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

Now that the console project is set up, you can begin coding it. This example is very simple, but it actually works. You can execute an IronPython script using this technique. Of course, you can’t interact with it much. Chapter 15 provides more detailed examples, but this example is a good starting place. Listing 14-2 shows the minimum code you need to execute an IronPython script and display the result of executing it onscreen.

Listin g 14-2: Executing the IronPython script

[code]
static void Main(string[] args)
{
// Create an IronPython ScriptRuntime.
ScriptRuntime Runtime = IronPython.Hosting.Python.CreateRuntime();
// Execute the script file and return scope information about
// the task.
ScriptScope Scope = Runtime.ExecuteFile(“Test.py”);
// Display the name of the file executed.
Console.WriteLine(“rnExecuted {0}“,
Scope.GetVariable<string>(“__name__“));
// Keep the output visible.
Console.WriteLine(“rnPress any key…”);
Console.ReadLine();
}
[/code]

The code begins by creating the ScriptRuntime object, Runtime. Notice that you create this object by directly accessing the IronPython assemblies, rather than the DLR assemblies. There are many ways to accomplish this task, but using the technique shown is the simplest. The Runtime object contains default settings for everything. For example, this ScriptRuntime doesn’t provide debugging capability. Consequently, this technique is only useful when you have a debugged script to work with and may not do everything needed in a production environment where you let users execute their own scripts as part of an application.

The Runtime.ExecuteFile() method is just one of several ways to execute a script. You use it when a script appears in a file on disk, as is the case for this example. When you call the Runtime .ExecuteFile() method, your application actually calls on the IronPython interpreter to execute the code. The output from the script appears in Figure 14-4. As you can see, the code executes as you expect without any interference from the host. In fact, you can’t even tell that the application has a host.

The script output appears as you might expect.
Figure 14-4: The script output appears as you might expect.

When the Runtime.ExecuteFile() method call returns, the C# application that executed the script receives a ScriptScope object that it can use to interact with the application in various ways. This ScriptScope object, like the ScriptRuntime object, contains all the usual defaults. It’s a good idea to examine both Runtime and Scope in the debugger to see what these objects contain because you’ll find useful information in both.

The script is running in a host application. In fact, they share the same console window. To show how this works, the example writes output to the console window. It retrieves the __name__ property from Scope and displays it onscreen with the message, as shown in Figure 14-5. The point of this example is that the IronPython script truly is hosted and not running on its own. The technique shown here lets you perform simple interactions between C# or Visual Basic.NET and IronPython.

The output shows that the host and the IronPython environment share the same console.
Figure 14-5: The output shows that the host and the IronPython environment share the same console.

Understanding the Extensions to LINQ Expression Tree

Part of the premise behind DLR is that every .NET language eventually ends up in Microsoft Intermediate Language (MSIL) form. Whether you use C# or Visual Basic.NET, or even managed C++, the output from the compiler is MSIL. That’s how the various languages can get along. They rely on MSIL as an intermediary so that managed languages can work together.

The problem with compiling everything to MSIL is that MSIL doesn’t necessarily perform tasks quickly or easily when working with dynamic languages such as IronPython. It would be far easier if there were a mechanism for translating the code directly into something that C# or Visual Basic .NET could use. That’s where the LINQ Expression Tree (ET) comes into play. A LINQ ET can represent IronPython or other code (such as JavaScript) in a tree form that DLR can then translate into something that C# or Visual Basic.NET can understand. The result is a DLR tree that presents the code in an easily analyzable and mutable form. The example at http://blogs.msdn.com/hugunin/ archive/2007/05/15/dlr-trees-part-1.aspx explains how DRL trees work graphically. In this case, the author explains how a DLR tree can represent a JavaScript application — the same technique also applies to IronPython.

The LINQ ET originally appeared in the .NET Framework 3.5. In its original form, Microsoft used the LINQ ET to model LINQ expressions written in C# and Visual Basic.NET. In the .NET Framework 4.0, Microsoft added extensions for a number of reasons. For the purposes of this book, the most important reason to extend LINQ ETs is to accommodate the DLR semantics used to translate IronPython code into something that C# and Visual Basic.NET can understand.

DLR trees work in the background. It’s helpful to know they exist, but you generally won’t worry about them when working with IronPython so this section is short. However, let’s say you want to create a scripting language for your application that isn’t as complex as IronPython. Perhaps you want to implement an editor and everything that goes with it in your application. In this case, you may very well want to work with DLR trees. The examples found at http://weblogs.asp.net/ podwysocki/archive/2008/02/08/adventures-in-compilers-building-on-the-dlr.aspx show what you need to do to create your own language compiler. Once you have a compiler like this built, you could execute the code using a technique similar to the one shown in Listing 14-2.

It’s important to consider one word of warning, however, when working with the current version of DLR trees. As you scan through the specification, you’ll find that the authors have left behind copious notes about issues that aren’t resolved now or features that were left out of the current implementation due to a lack of time. The result is conversations such as the one at http://stackoverflow.com/ questions/250377/are-linq-expression-trees-turing-complete. If you look at section 2.4.1 of the specification, you find that a higher-level looping mechanism was indeed cut, but Microsoft is aware of the problem and plans to implement the feature in the future. In short, DLR trees have limits that you need to consider before implementing them in your application.

Considering DynamicSite

When working with a static language such as C# or Visual Basic.NET, the compiler knows what to emit in the form of MSIL based on the code the developer provides. However, dynamic code isn’t static — it can change based on any of a number of factors. One problem with dynamic languages is that DLR doesn’t always know what to emit during compile time because the real time event hasn’t occurred yet. Of course, the static language still needs some code in place because static languages need to know what to do at compile time. This seeming conundrum is handled by invoking a DynamicSite object. Using a DynamicSite object means that the static language knows what to call at compile time and DLR can fill the DynamicSite object with executable code during run time.

As with many parts of DLR, the action takes place behind the scenes — you don’t even know it occurs. However, it’s useful to know what happens so you at least know what to suspect when an error occurs. The act of invoking the DynamicSite method creates an operation to perform and a delegate. The delegate contains caching logic that is updated every time the arguments change. In short, as the dynamic language changes, DLR generates events that change the content of the cache as well.

At the center of working with DynamicSite is the UpdateBindingAndInvoke() method. The first time that application code calls the DynamicSite object, the UpdateBindingAndInvoke() method queries the arguments for the specified code. For example, the code might be something simple such as x + y, so the query would request the types of x and y. At this point, UpdateBindingAndInvoke() generates a delegate that contains the implementation of the code.

The next time the application invokes the DynamicSite object, the delegate checks the arguments in the call against those in the cache. If the argument types match, then the delegate simply uses the current implementation of the code. However, if the arguments are different, then the delegate calls UpdateBindingAndInvoke(), which creates a new delegate that contains a definition of the new code with the updated arguments. The new delegate contains checks for both sets of argument types and calls the appropriate implementation based on the arguments it receives. Of course, if none of the argument sets match the call, then the process starts over again with a call to UpdateBindingAndInvoke().

Working with IDynamicObject

This section discusses the IDynamicObject interface provided as part of DLR, which doesn’t affect IronPython directly, but could affect how you use other languages to interact with IronPython. You can easily skip this section and leave it for later reading if you plan to work exclusively with IronPython for the time being. This is a very short discussion of the topic that is meant to fill in the information you have about DLR and its use with IronPython.

As mentioned throughout the book, C# and Visual Basic.NET are both static languages. Microsoft doesn’t appear to have any desire to change this situation in upcoming versions of either language. Consequently, you can’t create dynamic types using C# or Visual Basic. There isn’t any technique for defining missing methods or dynamic classes using either language. However, you can consume dynamic types defined using a new interface, IDynamicObject.

The IDynamicObject interface tells DLR that the class knows how to dispatch operations on itself. In some respects, IDynamicInterface is a kind of managed form of the IQueryable interface that C++ developers use when creating COM objects. The concept isn’t new, but the implementation of it in the .NET environment is new.

There are many levels of complexity that you can build into your dynamic implementation. The example in this section is a very simple shell that you can build on when creating a fullfledged application. It’s designed to show a common implementation that you might use in an application. You can see another simple example at http://blogs.msdn.com/csharpfaq/ archive/2009/10/19/dynamic-in-c-4-0-creating-wrappers-with-dynamicobject.aspx.

The starting point for this example is a class that implements DynamicObject. In order to create such a class, you need to include the following using statements:

[code]
using System;
using System.Dynamic;
[/code]

The class is called ADynamicObject and appears in Listing 14-3.

Listin g 14-3: Creating a class to handle dynamic objects

[code]
// Any dynamic object you create must implement IDynamicObject.
public class ADynamicObject : DynamicObject
{
// Calls a method provided with the dynamic object.
public override bool TryInvokeMember(InvokeMemberBinder binder,
object[] args, out object result)
{
Console.WriteLine(“InvokeMember of method {0}.”, binder.Name);
if (args.Length > 0)
{
Console.WriteLine(“tMethod call has {0} arguments.”, args.Length);
for (int i = 0; i < args.Length; i++)
Console.WriteLine(“ttArgument {0} is {1}.”, i, args[i]);
}
result = binder.Name;
return true;
}
// Gets the property value.
public override bool TryGetMember(GetMemberBinder binder,
out object result)
{
Console.WriteLine(“GetMember of property {0}.”, binder.Name);
result = binder.Name;
return true;
}
// Sets the property value.
public override bool TrySetMember(SetMemberBinder binder, object value)
Console.WriteLine(“SetMember of property {0} to {1}.”,
binder.Name, value);
return true;
}
}
[/code]

In this case, the code provides the ability to call methods, get property values, and set property values. Amazingly, DLR automatically calls the correct method without any hints from you.

Notice that each of the methods uses a different binder class: InvokeMemberBinder, GetMemberBinder, or SetMemberBinder as needed. The binder provides you with information about the member of interest. In most cases, you use the member name to locate the member within the dynamic object. In this case, the code simply displays the member name onscreen so you can see that the code called the correct member.

Two of these methods, TryInvokeMember() and TryGetMember(), return something to the caller. It’s important to remember that the data is marshaled, so you must use the out keyword for the argument that returns a value or the application will complain later (the compiler may very well accept the error without comment). In both cases, the code simply returns the binder.Name value. If you were building this dynamic object class for an application, you’d use the binder.Name value to access the actual property or method.

When invoking a method, the TryInvokeMember() method receives an array of arguments to use with the method call. The code shows how you detect the presence of arguments and then displays them onscreen for this example. In an actual application, you’d need to compare the arguments provided by the caller against those required by the method to ensure the caller has supplied enough arguments of the right type.

All three methods return true. If the code were to return false instead, you’d see a RuntimeBinderException in the caller code. This exception tells the caller that the requested method or property doesn’t exist.

When a C# application desires to create a dynamic object, it simply creates an instance of the dynamic class. The instance can create properties, methods, or other constructs as needed. Listing 14-4 shows an example of how a test application might appear.

Listin g 14-4: Using the ADynamicObject class

[code]
class Test
{
static void Main()
{
// Create a new dynamic object.
dynamic DynObject = new ADynamicObject();
// Set a property to a specific value.
Console.WriteLine(“Setting a Property to a Value”);
DynObject.AProp = 5;
// Use one property to set another property.
// You would see a property get, followed by a property set.
Console.WriteLine(“rnSetting a Property to another Property”);
DynObject.Prop1 = DynObject.AProp;
// Call a method and set its output to a property.
// You would see a method call, followed by a property set.
Console.WriteLine(“rnSetting a Property to a Method Output”);
DynObject.Prop2 = DynObject.AMethod();
// Call a method with a property argument and set a new property.
// You would see a property get, a method call, and finally a
// property set.
Console.WriteLine(“rnSetting a Property to Method Output with Args”);
DynObject.Prop3 = DynObject.AMethod(DynObject.AProp);
// Wait to see the results.
Console.WriteLine(“rnPress any key when ready…”);
Console.ReadLine();
}
}
[/code]

Notice that the code begins by creating a new dynamic object using the dynamic keyword. At this point, you can begin adding properties and methods to the resulting DynObject. Properties can receive values directly, from other properties, or from methods. Methods can use arguments to change their output. Figure 14-6 shows the output from this example. The path that the code takes through the various objects helps you understand how dynamic objects work.

The DynamicObject class actually provides support for a number of members. You can use these members to provide a complete dynamic implementation for your application. Here’s a list of the DynamicObject members you can override.

  • GetDynamicMemberNames()
  • GetMetaObject()
  • TryBinaryOperation()
  • TryConvert()
  • TryDeleteIndex()
  • TryDeleteMember()
  • TryGetIndex()
  • TryGetMember()
  • TryInvoke()
  • TryInvokeMember()
  • TrySetIndex()
  • TrySetMember()
  • TryUnaryOperation()
The output shows the process used to work with dynamic objects.
Figure 14-6: The output shows the process used to work with dynamic objects.

The point of all this is that you can implement a kind of dynamic object strategy for static languages, but it’s cumbersome compared to IronPython. You might use this approach when you need to provide a dynamic strategy for something small within C# or Visual Basic. This technique is also useful for understanding how IronPython works, at a very basic level. IronPython is far more robust than the code shown in this example, but the theory is the same.

Understanding the ActionBinder

DLR makes it possible to invoke dynamic code from within a static environment using a DynamicSite object. The actual process for creating the method invocation call is to create an Abstract Syntax Tree (AST). The AST has functions assigned to it using an Assign() method. When DLR wants to assign a new function to AST, it supplies a function name and provides a calling syntax using the Call() method. The Call() method accepts four arguments.

  • An object used to hold the function. Normally, the code calls the Create() method of the host class using GetMethod(“Create”).
  • A constant containing the name of the function as it appears within the host object.
  • The array of arguments supplied to the function.
  • A delegate instance used to invoke the code later. It’s this argument that you consider when working with an ActionBinder.

At this point, you have an object that holds the parameters of the function call, as well as a delegate used to execute the function. The problem now is one of determining how to call the function. After all, the rest of your code knows nothing about the delegate if you create it during run time, as is the case when working with dynamic languages. If none of the code knows about the delegate, there must be some way to call it other than directly.

To make rules work, your code has to include a GetRule() method that returns a StandardRule object. Inside GetRule() is a switch that selects an action based on the kind of action that DLR requests, such as a call (DynamicActionKind.Call). When DLR makes this request, the code creates a StandardRule object that contains an ActionBinder. The ActionBinder determines what kind of action the call performs. For example, you might decide that the ActionBinder should be LanguageContext.Binder, which defines a language context for the function. The language context is a definition of the language’s properties, such as its name, identifier, version, and specialized features. (You can learn more about how a language context works at http://www.dotnetguru .org/us/dlrus/DLR2.htm.) The code then calls SetCallRule() with the StandardRule object, the ActionBinder, and a list of arguments for the function.

Now, here’s the important consideration for this section. The ActionBinder is actually part of the language design. If you wanted to create a new language, then part of the design process is to design an ActionBinder for it. The ActionBinder performs an immense amount of work. For example, a call to ActionBinder.ConvertExpression() provides conversion information about the data types that the language supports. Of course, IronPython already performs this task for you, but it’s important to know how things work under the hood in case you encounter problems.

Understanding the Other DLR Features

DLR is a moving target at the time of this writing. The latest release, 0.92, isn’t even considered production code as of yet. Consequently, you might find that the version of DLR that you use has features not described in this chapter because they weren’t available at the time of this writing.

An ExpandoObject is a dynamic property bag. Essentially, you fill it with data you want to move from one language to another. It works just like any other property bag you’ve used in the past. Because the ExpandoObject class implements IDynamicMetaObjectProvider, you can use it with dynamic languages such as IronPython. You use this object when moving data from C# or Visual Basic.NET to IronPython.

Working with XML Data

Using the .NET XML Functionality

If you know how to work with XML in .NET languages such as C#, then you already know how to perform the same tasks in IronPython because you can import the System.Xml assembly to gain full access to this functionality. Because the XML capabilities of the .NET Framework are so well defined, using the System.Xml assembly may be all you need to perform tasks within your application. The main issue to consider is how you plan to use your application later. For example, if you plan to move your application to another platform, then using the .NET Framework solution won’t work. In addition, you need to consider data type translation in IronPython. The .NET data types that you use normally are translated into their IronPython counterparts, which could prove confusing for some developers. With these factors in mind, the following sections provide an overview of XML support in the .NET Framework from the IronPython perspective.

Considering the System.Xml Namespace

The System.Xml namespace provides access to the various classes used to interact with XML data. You use these classes to read, write, interpret, edit, build, and otherwise manage XML data. For example, you might use the XmlDeclaration class to begin building an XML data file from scratch when needed. All of these classes depend heavily on standards to ensure the file you create using IronPython is readable by other languages and applications. In fact, the System.Xml namespace supports these standards and specifications.

  • XML 1.0 (including Document Type Definition, DTD, support): http://www.w3.org/ TR/1998/REC-xml-19980210
  • XML Namespaces (both stream level and Document Object Model, DOM): http://www.w3.org/TR/REC-xml-names/
  • XSD Schemas: http://www.w3.org/2001/XMLSchema
  • XPath expressions: http://www.w3.org/TR/xpath
  • XSLT transformations: http://www.w3.org/TR/xslt
  • DOM Level 1 Core: http://www.w3.org/TR/REC-DOM-Level-1/
  • DOM Level 2 Core: http://www.w3.org/TR/DOM-Level-2/

Developing a Basic .NET XML Application

A .NET XML application will follow most of the same principles you use when working with a static language such as C# or Visual Basic.NET. In fact, you might not notice much difference at all except for the obvious structural requirements of a Python application. Consequently, you should find it easy to move your XML code over to IronPython because you really don’t have anything new to worry about. Listing 13-1 shows a simple XML application that creates an XML document, saves it to disk, reads it from disk, and then displays the content onscreen.

DOM-Only Support in the .NET Framework

It’s important to note that the .NET Framework supports DOM and not Simple API for XML (SAX). However, if you want SAX support, you can use the Python modules instead (see the “Working with xml.sax” section of this chapter). XML files include both data and context. In order to reconstruct the original dataset described by an XML file, you need a parser to read the text and then convert it to a usable object. DOM and SAX represent two different methods for interacting with XML documents without forcing the developer to create a parser. If you want more information about the DOM versus SAX approach to parsing XML parsers, check out the information at http://developerlife.com/tutorials/?p=28 and http://www.jamesh.id.au/ articles/libxml-sax/libxml-sax.html. Here’s a summary of the DOM features.

  • Object-based.
  • Object module is created automatically.
  • Element sequencing is preserved.
  • High memory usage.
  • Slow initial data retrieval.
  • Best for complex data structures.
  • In-memory document updates are supported.

SAX takes a completely different approach than DOM. Here’s a summary of the SAX features.

  • Event-based.
  • Object module is created by the application.
  • Element sequencing is ignored in favor of single events.
  • Low memory usage.
  • Fast initial data retrieval.
  • Best for simple data structures.
  • No document updates.

Listing 13-1: Reading and writing an XML document

[code]
# Import clr to add references.
import clr
# Add the required reference.
clr.AddReference(‘System.Xml’)
# Import the System.Xml classes.
from System.Xml import *
# This function creates the document and writes it to disk.
def CreateDocument():
# Create a document.
Doc = XmlDocument()
# Add the XML Declaration.
Declaration = Doc.CreateXmlDeclaration(‘1.0’, ‘utf-8’, ‘yes’)
Doc.AppendChild(Declaration)
# Create the root node.
Root = Doc.CreateNode(XmlNodeType.Element, ‘root’, None)
# Add child elements to the root.
MsgNode = Doc.CreateNode(XmlNodeType.Element, ‘Message’, None)
MsgNode.InnerXml = ‘Hello’
Root.AppendChild(MsgNode)
MsgNode = Doc.CreateNode(XmlNodeType.Element, ‘Message’, None)
MsgNode.InnerXml = ‘Goodbye’
Root.AppendChild(MsgNode)
# Add the root node to the document.
Doc.AppendChild(Root)
# Save the document to disk.
Doc.Save(‘Test.XML’)
def DisplayDocument():
# Create a document.
XMLDoc = XmlDocument()
# Load the XML data.
XMLDoc.Load(‘Test.XML’)
# Process the document.
for Nodes in XMLDoc:
if type(Nodes) == XmlElement:
for MsgNodes in Nodes:
print ‘Message:’, MsgNodes.InnerXml
# Interact with an XML document.
CreateDocument()
DisplayDocument()
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The code begins by importing clr, which the application uses to add the required reference to System.Xml using the clr.AddReference() method. The code then imports the System.Xml classes.

The example relies on two functions to keep the code simple: CreateDocument(), which creates and saves the document to disk, and DisplayDocument(), which reads the document from disk and displays the content on screen. The example calls each of these functions in turn.

The CreateDocument() function begins by creating an XmlDocument object, Doc. As with any .NET application, Doc doesn’t contain anything when you create it. The first task is to add the XML declarations so that the result is a well-formed XML document using Doc.CreateXmlDeclaration(). Calling Doc.AppendChild() adds the declaration to the document.

Now it’s time to create some content. All XML documents have a root node, which is Root for this example. The code creates Root using Doc.CreateNode() with an XmlNodeType.Element type and ‘root‘ for a name. The example doesn’t work with XML namespaces, so the third argument is set to None.

The most efficient way to create an XML document from scratch is to add all the child nodes to Root before you add Root to the document. The code creates MsgNode using the same technique as for Root. It adds content to MsgNode using the MsgNode.InnerXml property and then adds the node to Root using Root.AppendChild(). The example provides two ‘Message‘ nodes.

At this point, the code adds Root to the document using Doc .AppendChild(). It then saves the document to disk using Doc.Save(). Figure 13-1 shows the typical output from this example when viewed in Notepad (you can use any text editor to view the output because the Doc .Save() method includes spaces and line feeds).

The XML document output looks much as you might expect.
Figure 13-1: The XML document output looks much as you might expect.

The DisplayDocument() function begins by creating a document, XMLDoc, using the XmlDocument class constructor. It then loads the previously created XML document using XMLDoc.Load(). At this point, XMLDoc contains everything the code created earlier and you can easily explore it using the IronPython console.

If you’ve worked with XML documents using C# or Visual Basic.NET, you know that these languages sometimes make it hard to get to the data you really want. IronPython makes things very easy. All you need is a for loop, as shown in the code. Simple if statements make it easy to locate nodes of a particular type, XmlElement in this case.

By the time the code reaches the second for loop, it’s working with the ‘Message‘ elements. The code simply prints the MsgNodes.InnerXml property value to the screen, as shown in Figure 13-2. By now you can see that IronPython makes it incredibly simple to work with XML documents using the .NET Framework approach.

The example outputs the message content in the XML document.
Figure 13-2: The example outputs the message content in the XML document.

Loading and Viewing the XMLUtil Module

The example in this section assumes that you’ve loaded the XMLUtil module from the IronPython Tutorial directory. The following steps show you how to load this module manually so you can see the content.

  1. Open the IronPython console.
  2. Type import sys and press Enter. This command imports the sys module so that you can add the required directory to it.
  3. Type sys.path.append(‘C:/Program Files/IronPython 2.6/Tutorial‘) and press Enter (make sure you change the path information to match the location of your IronPython installation). The XMLUtil.py module exists in the Tutorial directory. Using this module is fine for experimentation, but be sure you copy the XMLUtil.py module to another location for other uses.
  4. Type print sys.path and press Enter. You should see the new path added to the list.
  5. Type dir(XMLUtil) and press Enter. You see the list of methods available in XMLUtil (as shown in Figure 13-3), which includes the Walk() method used in the example.
The Walk() method makes viewing XML data easier.
Figure 13-3: The Walk() method makes viewing XML data easier.

Loading and Viewing the XMLUtil Module

As previously mentioned, the XMLUtil.py file isn’t anything so advanced that you couldn’t put it together yourself, but it’s an interesting module to work with and use. Listing 13-2 shows a short example of how you could use this module in an application.

Listin g 13-2: Walking an XML document using XMLUtil

[code]
# Add the path required to import xmlutil.
import sys
sys.path.append(‘C:/Program Files/IronPython 2.6/Tutorial’)
# Import xmlutil to access the Walk() function.
import xmlutil
# Import clr to add references.
import clr
# Add the required reference.
clr.AddReference(‘System.Xml’)
# Import the System.Xml classes.
from System.Xml import *
# Create a document.
XMLDoc = XmlDocument()
# Load the XML data.
XMLDoc.Load(‘Test.XML’)
# Walk the file contents.
print ‘Contents of Test.XML’
for Node in xmlutil.Walk(XMLDoc):
print ‘nName:’, Node.Name
print ‘Value:’, Node.Value
print ‘InnerXml’, Node.InnerXml
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The example begins by importing sys, appending the Tutorial folder path, and importing XMLUtil. The code then imports clr, adds a reference to System.Xml, and imports the System.Xml classes. There isn’t anything new about any of this code.

The example makes use of the Text.XML file created in the “Developing a Basic .NET XML Application” section of this chapter. It creates an XmlDocument object, XMLDoc, and loads Text.XML into it using the XMLDoc.Load() method. At this point, you have an XML document that you can walk (go from node-to-node and examine). The XMLUtil.Walk() method can walk any sort of XML document, so you should try it out with other files after you’ve worked with the example for a while.

The next step is to call on XMLUtil.Walk() to walk the XML document for you. The example shows output from the Name, Value, and InnerXml properties. However, you have access to all the properties provided for the various XML data types that the .NET Framework provides. Consequently, you can use XMLUtil.Walk() to display any information needed, or to manage that information. Just because the example displays properties doesn’t mean you have any limitation on how you interact with the output of XMLUtil.Walk(). Figure 13-4 shows the output of this example.

Screen shows the output of the Walk() method for Test.XML.
Figure 13-4: Screen shows the output of the Walk() method for Test.XML.

The XMLUtil.Walk() function is so important because it demonstrates a Python generator (described later in the section when you have the required background). Most languages don’t provide support for generators, so they require a little explanation. The issue at the center of this whole discussion is the variant list. You know that an application will need to process some number of items during run time, but you have no idea of how long this list is or whether the list will exist at all. A producer function is one that outputs values one at a time in response to a request. The producer keeps processing items until it runs out, so the length of the list is no longer a concern (even if the list contains no items at all). Most languages rely on a callback, an address to the requestor, to provide a place to send the producer output. The problem with using a callback is that the code must provide some means of retaining state information to remember previous values. In some cases, using callbacks leads to unnatural, convoluted coding techniques that are hard to write, harder to understand, and nearly impossible to update later.

Developers have a number of alternatives they can use. For example, the developer could simply use a very large list. However, lists require that the developer know what values should appear in the list during design time, and lists can consume large quantities of memory, making them a less than helpful solution in many cases. Another solution is to use an iterator to perform the task. Using an iterator makes it easier to get out of a loop when the processing is finished and eliminates the memory requirements. However, using an iterator shifts the burden of maintaining state information to the producer, complicating an already difficult programming task because the producer may not know anything about the caller. There are other solutions, as well, such as running the requestor and producer on separate threads so that each object can maintain state information without worrying about the potential corruption that occurs when running the code on a single thread. Unfortunately, multithreaded applications can run slowly and require a platform that fully supports multithreading, making your application less portable. In short, most languages don’t provide a good solution to the problem of working with data of variant length.

A generator creates a situation where the producer continuously outputs individual results as in a loop, maintaining its state locally. The requestor actually views the function as a type of iterator, even though the producer isn’t coded to provide an iterator. To accomplish this task, Python provides the yield statement shown in Figure 13-5. The yield statement returns an intermediate result from the producer to the requestor, while the producer continues to process a list of items.

The code in Figure 13-5 begins with the definition of a function named Walk(). This function accepts some kind of XML as input. The first yield statement sends the entire xml input back to the requestor (the example application shown in Listing 13-2). Consequently, you see #document as the Name and the entire XML document as the InnerXml.

The second call to Walk() moves past the first yield statement. Because the second item doesn’t meet the hasattr(xml, “Attributes“) requirement, the code moves onto the loop statement at the bottom of the code listing shown in Figure 13-5. The effect of this loop is to obtain the child elements of the entire document. So the second call to Walk() ends with yield c, which returns the XML declaration element. As a result, you see xml for the Name, version=“1.0“ encoding=“utf-8“ standalone=“yes“ for the Value, and nothing for the InnerXml. This second call ends processing of the XML declaration.

The XMLUtil.Walk() function is interesting because it provides a generator.
Figure 13-5: The XMLUtil.Walk() function is interesting because it provides a generator.

The third call to Walk() begins processing of the root node. It’s interesting to trace through the code in the debugger because you see the for loops in XMLUtil.Walk() used to trace through each element of the input xml as if it were using recursion or perhaps some type of iteration, but the fact is that the code merely combines the for loop with a yield statement to feed each partial result back to the requestor. Using the Python debugger is actually a bit more helpful in this case than using the Visual Studio debugger because the Visual Studio debugger won’t show you the value of xml, child, or c so that you can see the changing values. The example code for this book includes XMLUtilDemo2.py for the purpose of using the Python debugger. Follow these steps to load the debugger so you can trace through the example yourself.

  1. Open the IronPython console.
  2. Type import sys and press Enter. This command imports the sys module so that you can add the required directory to it.
  3. Type sys.path.append(‘C:/Program Files/IronPython 2.6/Tutorial‘) and press Enter (make sure you change the path information to match the location of your IronPython installation).
  4. Type import XMLUtil and press Enter to import the support file (important if you want to see how the generator works).
  5. Type import XMLUtilDemo2 and press Enter to import the source code file.
  6. Type import pdb and press Enter to import the debugger.
  7. Type pdb.run(‘XMLUtilDemo2.main()‘) to start the debugger. At this point, you can single step through the code to see how everything works.

Using the Python Modules

At one point, the Python modules were stable and straightforward to use, but later versions are less stable and, when it comes to IronPython, may be missing required elements completely. Consequently, you might see tutorials such as the one at http://www.boddie.org.uk/python/XML_intro.html and wonder why they don’t work. These tutorials are based on earlier versions of Python and don’t account for the missing CPython elements in IronPython. The following sections describe how to overcome these problems in your application when you use the Python approach to XML file management in IronPython.

Working with xml.dom.minidom

The xml.dom.minidom module is designed to help you work with XML using the DOM approach. However, this module is far from complete in IronPython, partly due to the CPython support required in standard Python. The actual document support is complete, so you won’t have a problem building, editing, and managing XML documents. It’s the write and read support that are lacking.

Fortunately, you can overcome write issues by using a different approach to outputting the document to disk (or other media). Standard Python development practice is to use the xml.dom.ext .PrettyPrint() method, which simply doesn’t exist in IronPython. You get around the problem by performing the task in two steps, rather than one, as shown in Listing 13-3.

The reading problem isn’t as easy to solve. Standard Python development practice is to use the xml .dom.minidom.parse() method. This method does exist in IronPython, but it outputs an error stating

[code]
ImportError: No module named pyexpat
[/code]

This module actually is missing. In order to fix this problem, you must download the pyexpat. py file from https://fepy.svn.sourceforge.net/svnroot/fepy/trunk/lib/. Place this file in your Program FilesIronPython 2.6Lib, not the Program FilesIronPython 2.6Lib xmldom folder as you might think. As shown in Listing 13-3, the standard Python techniques work just fine now.

Listin g 13-3: Managing XML documents using the Python approach

[code]
# Import the required XML support.
import xml.dom.minidom
def CreateDocument():
# Create an XML document.
Doc = xml.dom.minidom.Document()
# Create the root node.
Root = Doc.createElement(‘root’)
# Add the message nodes.
MsgNode = Doc.createElement(‘Message’)
Message = Doc.createTextNode(‘Hello’)
MsgNode.appendChild(Message)
Root.appendChild(MsgNode)
MsgNode = Doc.createElement(‘Message’)
Message = Doc.createTextNode(‘Goodbye’)
MsgNode.appendChild(Message)
Root.appendChild(MsgNode)
# Append the root node to the document.
Doc.appendChild(Root)
# Create the output document.
MyFile = open(‘Test2.XML’, ‘w’)
# Write the output.
MyFile.write(Doc.toprettyxml(encoding=’utf-8’))
# Close the document.
MyFile.close()
def DisplayDocument():
# Read the existing XML document.
XMLDoc = xml.dom.minidom.parse(‘Test2.XML’)
# Print the message node content.
for ThisChild in XMLDoc.getElementsByTagName(‘Message’):
print ‘Message:’, ThisChild.firstChild.toxml().strip(‘nt’)
CreateDocument()
DisplayDocument()
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The first thing you should notice is that the code for this example is much shorter than its .NET counterpart, even though the result is essentially the same. Despite the problems with the Python libraries, you can write concise code for manipulating XML using Python.

The code begins by importing the only module it needs, xml.dom.minidom. It then calls CreateDocument() and DisplayDocument() in turn, just as the .NET example does. In fact, the output from this example is precisely the same. You see the same output shown in Figure 13-2 when you run this example.

The CreateDocument() function begins by creating an XML document, Doc, using xml.dom .minidom.Document(). The XML document automatically contains the XML declaration, so unlike the .NET version of the code, you don’t need to add it manually. So the first processing task is to create the root node using Doc.createElement(‘root‘).

As with the .NET example, this example creates two MsgNode elements that contain different messages. The technique used is different from the .NET example. Instead of setting an InnerXml property, the code creates an actual text node using Doc.createTextNode(). However, the result is the same, as shown in Figure 13-6. The last step is to add Root to Doc using Doc.appendChild().

A big difference between IronPython and Python is how you write the XML to a file. As previously mentioned, you can’t use the xml.dom.ext.PrettyPrint() method. In this case, the code creates a file, MyFile, using open(). The arguments define the filename and the mode, where ‘w‘ signifies write. In order to write the text to a file, you use a two-step process. First, the code creates formatting XML by calling Doc.toprettyxml(). The function accepts an optional encoding argument, but there isn’t any way to define the resulting XML document as stand-alone using the standalone=“yes“ attribute (see Figure 13-1). Second, the code writes the data to the file buffer using MyFile.write().

The Python output is similar, but not precisely the same as the .NET output.
Figure 13-6: The Python output is similar, but not precisely the same as the .NET output.

[code]
Calling MyFile.write() doesn’t write the data to disk. In order to clear the file buffer, you must call MyFile.close(). Theoretically, IronPython will call MyFile.close() when the application ends, but there isn’t any guarantee of this behavior, so you must specifically call MyFile.close() to ensure there isn’t any data loss.
[/code]

The DisplayDocument() function comes next. Reading an XML document from disk and placing it in a variable is almost too easy when using IronPython. All you need to do is make a single call to xml.dom.minidom.parse(). That’s it! The document is immediately ready for use.

The second step is to display the same output shown in Figure 13-2. Again, all you need in IronPython is a simple for loop, rather than the somewhat lengthy .NET code. In this case, you ask IronPython to retrieve the nodes you want using XMLDoc.getElementsByTagName(). The output is a list that you can process one element at a time. The print statement calls on a complex-looking call sequence.

[code]
ThisChild.firstChild.toxml().strip(‘nt’)
[/code]

However, if you take this call sequence apart, it really isn’t all that hard to understand. Every iteration of the loop places one of the MsgNode elements in ThisChild. The first (and only) child of MsgNode is the Message text node, so you can retrieve it using the firstChild property. The firstChild property contains a DOM Text node object, so you convert it to XML using the toxml() method. Unfortunately, the resulting string contains control characters, so you remove them using the strip(‘nt‘) method. The result is a simple value output.

Working with xml.sax

It’s important to remember that SAX is an event-driven method of working with XML. An application looks at a small number of bits out of an entire document. Consequently, SAX can be a good method for processing larger documents that you can’t read into memory at one time. A SAX application normally relies on three constructs:

  • One or more sources as input
  • A parser (normally, only one is used)
  • One or more handlers to respond to input events

There are many different Python SAX modules. Each of these modules provides different implementations of the three constructions. The default SAX implementation provides just four handlers. These handlers are implemented as classes that you use to interact with the events generated by the input file.

  • ContentHandler: Provides the main SAX interface for handling document events. Most applications use this interface as a minimum because it provides the basic support required for any document. The example shows how to use this handler, which is provided as part of the xml.sax module.
  • DTDHandler: Manages all of the Document Type Definition (DTD) events.
  • EntityResolver: Resolves external entities such as files referenced by processing instructions.
  • ErrorHandler: Reports any errors or warnings that the parser encounters when it processes the XML. Provided as part of the xml.sax module.

Now that you have a little better idea of what to expect, it’s time to look at an actual example. Listing 13-4 shows a simple SAX implementation that includes all of the constructs you normally need. Of course, you can easily add to this example to make it do considerably more than it does now.

Listin g 13-4: Parsing an XML document using SAX

[code]
# Import the required module.
import xml.sax
# Create a handler based on the default ContentHandler class.
class MessageHandler(xml.sax.ContentHandler):
# Contains the message text.
Message = ‘’
# Determines when the content is a message.
IsMessage = False
# Check for the kind of element before processing it.
def startElement(self, name, attrs):
if name == ‘Message’:
self.IsMessage = True
self.Message = ‘’
else:
self.IsMessage = False
# If this is the right kind of element, display the message for it.
def endElement(self, name):
if name == ‘Message’:
print ‘Message:’, self.Message.strip(‘nt’)
# Add each of the characters of the message to the Message variable.
def characters(self, ch):
if self.IsMessage:
self.Message += ch
# Create a parser.
Parser = xml.sax.make_parser()
# Create a handler for the parser and tell the parser to use it.
Handler = MessageHandler()
Parser.setContentHandler(Handler)
# Open a source and parse it using the parser with the custom handler.
Parser.parse(open(‘Test2.XML’))
# Pause after the debug session.
raw_input(‘nPress any key to continue…’)
[/code]

The code begins by importing the required xml.sax module. You don’t need anything fancy to create a basic SAX handler. Remember that SAX processes the file one character at a time and generates events based on the characters that the parser sees. Consequently, the code may seem a little odd for someone who is used to working with complete elements, but SAX gives you fine control over the processing cycle, including locating errors within the file.

The centerpiece of this example is the MessageHandler class. This class includes a variable to hold the message (Message), an indicator of whether an element is a message (IsMessage), and the three methods described in the following list.

  • startElement(): The parser calls this method at the beginning of an element.
  • endElement(): The parser calls this method at the end of an element.
  • characters(): Every character read from the source generates a call to characters().

For this example, the startElement() method checks the element name. If the element is a ‘Message‘ element, then the code sets IsMessage to True and clears Message of any existing content. This is a preparatory step.

When the characters() method sees that IsMessage is True, it appends every character it receives to Message. Remember that these are individual characters, so you can’t assume much about the content except that the flow is from the beginning of the file to the end of it. In other words, you won’t receive characters out of order.

The endElement() checks the element name again. When the element name is ‘Message‘, the code prints the content of Message. Because Message contains all of the characters from the source, you must use strip(‘nt‘) to remove any control characters. The output from this example is the same as shown in Figure 13-2.

Now that you understand the handler, it’s time to see how you put it to work. The main part of the code begins by creating a parser, Parser, using xml.sax.make_parser(). Remember that the parser simply generates events based on the input characters it sees. The handler performs the actual interpretation of those characters.

The next step is to create an instance of MessageHandler named Handler. The code uses Parser .setContentHandler() to assign the handler to Parser. Otherwise, Parser won’t know which handler to use to process the XML characters.

In order to process the XML file, the code still requires a source — the third construct. The open(‘Test2.XML‘) call opens Test2.XML as a source and passes this source to Parser through the Parser.parse() method. It’s the call to the Parser.parse() method that actually begins the process of generating events.

Using .NET Data Types

When you work with .NET in IronPython, you have full access to every type that .NET supports. However, you don’t always create these types as you would in another language. For example, when working with C# or Visual Basic, you simply declare a variable of a certain type and then make an assignment to it. When working in IronPython, you must remember that making an assignment creates a Python type, not a .NET type. For example, let’s suppose you create a UInt32 variable and then make an assignment to it. Figure 7-10 shows the sequence of events that will occur.

As you can see, when you initially create the variable, IronPython recognizes it as a UInt32. In fact, even the type() function knows that this is a UInt32. When the code makes a simple assignment, however, notice that IronPython changes the type to a simple int. A check using type() shows that the data type has indeed changed. In order to change the value of a UInt32, you must make another assignment using the UInt32() constructor.

Figure 7-10: .NET and Python variables don’t mix very well in most cases.
Figure 7-10: .NET and Python variables don’t mix very well in most cases.

You can easily convert Python variables to their .NET counterparts in many cases. For example, you can use the following code to create a Python variable and then use it to create a .NET variable.

[code]

PVar = 5
NetVar = System.UInt32(PVar)

[/code]

In some cases, you’ll find that a direct conversion won’t work for any of a number of reasons. For example, you might find that converting Python numeric variables won’t work in some cases because .NET is expecting a string. In this case, you can try the clr.Convert() method. Here’s an example of using clr.Convert() to produce the same results as the previous example. (Remember that you must type import clr before you can use it.)

[code]

NetVar2 = clr.Convert(PVar, System.UInt32)

[/code]

The first argument contains the Python variable. The second argument contains the .NET data type you want as output. Of course, you must ensure you have imported the .NET assembly that contains the desired output type because the call will fail otherwise. In fact, any time clr.Convert() fails to convert the data, you get a TypeError.

Of course, before you can convert anything, you need to know the best type to use for the conversion. For example, you run the type() function against a Python variable to obtain the type information, but don’t know the equivalent .NET type. In this case, you can use clr.GetClrType() to obtain the closest .NET equivalent, as shown here.

[code]

# Produces System.Int32 as output.
clr.GetClrType(int)
# Produces System.String as output.
clr.GetClrType(str)

[/code]

The clr.GetPythonType() method obtains the Python equivalent of .NET data types. For example, if you type clr.GetPythonType(System.Int32), you get int as output. Of course, you’ll want to know how to convert your .NET data into a Python equivalent. In this case, you simply rely on the Python functions you use to create variables of specific types. For example, the following code converts a .NET UInt32 into a Python long.

[code]

MyVar = long(NetVar)

[/code]

Some .NET to Python conversions will result in data loss and Python won’t warn you about the problem. However, Python normally chooses a data type override that will work. For example, if you try to convert a number that won’t fit into an int, Python will automatically choose a long for you. Make sure that you always test a conversion before you assume it will work.

 

Understanding Tuples, Lists, and Arrays

As with many languages, IronPython has its own set of odd terminology. Any time you see a list of items separated by commas such as this:

[code]MyTuple = ‘Hello’, ‘Goodbye’, ‘Red’, ‘Green’[/code]

the precise terminology for the structure is a tuple. IronPython also supports lists natively. Listing 4-1 shows a simple application that creates a list and then displays its content. Figure 4-1 shows the output from this example.

Listin g 4-1: Creating and using a list

[code]# Define a list
MyList = [‘Hello’, ‘Goodbye’, ‘Red’, ‘Green’]
# Display each list element.
for TheString in MyList:
print TheString
# Pause after the debug session.
raw_input(‘Press any key to continue…’)[/code]

Lists produce the same output as a tuple given the same input.

The main difference between a tuple and a list is that you can change the content of a list, but you can’t change the content of a tuple. If you create a tuple and try to change one of its elements, you get an error message. Listing 4-2 shows what happens when you try to change a tuple and a list.

Listin g 4-2: Changing tuples and lists

[code]# Create the tuple
MyTuple = ‘Red’, ‘Green’, ‘Blue’, ‘Yellow’
# Create the list
MyList = [‘Red’, ‘Green’, ‘Blue’, ‘Yellow’]
# Attempt to change the tuple, which will result in an error.
try:
MyTuple[1] = ‘Orange’
except TypeError:
print ‘Couldn‘t change the tuple.n’
# Attempt to change the list.
try:
MyList[1] = ‘Orange’
except TypeError:
print ‘Couldn‘t change the list.n’
# Verify that the change worked.
print ‘Displaying the list content.’
for TheString in MyList:
print TheString
# Pause after the debug session.
raw_input(‘Press any key to continue…’)[/code]

The example attempts to change both a tuple and a list. However, when the example tries to change the tuple, it generates an exception, as shown in Figure 4-2. Notice how the code uses a try…except structure to catch the exception and display an error message onscreen. The except clause can stand alone, but it’s better to provide a particular kind of exception, which is TypeError in this case. The code does successfully modify the list and displays the results in Figure 4-2.

Lists are mutable; tuples aren’t.

Tuples and lists are more similar than different. For example, it’s possible to add elements to a tuple, just as you can to a list. In fact, except for the fact that tuples are immutable (not changeable) and lists are mutable (you can change them), both structures provide precisely the same features. Both of these structures are also roughly equivalent to arrays in other languages and you can call them arrays without provoking too many odd reactions — at least, not from normal people.

Of course, you’re wondering now why Python and IronPython support two array-like structures. Tuples come in handy for a few reasons. First, because tuples can’t change, IronPython can implement them more efficiently, which means that using tuples is faster. Second, tuples are less susceptible to outside influences such as viruses. Because you can’t change the content of a tuple, you also can’t fill it with things that the application can’t use. Third, tuples are marginally faster to type.

The object that IronPython uses as an array really isn’t an array in the common sense of the word. It’s more along the lines of a byte array for most programming languages. However, the IronPython array really doesn’t fit that description either. Rather, you tell IronPython what kind of sequence you want to store and then provide the storage values. Table 4-1 provides a list of the array element types.

IronPython Array Data Types

As you can see from the table, an array in IronPython doesn’t include any concept of a string. Strings in IronPython are considered a kind of sequence. To create an array of strings in IronPython, you’d need to create an array of character arrays. In short, the concept of an array is somewhat primitive in IronPython.

Arrays also differ from other array-like structures in IronPython in that you must provide a type as part of the array definition. Consequently, if you create a character array, you can’t suddenly decide to use it to store integer values. You must also import the array module to use IronPython arrays, because arrays aren’t part of the initial interpreter configuration. Listing 4-3 shows an example of an array in use.

Listin g 4-3: Working with an actual array in IronPython

[code]import array
# define a local version of array.
array = array.array
# Create a character array and assign it some values.
MyArray = array(‘c’, ‘Hello World’)
# Display each list element.
for TheCharacter in MyArray:
print TheCharacter
# Pause after the debug session.
raw_input(‘Press any key to continue…’)[/code]

The example begins by importing the array module and then assigning the array.array method to a local variable.

At this point, the code creates a character array. Now it may appear that this array contains a string, but what it really contains are individual characters. When you output the array, you see the individual characters, as shown in Figure 4-3. Consequently, most developers of other languages are going to view the IronPython array as a sort of byte array.

Arrays handle sequences of values.

After reading this section, you might find yourself a bit confused, especially if you’ve worked with Visual Basic.NET or C# in the past where arrays really are arrays. What you’ll want most often is a list in IronPython and you’ll likely use a tuple in some situations as well. When you think array for IronPython, think about these two kinds of objects. On the other hand, when you really do need to work with individual bits of data, then think about the IronPython array because it does work with those individual sequences.

Stringing Statements Together

A basic IronPython application begins as a series of statements, as a procedure. These statements perform some task at the command line or within a window, depending on which IronPython interpreter you use. This initial execution path is called the main function, even though there really isn’t a function — simply a set of statements.

A lot of people feel uncomfortable with the open feel of IronPython’s main function so they create something that looks a bit more like what they’re used to using, as shown in Listing 3-1.

Listin g 3-1: Creating an actual main() function

import sys
# Create a main() function that everyone can recognize.
def main(argv = None):
# Obtain the command line arguments.
if argv is None:
argv = sys.argv
# Display the command line arguments.
for ThisArg in argv:
print ThisArg
# Pause after the debug session.
raw_input(‘Press any key to continue…’)
# Call the main() function.
if __name__ == “__main__“:
main()

This code shows a few interesting structural features of IronPython, which is why it’s such a good example to start with. The code actually begins with a comparison of __name__ to “__main__“. It turns out that __name__ always contains the name of the current function. Using __name__ makes it possible for your application to detect its position in the execution loop.

Most programming languages provide an equivalent of the main() function described in Listing 3-1. Even C# and Visual Basic have a main()-type function that creates the initial form. Whether you actually use a main() function in your applications depends upon how you expect other developers to interact with your code. The examples in this book don’t rely on a main function for the sake of clarity, but you might want to consider including one when developers are used to working with main() as part of their coding experience. Of course, you can call main() anything you like to match the programming language that your developers normally use.

In this case, the application calls main(), the new main function. If you’ve spent any time writing C or C++ code, this version of main() will look a little familiar. While it lacks argc (the argument count), this main() does have an argv. However, the arguments can change between the time the code starts the application and begins processing main(), so you want to set argv to None (essentially, nothing at all in IronPython), and obtain the command line arguments from sys.argv.

At this point, the code uses a for loop to parse argv and displays its arguments on screen. At a minimum, you get the name of the application. The arguments you see depend on their order on the command line (or in Visual Studio). For example, if you specify -D after the filename, then you see it as part of the arguments. Figure 3-1 shows an example of what a series of arguments might look like when using a command line of NewMain.py -D -c These are arguments. The code ends by pausing so you can see the output when using the Visual Studio debugger.

Create a main() function and then process the command line arguments in it.Notice the levels of indentation in this example. The levels of indentation show the structure of the application. Unlike many application development languages, IronPython enforces indentation, with the result that you can see the application structure quite easily.

In IronPython, structure comes in a number of forms, all of which are accessible to you as the developer. Using structure properly makes your applications easier to understand. From the main (and any other) function, you can use the following structural elements:

  • Import external files
  • Call other functions
  • Use decision-making or loop structures
  • Interact with objects

 

Understanding the Dynamic Language Runtime

IronPython is a dynamic language, yet the Common Language Runtime (CLR) is a static environment. While you can build a compiler that makes it possible to use a dynamic language with CLR, as was done for IronPython 1.0, you’ll find that certain functionality is missing because CLR simply doesn’t understand dynamic languages. Consequently, Microsoft started the Dynamic Language Runtime (DLR) project (see http://dlr.codeplex.com/ for additional information). DLR sits on top of CLR and performs a level of interpretation that offers additional functionality for dynamic languages. By relying on DLR, IronPython gains access to the following support:

  • Shared dynamic type support
  • Shared hosted model
  • Quick dynamic code generation
  • Interaction with other dynamic languages
  • Improved interaction with static languages such as C# and Visual Basic.NET
  • Shared sandbox security model and browse integration

DLR is now part of the .NET Framework 4.0. Consequently, you can begin accessing these features immediately when using Visual Studio 2010 without having to install any additional support. Microsoft currently supports these languages using DLR:

  • IronPython
  • IronRuby
  • JavaScript (EcmaScript 3.0)
  • Visual Basic

Silverlight also provides support for DLR and there’s even a special SDK for Silverlight DLR. You can discover more about this SDK at http://silverlight.net/learn/dynamic-languages/.

  • Makes it easier to port dynamic languages to the .NET Framework
  • Lets you include dynamic features in static languages
  • Creates an environment where sharing of objects and libraries between languages is possible
  • Makes it possible to perform fast dynamic dispatch and invocation of objects

This section provides a good overview of DLR. You’ll discover additional details about DLR as the book progresses. However, if you’d like to delve into some of the architectural details of DLR, check out the article at http://msdn.microsoft.com/library/dd233052.aspx.

 

An Overview of IronPython

It surprises many developers to discover that computer languages are for humans, not for computers. A computer couldn’t care less about which language you use, because it’s all bits and bytes in the end anyway. Consequently, when you decide to learn another computer language, it really does pay to know what that language will do for you, the developer. Otherwise, there really isn’t a point in making the effort.

One phrase you often hear when discussing Python (and by extension, IronPython) is “batteries included.” Python has an immense standard library that addresses everything from working with ZIP files to interacting with the file system. You’ll discover the details of working with the Standard Library in Chapter 6. For now, it’s important to know that the Standard Library has a lot to offer and you may very well be able to build many of your applications without ever thinking about the .NET Framework.

As previously mentioned, IronPython is a .NET version of the Python language. For a .NET developer, using IronPython has the advantage of letting you create extensions using .NET. In addition, you have full access to the .NET Framework (see Chapter 7 for details). You can work with IronPython and other .NET languages that you already know, which means that you can use the right tool for every job. However, IronPython has a few differences from the CPython implementation that everyone else uses, which means that you can occasionally run into some odd compatibility problems when using IronPython. As with most things in life, advantages usually come with a few disadvantages.

There are some basic reasons that you want to use IronPython (or Python for that matter). The most important reason is that IronPython is a dynamic language, which means that it performs many tasks during run time, rather than compile time. Using a dynamic language means that your code has advantages of static languages, such as Visual Basic, in that it can more easily adapt to changing environmental conditions. (You’ll discover many other dynamic language advantages as the chapter progresses.) Unfortunately, you often pay for runtime flexibility with poorer performance — there’s  always a tradeoff between flexibility and performance.

Dynamic languages provide a number of benefits such as the ability to enter several statements and execute them immediately to obtain feedback. Using a dynamic language also provides easier refactoring and code modification because you don’t have to change static definitions throughout your code. It’s even possible to call functions you haven’t implemented yet and add an implementation later in the code when it’s needed. Don’t get the idea that dynamic languages are new. In fact, dynamic languages have been around for a very long time. Examples of other dynamic languages include the following:

  • LISP (List Processing)
  • Smalltalk
  • JavaScript
  • PHP
  • Ruby
  • ColdFusion
  • Lua
  • Cobra
  • Groovy

Developers also assign a number of advantages specifically to the Python language (and IronPython’s implementation of it). Whether these features truly are advantages to you depends on your perspective and experience. Many people do agree that Python provides these features:

  • Support for the Windows, Linux/Unix, and Mac OS X platforms
  • Managed support using both Java and .NET
  • Considerable object-oriented programming (OOP) functionality that is easy to understand and use
  • The capability to look within the code — .NET developers will know this as a strong form of reflection
  • An extensive array of standard libraries
  • Full library support using hierarchical packages (a concept that is already familiar to every .NET developer)
  • Robust third-party libraries that support just about every need
  • Support for writing both extensions and modules in both C and C++
  • Support for writing extensions and modules using third-party solutions for both .NET (IronPython) and Java (Jython)
  • Modular application development
  • Error handling through exceptions (another concept familiar to any .NET developer)
  • High-level dynamic data types
  • Ease of embedding within applications as a scripting solution
  • Procedural code that is relatively easy and natural to write
  • Ease of reading and a clear syntax

All these features translate into increased developer productivity, which is something that dynamic languages as a whole supposedly provide (productivity is one of these issues that is hard to nail down and even harder to prove unless you resort to metrics such as lines of code, which prove useless when comparing languages). In addition to the great features that Python provides, IronPython provides a few of its own. The following list provides a brief overview of these features:

  • Full access to the .NET Framework
  • Usability within Silverlight applications
  • Interactive console with full dynamic compilation provided as part of the product
  • Accessibility from within a browser (see http://ironpython.codeplex.com/Wiki/View
    .aspx?title=SilverlightInteractiveSession for details)
  • Full extensibility using the .NET Framework
  • Complete source code available (see http://ironpython.codeplex.com/SourceControl/ListDownloadableCommits.aspx for details)

One of the negatives of working with IronPython, versus Python (in the form of CPython), is that you lose support for multiple platforms — you only have direct access to Windows. You can get around this problem using Mono (http://www.mono-project.com/Main_Page), but it isn’t a straightforward fix and many developers will find it cumbersome.  Of course, there isn’t any way to get around the lack of Java support — you simply choose one virtual machine or the other. Appendix A lists more IronPython differences from CPython, most of which will cause compatibility and other issues for you.

Don’t get the idea that IronPython is going to restrict your use of familiar technologies. You can still create a Windows Forms application and interact with COM. It’s even possible to create command line (console) applications  and work with the Internet just as you always have. What IronPython provides is another way to view problems that you must address using your applications. As with most languages, what you’re getting is another tool that lets you create  solutions in the least amount of time and with the fewest bugs.