Advanced Examples


Several chapters in this book have covered the many pieces of the .NET Framework namespaces. For example, Chapter 27 describes many of the most useful tools provided by the System.Globalization and System.Resources namespaces. Similarly, Chapters 20 through 24 explain many of the most useful drawing tools provided by the System.Drawing namespace.

Other parts of the .NET Framework namespaces are quite specialized, and you may never need to use their capabilities. Many developers can use fairly standard installation techniques, so they will never need to use the System.Deployment classes to programmatically update ClickOnce deployments.

A few namespaces bear some special mention here, however. They don’t really fit into the rest of the book, but they are important enough that you may find a few examples helpful. The following sections give a few examples that use some of the more useful namespaces.

Regular Expressions

To most programmers, a regular expression is a series of symbols that represents a class of strings. A program can use regular expression tools to determine whether a string matches a regular expression or to extract pieces of a string that match an expression. For example, a program can use regular expressions to see if a string has the format of a valid phone number, Social Security number, ZIP code or other postal code, e-mail address, and so forth.

The following regular expression represents a 7- or 10-digit phone number in the United States:

  ^([2-9]{3}-)?[2-9]{3}-\d{4}$ 

The following table describes the pieces of this expression.

Open table as spreadsheet

Subexpression

Meaning

^

(The caret symbol.) Matches the beginning of the string.

[2-9]

Matches the characters 2 through 9 (United States phone numbers cannot begin with 0 or 1).

{3}

Repeat the previous group ([2-9]) exactly three times.

-

Match a dash.

([2-9]{3}-)?

The parentheses group the items inside. The ? makes the expression match the previous item exactly zero or one times. Thus the subexpression ([2-9]{3}-)? matches three digits 2 through 9 followed by a dash, zero, or one times.

[2-9]{3}-

Matches three digits 2 through 9 followed by a dash.

\d{4}

Matches any digit (0 through 9) exactly four times.

$

Matches the end of the string.

Taken together, this regular expression matches strings of the form NXX-XXXX and NXX-NXX-XXXX where N is a digit 2 through 9 and X is any digit.

A complete discussion of regular expressions is outside the scope of this book. Search the online help or the Microsoft web site to learn about the rules for building regular expressions. The web page msdn.microsoft .com/library/en-us/cpgenref/html/cpconregularexpressionslanguageelements.asp provides useful links to information about regular expression language elements. Another useful page is www.regexlib.com/RETester.aspx, which provides a regular expression tester and a library of useful regular expressions.

As you read the rest of this chapter and when visiting regular expression web sites, be aware that there are a couple different types of regular expression languages and that they won’t all work with every regular expression class.

The following code shows how a program can validate a text field against a regular expression. When the user changes the text in the txtTestExp control, its Changed event handler creates a new Regex object, passing its constructor the regular expression held in the txtRegExp text box. It then calls the Regex object’s IsMatch method to see if the text matches the regular expression. If the text matches, the program sets the txtTestExp control’s background color to white. If the text doesn’t match the expression, the program makes the control’s background yellow to indicate an error.

  Private Sub txtTestExp_TextChanged(ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles txtTestExp.TextChanged     Dim reg_exp As New Regex(txtRegExp.Text)     If reg_exp.IsMatch(txtTestExp.Text) Then         txtTestExp.BackColor = Color.White     Else         txtTestExp.BackColor = Color.Yellow     End If End Sub 

The following example uses a Regex object’s Matches method to retrieve a collection of Match objects that describe the places where a string matches a regular expression. It then loops through the collection, highlighting the matches in a Rich Text Box.

  Private Sub btnGo_Click(ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles btnGo.Click     Dim reg_exp As New Regex(txtPattern.Text)     Dim matches As MatchCollection     matches = reg_exp.Matches(txtTestString.Text)     rchResults.Text = txtTestString.Text     For Each a_match As Match In matches         rchResults.Select(a_match.Index, a_match.Length)         rchResults.SelectionBackColor = Color.Black         rchResults.SelectionColor = Color.White     Next a_match End Sub 

Figure 32-1 shows the result. In this example, the regular expression is (in|or), so the program finds matches where the string contains in or or.

image from book
Figure 32-1: The Regex object’s Matches method returns a collection describing the places where a string matches a regular expression.

The following code uses a Regex object to make replacements in a string. It creates a Regex object, passing its constructor the IgnoreCase option to tell the object to ignore capitalization in the string. It then calls the object’s Replace, passing it the string to modify and the pattern that it should use to make the replacement.

  Dim reg_exp As New Regex(txtPattern.Text, RegexOptions.IgnoreCase) lblResult.Text = reg_exp.Replace(Me.txtTestString.Text, txtReplacementPattern.Text) 

Figure 32-2 shows an example that uses the regular expression [aeiouAEIOU] to match vowels. It then replaces the vowels with periods.

image from book
Figure 32-2: The Regex object can replace matching strings with a new string.

The Regex class can perform much more complicated matches. For example, you can use it to find fields within each line in a multiline string and then build a string containing the fields reformatted or reordered. See the online help for more details.

XML

Extensible Markup Language (XML) is a simple language for storing data in a text format. It encloses data within tags that delimit the data. You can give those tags any names that you want. For example, the following text shows an XML file containing three Employee records.

  <?xml version="1.0" encoding="utf-8" standalone="yes"?> <Employees>     <Employee>         <FirstName>Albert</FirstName>         <LastName>Anders</LastName>         <EmployeeId>11111</EmployeeId>     </Employee>     <Employee>         <FirstName>Betty</FirstName>         <LastName>Beach</LastName>         <EmployeeId>22222</EmployeeId>     </Employee>     <Employee>         <FirstName>Chuck</FirstName>         <LastName>Cinder</LastName>         <EmployeeId>33333</EmployeeId>     </Employee> </Employees> 

The System.Xml namespace contains classes for reading, writing, and manipulating XML data. Different classes let you process XML files in different ways. For example, the XmlDocument class lets you represent an XML document completely within memory. Using this class, you can perform complex manipulations of an XML file, adding and removing elements, searching for elements with particular attributes, and merging XML documents.

On the other hand, the XmlTextReader and XmlTextWriter classes let you read and write XML data in a fast, forward-only fashion. These classes can be more efficient than XmlDocument when you must quickly build or scan very large XML files that might not easily fit in memory all at once.

The following code shows one way a program can use the System.Xml namespace to generate the previous employee XML file. Note that Visual Basic programs do not initially contain a reference to the System.Xml namespace by default. To use the namespace, you must select the Project menu’s Add Reference command and select the System.Xml.dll reference.

The code starts by creating an XmlTextWriter object. This class provides methods for efficiently writing items into of an XML file. The code sets the writer’s Formatting and Indentation properties to make the object indent the resulting XML file nicely. If you don’t set these properties, the file comes out all run together on a single line. That’s fine for programs that process XML files but makes the file hard for humans to read.

The program calls the WriteStartDocument method to write the file’s XML declaration, including the XML version, encoding, and standalone attribute. It calls WriteStartElement to write the starting <Employees> XML tag and then calls subroutine MakeEmployee to generate three Employee items. It calls the WriteEndElement method to write the </Employees> end tag, and calls WriteEndDocument to end the document. The program then closes the XmlTextWriter to close the file.

Subroutine MakeEmployee writes a starting <Employee> element into the file. It then uses the Write?StartElement, WriteString, and WriteEndElement methods to add the employee’s FirstName, LastName, and EmployeeId elements to the document. The routine finishes by calling WriteEnd?Element to create the </Employee> end tag.

  Private Sub btnGo_Click(ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles btnGo.Click     Dim xml_text_writer As _         New XmlTextWriter("employees.xml", System.Text.Encoding.UTF8)     ' Use indentation to make the result look nice.     xml_text_writer.Formatting = Formatting.Indented     xml_text_writer.Indentation = 4     ' Write the XML declaration.     xml_text_writer.WriteStartDocument(True)     ' Start the Employees node.     xml_text_writer.WriteStartElement("Employees")     ' Write some Employee elements.     MakeEmployee(xml_text_writer, "Albert", "Anders", 11111)     MakeEmployee(xml_text_writer, "Betty", "Beach", 22222)     MakeEmployee(xml_text_writer, "Chuck", "Cinder", 33333)     ' End the Employees node.     xml_text_writer.WriteEndElement()     ' End the document.     xml_text_writer.WriteEndDocument()     ' Close the XmlTextWriter.     xml_text_writer.Close() End Sub ' Add an Employee node to the document. Private Sub MakeEmployee(ByVal xml_text_writer As XmlTextWriter, _  ByVal first_name As String, ByVal last_name As String, ByVal emp_id As Integer)     ' Start the Employee element.     xml_text_writer.WriteStartElement("Employee")     ' Write the FirstName.     xml_text_writer.WriteStartElement("FirstName")     xml_text_writer.WriteString(first_name)     xml_text_writer.WriteEndElement()     ' Write the LastName.     xml_text_writer.WriteStartElement("LastName")     xml_text_writer.WriteString(last_name)     xml_text_writer.WriteEndElement()     ' Write the EmployeeId.     xml_text_writer.WriteStartElement("EmployeeId")     xml_text_writer.WriteString(emp_id.ToString)     xml_text_writer.WriteEndElement()     ' Close the Employee element.     xml_text_writer.WriteEndElement() End Sub 

Other classes within the System.Xml namespace let you load and manipulate XML data in memory, read XML data in a fast forward–only manner, and search XML documents for elements matching certain criteria. XML is quickly becoming a common language that allows unrelated applications to communicate with each other. Using the XML tools provided by the System.Xml namespace, your application can read, write, and manipulate XML data, too.

Cryptography

The System.Security namespace includes objects for performing various cryptographic operations. The four main scenarios supported by these objects include the following:

  • Secret-key encryption - This technique encrypts data so you cannot read it unless you know the secret key. This is also called symmetric cryptography.

  • Public-key encryption - This technique encrypts data using a public key that everyone knows. Only the person with a secret private key can read the data. This is useful if you want anyone to be able to send messages to you, but you should be able to read them. This is also called asymmetric cryptography.

  • Signing - This technique signs data to guarantee that it really came from a specific party. For example, you can sign an executable program to prove that it’s really your program and not a virus substituted by some hacker.

  • Hashing - This technique maps a piece of data such as a document into a hash value in a way that guarantees that two different documents are unlikely to hash to the same value. If you know a document’s hash value, you can later hash the document again and compare the values. If the calculated value matches the previously known value, it is very unlikely that anyone has modified the file since the first hashing.

The example described later in this section encrypts and decrypts files. The basic idea is to create a Crypto?Stream object attached to a file stream opened for writing. As you write data into the CryptoStream, it encrypts or decrypts the data and sends the result to the output file stream.

Although the classes provided by Visual Studio 2005 are easier to use than the routines contained in the underlying cryptography API, the details are still somewhat involved. To encrypt and decrypt files, you must first select an encryption algorithm. You need to pick a key size and block size that are supported by the corresponding encryption provider.

To use an encryption provider, you must pass it a key and initialization vector (IV). Each of these is a series of bytes that the encryption provider uses to initialize its internal state before it encrypts or decrypts files.

If you want to control the encryption with a textual password, you must convert it into a series of bytes that you can use for the key and initialization vector. You can do that with a PasswordDeriveBytes object, but that object also requires the name of the hashing algorithm that it should use to convert the password into the key and initialization vector bytes.

Working through the following example should make this less confusing. This example uses a triple DES encryption algorithm to encrypt and decrypt files. It uses the SHA384 hashing algorithm to convert a text password into key and initialization vector bytes.

Subroutine CryptFile encrypts or decrypts a file, saving the result in a new file. It takes as parameters a password string, the names of the input and output files, and a Boolean indicating whether it should perform encryption or decryption.

The routine starts by opening the input and output files. It then makes a TripleDESCryptoService?Provider object to provide the encryption and decryption algorithms using triple DES.

Next, the program must find a key length that is supported by the encryption service provider. This code counts backward from 1,024 until it finds a value that the provider’s ValidKeySize method approves. On my computer, the largest key size the provider supports is 128 bits.

The triple DES algorithm encrypts data in blocks. The program uses the provider’s BlockSize property to see how big those blocks are. The program must generate an initialization vector that has this same size.

Next, the program calls the MakeKeyAndIV subroutine. This routine, which is described shortly, converts a text password into arrays of bytes for use as the key and initialization vector. The salt array contains a series of random bytes to make guessing the password harder for an attacker. The Rfc2898DeriveBytes class used by subroutine MakeKeyAndIV can generate a random salt for the program, but this example uses a salt array written into the code to make reading the code easier.

After obtaining the key and initialization vector, the program makes an object to perform the encryption or decryption transformation, depending on whether the subroutine’s encrypt parameter is True or False.

The program uses the encryption provider’s CreateEncryptor or CreateDecryptor method, passing it the key and initialization vector.

Now, the program makes a CryptoStream object attached to its output file stream. It passes the object’s constructor and output file stream, the cryptographic transformation object, and a flag indicating that the program will write to the stream.

At this point, the program has set the stage and can finally begin processing data. It allocates a buffer to hold data and then enters a Do loop. In the loop, it reads data from the input file into the buffer. If it read no bytes, the program has reached the end of the input file, so it exits the loop. If it read some bytes, the program writes them into the CryptoStream. The CryptoStream uses its cryptographic transformation object to encrypt or decrypt the data and sends it on to its attached output file stream.

When it has finished processing the input file, the subroutine closes its streams.

Subroutine MakeKeyAndIV uses a text password to generate arrays of bytes to use as a key and initialization vector. It begins by creating an Rfc2898DeriveBytes object, passing its constructor the password text, the salt, and the number of iterations the object should use to generate the random bytes. The salt can be any array of bytes as long as it’s the same when encrypting and decrypting the file. The salt makes it harder for an attacker to build a dictionary of key and initialization vector values for every possible password string.

Having built the PasswordDeriveBytes object, the subroutine calls its GetBytes method to get the proper number of bytes for the key and initialization vector.

  ' Encrypt or decrypt a file, saving the results  ' in another file. Private Sub CryptFile(ByVal password As String, ByVal in_file As String, _  ByVal out_file As String, ByVal encrypt As Boolean)     ' Create input and output file streams.     Dim in_stream As New FileStream(in_file, FileMode.Open, FileAccess.Read)     Dim out_stream As New FileStream(out_file, FileMode.Create, FileAccess.Write)     ' Make a triple DES service provider.     Dim des_provider As New TripleDESCryptoServiceProvider()     ' Find a valid key size for this provider.     Dim key_size_bits As Integer = 0     For i As Integer = 1024 To 1 Step -1         If des_provider.ValidKeySize(i) Then             key_size_bits = i             Exit For         End If     Next i     Debug.Assert(key_size_bits > 0)     ' Get the block size for this provider.     Dim block_size_bits As Integer = des_provider.BlockSize     ' Generate the key and initialization vector.     Dim key As Byte() = Nothing     Dim iv As Byte() = Nothing     Dim salt As Byte() = {&H0, &H0, &H1, &H2, &H3, &H4, &H5, &H6, _         &HF1, &HF0, &HEE, &H21, &H22, &H45}     MakeKeyAndIV(password, salt, key_size_bits, block_size_bits, key, iv)     ' Make the encryptor or decryptor.     Dim crypto_transform As ICryptoTransform     If encrypt Then         crypto_transform = des_provider.CreateEncryptor(key, iv)     Else         crypto_transform = des_provider.CreateDecryptor(key, iv)     End If     ' Attach a crypto stream to the output stream.     Dim crypto_stream As New CryptoStream(out_stream, crypto_transform, _         CryptoStreamMode.Write)     ' Encrypt or decrypt the file.     Const BLOCK_SIZE As Integer = 1024     Dim buffer(BLOCK_SIZE) As Byte     Dim bytes_read As Integer     Do         ' Read some bytes.         bytes_read = in_stream.Read(buffer, 0, BLOCK_SIZE)         If bytes_read = 0 Then Exit Do         ' Write the bytes into the CryptoStream.         crypto_stream.Write(buffer, 0, bytes_read)     Loop     ' Close the streams.     crypto_stream.Close()     in_stream.Close()     out_stream.Close() End Sub ' Use the password to generate key bytes. Private Sub MakeKeyAndIV(ByVal password As String, ByVal salt() As Byte, _  ByVal key_size_bits As Integer, ByVal block_size_bits As Integer, _  ByRef key As Byte(), ByRef iv As Byte())     Dim derive_bytes As New Rfc2898DeriveBytes( _         txtPassword.Text, salt, 1000)     key = derive_bytes.GetBytes(key_size_bits \ 8)     iv = derive_bytes.GetBytes(block_size_bits \ 8 End Sub 

The following code uses the CryptFile subroutine to encrypt and then decrypt a file. First it calls CryptFile, passing it a password, input and output file names, and the value True to indicate that the routine should encrypt the file. Next, the code calls CryptFile again, this time to decrypt the encrypted file.

  ' Encrypt the file. CryptFile(txtPassword.Text, txtPlaintextFile.Text, txtCyphertextFile.Text, True) ' Decrypt the file. CryptFile(txtPassword.Text, txtCyphertextFile.Text, txtDecypheredFile.Text, False) 

Figure 32-3 shows the DesFile example program that is available for download on the book’s web site. Enter some text and a password, and then click the left > button to encrypt the file. Click the right > button to decrypt the encrypted file.

image from book
Figure 32-3: Program DesFile encrypts and decrypts files.

If you change the password by even a single character, the decryption returns gibberish. Figure 32-4 shows the program trying to decrypt a message incorrectly. To encrypt the file, the program used the same message and password shown in Figure 32-3. When decrypting the file, I added an s to the end of the password. The result is completely unreadable.

image from book
Figure 32-4: Changing even a single character in the password makes decryption produce an unintelligible result.

See the online help for information about the other main cryptographic operations (secret-key encryption, public-key encryption, signing, and hashing). Other books may also provide additional insights into cryptography. For example, see Applied Cryptography: Protocols, Algorithms, and Source Code in C, Second Edition by Bruce Schneier (Wiley, 1996).

Reflection

Reflection lets a program learn about itself and other programming entities. It includes objects that tell the program about assemblies, modules, and types.

For example, the following code examines the program’s form and displays a list of its properties, their types, and their values. The program starts by formatting the ListView control named lvwProperties. Next, the program defines an array of PropertyInfo objects named properties_info. It uses GetType to get type information about the Form1 class and then uses the type’s GetProperties method to get information about the properties. The program then loops through the PropertyInfo objects.

If the object’s GetIndexParameters array contains no entries, the property is not an array. In that case, the program uses the PropertyInfo object’s GetValue method to get the property’s value. The code then displays the property’s name, type, and value.

If the PropertyInfo object’s GetIndexParameters array contains entries, the property is an array. In that case, the program displays the property’s name, type, and the string <array>.

The subroutine finishes by sizing ListView control’s columns and then making the form fit the columns.

The helper subroutine ListViewMakeRow adds a row of values to the ListView control. It adds a new item to the control and then adds subitems to the item. The item appears in the control’s first column and the subitems appear in the other columns.

  Private Sub Form1_Load(ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles MyBase.Load     ' Make column headers.     lvwProperties.View = View.Details     lvwProperties.Columns.Clear()     lvwProperties.Columns.Add("Property", 10, _         HorizontalAlignment.Left)     lvwProperties.Columns.Add("Type", 10, _         HorizontalAlignment.Left)     lvwProperties.Columns.Add("Value", 10, _         HorizontalAlignment.Left)     ' List the properties.     Dim property_value As Object     Dim properties_info As PropertyInfo() = _         GetType(Form1).GetProperties()     lvwProperties.Items.Clear()     For i As Integer = 0 To properties_info.Length - 1         With properties_info(i)             If .GetIndexParameters().Length = 0 Then                 property_value = .GetValue(Me, Nothing)                 If property_value Is Nothing Then                     ListViewMakeRow(lvwProperties, _                         .Name, _                         .PropertyType.ToString, _                         "<Nothing>")                 Else                     ListViewMakeRow(lvwProperties, _                         .Name, _                         .PropertyType.ToString, _                         property_value.ToString)                 End If             Else                 ListViewMakeRow(lvwProperties, _                     .Name, _                     .PropertyType.ToString, _                     "<array>")             End If         End With     Next i     ' Size the columns to fit the data.     lvwProperties.Columns(0).Width = -2     lvwProperties.Columns(1).Width = -2     lvwProperties.Columns(2).Width = -2 End Sub ' Make a ListView row. Private Sub ListViewMakeRow(ByVal lvw As ListView, _  ByVal item_title As String, ByVal ParamArray subitem_titles() As String)     ' Make the item.     Dim new_item As ListViewItem = lvw.Items.Add(item_title)     ' Make the subitems.     For i As Integer = subitem_titles.GetLowerBound(0) To _         subitem_titles.GetUpperBound(0)         new_item.SubItems.Add(subitem_titles(i))     Next i End Sub 

Figure 32-5 shows this code in action. It displays its form’s properties, their types, and their values. If you look closely, you can see that the form’s Text property shown in the list matches the value in its title bar.

image from book
Figure 32-5: This program uses reflection to learn about its form’s properties.

Using reflection to learn about your application is interesting, but not always necessary. After all, if you build an object, you probably know what its properties are.

However, reflection can also tell you a lot about other applications. The following example shows how a program can learn about another application. This program reads the assembly information in a file and lists the embedded resources that it contains. The user can then select a resource to view it.

The user enters the name of the assembly to load the txtFile text box. For example, this can be the name of a .NET executable.

When the user clicks the List button, the btnList_Click event handler uses the Assembly class’s shared LoadFile method to load an Assembly object representing the indicated assembly. It then loops through the array of strings returned by the Assembly object’s GetManifestResourceNames method, adding the resource file names to the ListBox named lstResourceFiles.

When the user selects a resource file from the list, the lstResourceFiles_SelectedIndexChanged event handler displays a list of resources in the file. It uses the Assembly object’s GetManifestResource?Stream method to get a stream for the resources. It uses the stream to make a ResourceReader object and then enumerates the items found by the ResourceReader. It saves each object in a new Resource?Info object (this class is described shortly) and adds it to the lstResources list.

When the user selects a resource from lstResources, its SelectedIndexChanged event handler retrieves the selected ResourceInfo object, converts its Value property into an appropriate data type, and displays the result. The ResourceInfo class stores Key and Value information for a resource enumerated by a ResourceReader object’s enumerator. It provides an overloaded ToString that the lstResources list uses to represent the items.

  Private m_TargetAssembly As Assembly ' List the target assembly's resources. Private Sub btnList_Click(ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles btnList.Click     ' Get the target assembly.     m_TargetAssembly = Assembly.LoadFile(txtFile.Text)     ' List the target's manifest resource names.     lstResourceFiles.Items.Clear()     For Each str As String In m_TargetAssembly.GetManifestResourceNames()         lstResourceFiles.Items.Add(str)     Next str End Sub ' List this file's resources. Private Sub lstResourceFiles_SelectedIndexChanged(ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles lstResourceFiles.SelectedIndexChanged     lstResources.Items.Clear()     Dim resource_reader As ResourceReader     resource_reader = New ResourceReader( _         m_TargetAssembly.GetManifestResourceStream(lstResourceFiles.Text))     Dim dict_enumerator As IDictionaryEnumerator = resource_reader.GetEnumerator()     While dict_enumerator.MoveNext()         lstResources.Items.Add(New ResourceInfo( _             dict_enumerator.Key, _             dict_enumerator.Value))     End While     resource_reader.Close() End Sub ' Display the selected resource. Private Sub lstResources_SelectedIndexChanged(ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles lstResources.SelectedIndexChanged     lblString.Text = ""     picImage.Image = Nothing     Dim resource_info As ResourceInfo = _         DirectCast(lstResources.SelectedItem, ResourceInfo)     Select Case resource_info.Value.GetType.Name         Case "Bitmap"             picImage.Image = CType(resource_info.Value, Bitmap)             lblString.Text = ""         Case "String"             picImage.Image = Nothing             lblString.Text = CType(resource_info.Value, String)     End Select End Sub Private Class ResourceInfo     Public Key As Object     Public Value As Object     Public Sub New(ByVal new_key As Object, ByVal new_value As Object)         Key = new_key         Value = new_value     End Sub     Public Overrides Function ToString() As String         Return Key.ToString & " (" & Value.ToString & ")"     End Function End Class 

Figure 32-6 shows the result.

image from book
Figure 32-6: This application uses reflection to display another application’s resources.

This is admittedly a fairly complex example, but it performs the fairly remarkable feat of pulling resources out of another compile application.

Reflection can provide a lot of information about applications, modules, types, methods, properties, events, parameters, and so forth. It lets a program discover and invoke methods at runtime and build types at runtime.

An application also uses reflection indirectly when it performs such actions as serialization, which uses reflection to learn how to serialize and deserialize objects.

Reflection is a very advanced and somewhat arcane topic, but it is extremely powerful.

Tip 

Note that the behavior of some of these reflection routines changed fairly late in the Visual Basic 2005 beta process, so it’s possible they will change again before the final release. Check the book’s web site at www.vb-helper.com/vb_prog_ref.htm for updates.

Direct3D

DirectX is a set of APIs that provide tools for high-performance multimedia applications. These APIs include tools for working with audio, music, input devices (such as joysticks and force-feedback devices), and other multimedia objects.

Direct3D is the three-dimensional drawing library provided by DirectX. Figure 32-7 shows a simple Direct3D application that displays an octahedron rotating around a vertical axis. Each of the octahedron’s faces has a different color so that it’s easy to see it rotating.

image from book
Figure 32-7: This Direct3D application displays a rotating octahedron.

Before an application can use the DirectX and Direct3D libraries, you must perform a few preliminary tasks. First, go to the Microsoft web site at www.microsoft.com/downloads and download and install the latest DirectX development kit. As of this writing, the most recent version is a version of DirectX 9 built in October 2006 that is roughly 500 MB in size.

Next, start a new application. Use the Project menu’s Add Reference command to add references to the Microsoft.DirectX and Microsoft.DirectX.Direct3D libraries.

Also note that, while I was testing this program on my new 64-bit computer, the program threw a Bad??ImageFormatException with the description “is not a valid Win32 application.” To work around this issue, open Solution Explorer and double-click the application’s My Project entry. Select the Compile tab and click the Advanced Compile Options button to display the dialog box shown in Figure 32-8. In the Target CPU drop-down list box at the bottom, select x86 and click OK. Now the application will use the 32-bit Jet drivers instead of looking for 64-bit versions.

image from book
Figure 32-8: When using Direct3D on 64-bit computers, set the target CPU to x86.

The following code shows the program’s Main subroutine. The routine creates a new OctahedronForm object, calls its InitializeGraphics method to get it ready to display graphics, and displays the form. It then enters an event loop. As long as the form is valid, the program calls its Render method to draw the octahedron, and then calls Application.DoEvents to process any events that have occurred (such as the user closing the form).

  ' Build the form and display the graphics. Sub Main()     System.Windows.Forms.Application.EnableVisualStyles()     ' Make the form.     Dim octahedron_form As New OctahedronForm()     Using octahedron_form         ' Initialize graphics.         If Not octahedron_form.InitializeGraphics() Then             MessageBox.Show("Error initializing Direct3D.", "Direct3D Error", _                 MessageBoxButtons.OK, MessageBoxIcon.Exclamation)             Return         End If         ' Display the form.         octahedron_form.Show()         ' While the form is valid, render the scene.         While octahedron_form.Created             octahedron_form.Render()             Application.DoEvents()         End While     End Using End Sub 

The following code shows how the OctahedronForm works. The class begins by defining private constants giving the number of triangles it displays (8 triangles in an octahedron) and the number of vertices it needs to define the triangles (3 * 8 = 24). It also defines private variables to hold references to a Device, a VertexBuffer, and an array of PresentParameters items.

The form’s InitializeGraphics subroutine prepares the Direct3D subsystem for use. This routine sets some presentation parameters and creates a new Device object to represent the device on which the program will draw. The Windowed parameter means the device will sit within the form’s window. The SoftwareVertexProcessing flag in the device’s constructor means Direct3D should use software to process the data, rather than relying on special graphics hardware. Graphics hardware would produce a faster program, but the code would need to perform extensive checks to verify that the system actually had the necessary hardware.

The subroutine adds an event handler for the device’s DeviceReset event. This event occurs when the device is reset, which happens if the form’s window is recreated when it is maximized or restored. The subroutine calls subroutine CreateVertexBuffer to create the vertex buffer. It finishes by calling OnResetDevice to prepare the device for use.

Subroutine CreateVertexBuffer creates a buffer to hold data defining the octahedron’s vertices. It passes the VertexBuffer class’s constructor parameters, giving the type of vertex (PositionColored means each entry gives the vertex’s position and color), the number of vertices, the device, a usage flag (0 means no flags), the vertex format (position colored), and the memory pool that should contain the buffer memory.

The routine then adds an event handler for the buffer object’s Created event. This event occurs when the buffer is recreated. That happens when the device is reset. Finally, the routine calls OnCreateVertexBuffer to initialize the vertex data.

When the device is reset, subroutine OnResetDevice prepares the device for use. In this example, the event handler disables Direct3D lighting. Lighting would allow the program to illuminate the scene with various light sources, but this example explicitly gives the colors of all of its vertices, so light sources are unnecessary.

When the vertex buffer is created, subroutine OnCreateVertexBuffer initializes it. The routine calls the buffer’s Lock method to lock the vertex memory and retrieve an array containing a usable copy of the vertex data. The routine calls subroutine MakeTriangle eight times to initialize the vertices for the octahedron’s eight faces. Then it calls the buffer’s Unlock method to unlock the data.

Subroutine MakeTriangle simply initializes a triangle’s vertices.

Subroutine Render draws the scene onto the graphics device. First, it clears the device’s background. It calls the device’s BeginScene method to tell Direct3D that it is about to draw objects. Next, the code calls subroutine SetupMatrices to build transformation matrixes that define how the scene should be drawn. It sets the device’s data source to the vertex buffer, tells the device the buffer’s format, and calls subroutine DrawPrimatives to draw the triangles stored in the buffer. The routine finishes by calling the device’s EndScene method to tell Direct3D that it is finished drawing and by calling its Present method to display the result.

Subroutine SetupMatrix defines the matrices that Direct3D uses to map three-dimensional objects onto the two-dimensional screen. The routine defines three matrices. The world matrix tells Direct3D how to transform the objects’ coordinates before drawing. This could be an identity matrix (one that leaves the data unchanged) if you don’t need to transform the data. In this example, this matrix rotates the points around the vertical axis. The code calculates the angle of rotation, so the data rotates around the axis completely every two seconds.

The second view matrix tells Direct3D how the program should view the data. You can think of this as the camera position. You can define this matrix by specifying the location where the viewer is standing, the direction the viewer is looking, and an up direction. For example, imagine a camera hanging from a boom, pointed toward some center of interest. The up direction tells which way the camera is rotated: right side up, upside down, sideways, and so forth. In this example, the camera is at position (0, 3, –10) looking toward the origin (0, 0, 0), and the up direction is in the normal up direction. In the Direct3D coordinate system, that’s the direction of the positive Y-axis.

The third matrix is a projection matrix that tells Direct3D how to project the three-dimensional data into two-dimensions. This example uses a perspective projection.

  Imports Microsoft.DirectX Imports Microsoft.DirectX.Direct3D Public Class OctahedronForm     ' The graphics device. Private m_Device As Device = Nothing ' Data variables. Private Const NUM_TRIANGLES As Integer = 8 Private Const NUM_POINTS As Integer = 3 * NUM_TRIANGLES Private m_VertexBuffer As VertexBuffer = Nothing Private m_PresentParams As New PresentParameters() ' Initialize Direct3D. Public Function InitializeGraphics() As Boolean     Try         ' Initialize presentation parameters.         m_PresentParams.Windowed = True         m_PresentParams.SwapEffect = SwapEffect.Discard         ' Make the device.         m_Device = New Device(0, DeviceType.Hardware, _             Me, CreateFlags.SoftwareVertexProcessing, _             m_PresentParams)         ' Add an event handler for DeviceReset.         ' This event is called when the device is reset         ' (you probably guessed that). That happens when         ' the form is recreated when it is maximized         ' or restored.         AddHandler m_Device.DeviceReset, _             AddressOf Me.OnResetDevice         ' Call CreateVertexBuffer and OnResetDevice.         Me.CreateVertexBuffer()         Me.OnResetDevice(m_Device, Nothing)         Return True     Catch e As DirectXException         Return False     End Try End Function ' Create a vertex buffer for the device. Public Sub CreateVertexBuffer()     ' Create a vertex buffer.     Dim dev As Device = CType(sender, Device)     m_VertexBuffer = New VertexBuffer( _         GetType(CustomVertex.PositionColored), _         NUM_POINTS, m_Device, 0, CustomVertex.PositionColored.Format, _         Pool.Default)     ' Add an event handler for m_VertexBuffer.Created.     ' This event occurs when the device is recreated     ' and the vertex buffer is reinitialized.     AddHandler m_VertexBuffer.Created, _         AddressOf Me.OnCreateVertexBuffer     ' Call m_VertexBuffer.Created to initialize      ' the vertex data.     Me.OnCreateVertexBuffer(m_VertexBuffer, Nothing) End Sub ' Reset the device. Public Sub OnResetDevice(ByVal sender As Object, ByVal e As EventArgs)     ' Turn off D3D lighting because      ' we set the vertex colors explicitly.     Dim dev As Device = CType(sender, Device)     dev.RenderState.Lighting = False End Sub ' Define in the vertics. Public Sub OnCreateVertexBuffer(ByVal sender As Object, ByVal e As EventArgs)     Dim vb As VertexBuffer = CType(sender, VertexBuffer)     ' Lock the vertex buffer.      ' Lock returns an array of positionColored objects.     Dim vertices As CustomVertex.PositionColored() = _         CType(vb.Lock(0, 0), _             CustomVertex.PositionColored())     Dim start_index As Integer = 0     Const WID As Single = 2     MakeTriangle(vertices, start_index, Color.FromArgb(255, &HFF, 0, 0), _         0, WID, 0, _         0, 0, WID, _         WID, 0, 0)     ...     MakeTriangle(vertices, start_index, Color.FromArgb(255, 0, 0, &H60), _         0, -WID, 0, _         0, 0, WID, _         -WID, 0, 0)     vb.Unlock() End Sub Private Sub MakeTriangle( _     ByVal vertices As CustomVertex.PositionColored(), _     ByRef start_index As Integer, ByVal clr As Color, _     ByVal x1 As Single, ByVal y1 As Single, ByVal z1 As Single, _     ByVal x2 As Single, ByVal y2 As Single, ByVal z2 As Single, _     ByVal x3 As Single, ByVal y3 As Single, ByVal z3 As Single)     With vertices(start_index)         .X = x1         .Y = y1         .Z = z1         .Color = clr.ToArgb()     End With     start_index += 1     With vertices(start_index)         .X = x2         .Y = y2         .Z = z2         .Color = clr.ToArgb()     End With     start_index += 1     With vertices(start_index)         .X = x3         .Y = y3         .Z = z3         .Color = clr.ToArgb()     End With     start_index += 1 End Sub ' Render the scene. Private Sub Render()     ' Do nothing if the device has not been created yet.     If m_Device Is Nothing Then Return     ' Clear the back buffer.     m_Device.Clear(ClearFlags.Target, Color.White, 1, 0)     ' Begin the scene.     m_Device.BeginScene()     ' Setup the world, view, and projection matrices.     SetupMatrices()     ' Set the device's data stream source      ' (the vertex buffer).     m_Device.SetStreamSource(0, m_VertexBuffer, 0)     ' Tell the device the format of the vertices.     m_Device.VertexFormat = CustomVertex.PositionColored.Format     ' Draw the primitives in the data stream.     m_Device.DrawPrimitives(PrimitiveType.TriangleList, 0, NUM_TRIANGLES)     ' End the scene.     m_Device.EndScene()     ' Display the result.     m_Device.Present() End Sub ' Setup the world, view, and projection matrices. Private Sub SetupMatrices()     ' World Matrix:     ' Rotate the object around the Y axis by     ' 2 * Pi radians per 2000 ticks (2 seconds).     Const TICKS_PER_REV As Integer = 2000         Dim ms_rotated As Integer = Environment.TickCount Mod TICKS_PER_REV         Dim angle As Double = ms_rotated * (2 * Math.PI) / TICKS_PER_REV         m_Device.Transform.World = Matrix.RotationY(CSng(angle))         ' View Matrix:         ' This is defined by giving:         '       An eye point            (0, 3, -10)         '       A point to look at       (0, 0, 0)         '       An "up" direction        <0, 1, 0>         m_Device.Transform.View = Matrix.LookAtLH( _             New Vector3(0, 3, -10), _             New Vector3(0, 0, 0), _             New Vector3(0, 1, 0))         ' Projection Matrix:         ' Perspective transformation defined by:         '       Field of view           Pi / 4         '       Aspect ratio            1         '       Near clipping plane     Z = 1         '       Far clipping plane      Z = 100         m_Device.Transform.Projection = _             Matrix.PerspectiveFovLH(Math.PI / 4, 1, 1, 100)     End Sub End Class 

Setting all this up is a lot of work, but once it is running, it’s not too hard to make changes. You can add, remove, or modify the vertex data. You can also change the view matrix to look at the data from different positions.

Direct3D is just one part of DirectX, and even that part is too complicated to describe completely here. For more information, see the online help, the examples, and the tutorials that come with the development kit. For more information about three-dimensional graphics in general, transformations, and the matrixes that define transformations, you can also see a more theoretical three-dimensional graphics book such as 3D Computer Graphics, Third Edition by Allan H. Watt (Addison Wesley, 2000). My book Visual Basic Graphics Programming: Hands-On Applications and Advanced Color Development, Second Edition (John Wiley & Sons, 1999) also covers three-dimensional graphics in addition to many other graphics topics. Unfortunately the examples use Visual Basic 6 code (although perhaps there will be a Visual Basic .NET edition at some point).




Visual Basic 2005 with  .NET 3.0 Programmer's Reference
Visual Basic 2005 with .NET 3.0 Programmer's Reference
ISBN: 470137053
EAN: N/A
Year: 2007
Pages: 417

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net