// set the file callback for reading // begin reading the string from the client public void StartRead // when called back by the read, display the string // and echo it back to
Trang 1myFileCallBack, // call back delegate
null); // local state object
}
The cycle begins again with another read of the file, and the cycle continues until the file has been completely read and transmitted to the client The client code simply writes a filename to the network stream to kick off the file read:
string message = @"C:\test\source\AskTim.txt";
bool fQuit = false;
while (!fQuit)
{
char[] buffer = new char[BufferSize];
You are now ready to create a new StreamReader from the NetworkStream member variable streamToServer:
System.IO.StreamReader reader =
Trang 2The call to Read( ) takes three parameters: the buffer, the offset at which to begin reading, and the size of the buffer:
int bytesRead = reader.Read(buffer,0, BufferSize);
Check to see if the Read( ) returned any bytes; if not you are done and you can set the Boolean value fQuit to true, causing the loop to terminate:
// get a file name from the client
// open the file and send the
// contents from the server to the client
public class AsynchNetworkFileServer
buffer = new byte[256];
// create the network stream
Trang 3// set the file callback for reading
// begin reading the string from the client
public void StartRead( )
// when called back by the read, display the string
// and echo it back to the client
private void OnReadComplete( IAsyncResult ar )
{
int bytesRead = networkStream.EndRead(ar);
// if you got a string
"Opening file {0}", fileName);
// open the file input stream
myFileCallBack, // call back delegate
null); // local state object
}
Trang 4// when you have a buffer-full of the file
void OnFileCompletedRead(IAsyncResult asyncResult)
// after writing the string, get more of the file
private void OnWriteComplete( IAsyncResult ar )
{
networkStream.EndWrite(ar);
Console.WriteLine( "Write complete");
// begin reading more of the file
inputStream.BeginRead(
buffer, // holds the results
0, // offset
buffer.Length, // (BufferSize)
myFileCallBack, // call back delegate
null); // local state object
}
private const int BufferSize = 256;
private byte[] buffer;
private Socket socket;
private NetworkStream networkStream;
private Stream inputStream;
private AsyncCallback callbackRead;
private AsyncCallback callbackWrite;
private AsyncCallback myFileCallBack;
}
Trang 5public static void Main( )
// if a client connects, accept the connection
// and return a new socket named socketForClient
// while tcpListener keeps listening
Trang 6private int Run( )
{
string message = @"C:\test\source\AskTim.txt";
Console.Write(
"Sending {0} to server.", message);
// create a streamWriter and use it to
// write a string to the server
System.IO.StreamWriter writer =
new System.IO.StreamWriter(streamToServer);
writer.Write(message);
writer.Flush( );
bool fQuit = false;
// while there is data coming
// from the server, keep reading
while (!fQuit)
{
// buffer to hold the response
char[] buffer = new char[BufferSize];
// Read response
System.IO.StreamReader reader =
new System.IO.StreamReader(streamToServer);
// see how many bytes are
// retrieved to the buffer
private const int BufferSize = 256;
private NetworkStream streamToServer;
Trang 7will encapsulate the object pointed to by the URI That is, you can call GetResponse( ) on your WebRequest object to get the actual object (e.g., a web page) pointed to by the URI What is returned is encapsulated in a WebResponse object You can then ask that WebResponse object for a Stream object by calling GetResponseStream( ) GetResponseStream( ) returns a stream that encapsulates the contents of the web object (e.g., a stream with the web page)
The next example retrieves the contents of a web page as a stream To get a web page, you'll want to use HttpWebRequest HttpWebRequest derives from WebRequest and provides additional support for interacting with the HTTP protocol
To create the HttpWebRequest, cast the WebRequest returned from the static Create( ) method of the WebRequestFactory:
Creating the HTTPWebRequest establishes a connection to a page on my web site What you get back from the host is encapsulated in an HttpWebResponse object, which is an HTTP protocol-specific subclass of the more general WebResponse class:
Trang 8Example 21-14 Reading a web page as an HTML stream
// get the streamReader from the response
StreamReader streamReader = new StreamReader(
<body bgcolor="#ffffff" vlink="#808080"
alink="#800000" topmargin="0" leftmargin="0">
<table border="0" cellpadding="0" cellspacing="0" width="454"
Trang 9like to work on a programming project."
</font></b><font face="times new roman, times,
serif" size="3"><b>
</b> Barnes & Noble</font></i><font size="3"><br>
The output shows that what is sent through the stream is the HTML of the page you
requested You might use this capability for screen scraping; reading a page from a site into a
buffer and then extracting the information you need
All examples of screen scraping in this book assume that you are reading a site for which you have copyright permission
21.6 Serialization
When an object is streamed to disk, its various member data must be serialized that is,
written out to the stream as a series of bytes The object will also be serialized when stored in
a database or when marshaled across a context, app domain, process, or machine boundary
The CLR provides support for serializing an object-graph an object and all the member data
of that object As noted in Chapter 19, by default, types are not serialized To serialize an object, you must explicitly mark it with the [Serializable] attribute
In either case, the CLR will do the work of serializing your object for you Because the CLR knows how to serialize all the primitive types, if your object consists of nothing but primitive types (all your member data consists of integers, longs, strings, etc.), you're all set If your object consists of other user-defined types (classes), you must ensure that these types are also serializable The CLR will try to serialize each object contained by your object (and all their contained objects as well), but these objects themselves must either be primitive types or they must be serializable
This was also evident in Chapter 19 when you marshaled a Shape object that contained a Point object as member data The Point object in turn consisted of primitive data In order to serialize (and thus marshal) the Shape object, its constituent member, the Point object, also had to be marked as serializable
When an object is marshaled, either by value or by reference, it must be serialized The difference is only whether a copy is made or a proxy is provided to the client Objects marked with the [Serializable] attribute are marshaled by value; those that derive from MarshalByRefObject are marshaled by reference, but both are serialized See Chapter 19 for more information
21.6.1 Using a Formatter
When data is serialized, it will eventually be read; either by the same program or by a different program running on another machine In any case, the code reading the data will expect that data to be in a particular format Most of the time in a NET application, the
Trang 10expected format will either be native binary format or Simple Object Access Protocol (SOAP)
SOAP is a simple, lightweight XML-based protocol for exchanging information across the Web SOAP is highly modular and very extensible It also leverages existing Internet technologies, such as HTTP and SMTP
When data is serialized, the format of the serialization is determined by the formatter you
apply In Chapter 19, you used formatters with channels when communicating with a remote object Formatter classes implement the interface IFormatter; you are also free to create your own formatter, though very few programmers will ever need or want to! The CLR provides both a SoapFormatter for Internet serialization and a BinaryFormatter that is useful for fast local storage
You can instantiate these objects with their default constructors:
BinaryFormatter binaryFormatter =
New BinaryFormatter( );
Once you have an instance of a formatter, you can invoke its Serialize( ) method, passing
in a stream and an object to serialize You'll see how this is done in the next example
21.6.2 Working with Serialization
To see serialization at work, you need a sample class that you can serialize and then deserialize You can start by creating a class named SumOf SumOf has three member variables:
private int startNumber = 1;
private int endNumber;
private int[] theSums;
The member array theSums represents the value of the sums of all the numbers from startNumber through endNumber Thus, if startNumber is 1 and endNumber is 10, the array will have the values:
1,3,6,10,15,21,28,36,45,55
Each value is the sum of the previous value plus the next in the series Thus if the series is 1,2,3,4, the first value in theSums will be 1 The second value is the previous value (1) plus the next in the series (2); thus, theSums[1] will hold the value 3 Likewise, the third value is the previous value (3) plus the next in the series theSums[2] is 6 Finally, the fourth value
in theSums is the previous value (6) plus the next in the series (4), for a value of 10
The constructor for the SumOf object takes two integers: the starting number and the ending number It assigns these to the local values and then calls a helper function to compute the contents of the array:
Trang 11public SumOf(int start, int end)
int count = endNumber - startNumber + 1;
theSums = new int[count];
You can display the contents of the array at any time by using a foreach loop:
private void DisplaySums( )
21.6.2.1 Serializing the object
Now, mark the class as eligible for serialization with the [Serializable] attribute:
binaryFormatter.Serialize(fileStream,this);
This will serialize the SumOf object to disk
21.6.2.2 Deserializing the object
To reconstitute the object, open the file and ask a binary formatter to DeSerialize it:
Trang 12public static SumOf DeSerialize( )
to display its values:
public static void Main( )
{
Console.WriteLine("Creating first one with new ");
SumOf app = new SumOf(1,10);
Console.WriteLine(
"Creating second one with deserialize ");
SumOf newInstance = SumOf.DeSerialize( );
Console.WriteLine("Creating first one with new ");
SumOf app = new SumOf(1,10);
Console.WriteLine("Creating second one with deserialize ");
SumOf newInstance = SumOf.DeSerialize( );
Trang 13private void ComputeSums( )
{
int count = endNumber - startNumber + 1;
theSums = new int[count];
private int startNumber = 1;
private int endNumber;
private int[] theSums;
Trang 1421.6.3 Handling Transient Data
In some ways, the approach to serialization demonstrated in Example 21-15 is very wasteful Because you can compute the contents of the array given its starting and ending numbers, there really is no reason to store its elements to disk Although the operation might be inexpensive with a small array, it could become costly with a very large one
You can tell the serializer not to serialize some data by marking it with the [NonSerialized] attribute:
[NonSerialized] private int[] theSums;
If you don't serialize the array, however, the object you create will not be correct when you deserialize it The array will be empty Remember, when you deserialize the object, you simply read it up from its serialized form; no methods are run
To fix the object before you return it to the caller, implement the IDeserializationCallback interface:
[Serializable]
class SumOf : IDeserializationCallback
Also implement the one method of this interface: OnDeserialization( ) The CLR promises that if you implement this interface, your class's OnDeserialization( ) method will be called when the entire object graph has been deserialized This is just what you want; the CLR will reconstitute what you've serialized, and then you have the opportunity to fix up the parts that were not serialized
This implementation can be very simple; just ask the object to recompute the series:
public virtual void OnDeserialization (Object sender)
{
ComputeSums( );
}
Trang 15This is a classic space/time trade-off; by not serializing the array you make deserialization somewhat slower (because you must take the time to recompute the array), and you make the file somewhat smaller To see if not serializing the array had any effect, I ran the program with the digits 1 to 5,000 Before setting [NonSerialized] on the array, the serialized file was 20K After setting [NonSerialized], the file was 1K Not bad Example 21-16 shows the source code using the digits 1 to 5 as input (to simplify the output)
Example 21-16 Working with a nonserialized object
Console.WriteLine("Creating first one with new ");
SumOf app = new SumOf(1,5);
Console.WriteLine("Creating second one with deserialize "); SumOf newInstance = SumOf.DeSerialize( );
int count = endNumber - startNumber + 1;
theSums = new int[count];
Trang 16private void Serialize( )
// fix up the nonserialized data
public virtual void OnDeserialization
(Object sender)
{
ComputeSums( );
}
private int startNumber = 1;
private int endNumber;
[NonSerialized] private int[] theSums;
Trang 17So far you've streamed your data to disk for storage and across the network for easy communication with distant programs There is one other time you might create a stream: to store permanent configuration and status data on a per-user basis For this purpose, the NET
Frameworks offer isolated storage
21.7 Isolated Storage
The NET CLR provides isolated storage to allow the application developer to store data on a
per-user basis Isolated storage provides much of the functionality of traditional Windows ini
files or the more recent HKEY_CURRENT_USER key in the Windows Registry
Applications save data to a unique data compartment associated with the application The CLR implements the data compartment with a data store, which is typically a directory on the
filesystem
Administrators are free to limit how much isolated storage individual applications can use They can also use security so that less trusted code cannot call more highly trusted code to write to isolated storage
What is important about isolated storage is that the CLR provides a standard place to store your application's data, but it does not impose (or support) any particular layout or syntax for that data In short, you can store anything you like in isolated storage
Typically, you will store text, often in the form of name-value pairs Isolated storage is a good mechanism for saving user configuration information such as login name, the position of various windows and widgets, and other application-specific, user-specific information The data is stored in a separate file for each user, but the files can be isolated even further by distinguishing among different aspects of the identity of the code (by assembly or by originating application domain)
Using isolated storage is fairly straightforward To write to isolated storage, create an instance
of an IsolatedStorageFileStream, which you initialize with a filename and a file mode (create, append, etc.):
Trang 18Example 21-17 Writing to isolated storage
System.DateTime currentTime = System.DateTime.Now;
output = "Last access: " + currentTime.ToString( );
Trang 19Example 21-18 Reading from isolated storage
private void Run( )
Trang 20Chapter 22 Programming NET and COM
Programmers love a clean slate Although it would be nice if we could throw away all the code we've ever written and start over, this typically isn't a viable option for most companies Over the past decade, many development organizations have made a substantial investment in developing and purchasing COM components and ActiveX controls If NET is
to be a viable platform, these legacy components must be usable from within NET applications, and to a lesser degree, NET components must be callable from COM
This chapter describes the support NET provides for importing ActiveX controls and COM components into your application, for exposing NET classes to COM-based applications, and for making direct calls to Win32 APIs You will also learn about C# pointers and keywords for accessing memory directly, a technique that may be crucial in some applications
22.1 Importing ActiveX Controls
ActiveX controls are COM components typically dropped into a form, which might or might not have a user interface When Microsoft developed the OCX standard, which allowed developers to build ActiveX controls in Visual Basic and use them with C++ (and vice versa), the ActiveX control revolution began Over the past few years, thousands of such controls have been developed, sold, and used They are small, easy to work with, and an effective example of binary reuse
Importing ActiveX controls into NET is surprisingly easy, considering that the COM binary standard and the NET binary standard are not compatible Visual Studio NET is able to import ActiveX controls Microsoft has also developed a command-line utility, AxImp, which will create the assemblies necessary for the control to be used in a NET application
22.1.1 Creating an ActiveX Control
To demonstrate the ability to use classic ActiveX controls in a NET application, first develop
a simple four-function calculator as an ActiveX control and then invoke that ActiveX control from within a C# application Build the control in VB6, and test it in a VB6 application If you don't have VB6 or don't want to bother creating the control, you can download the control from my web site (http://www.libertyassociates.com/)
Once the control is working in the standard Windows environment, you'll copy it to your NET development environment, register it, and import it into a Windows Forms application
To create the control, open VB6 and choose ActiveX Control as the new project type Make the project form as small as possible, because this control will not have a user interface Right-click UserControl1 and choose Properties Rename it Calculator in the Properties window Click the Project in the project explorer, and in the Properties window, rename it to CalcControl Immediately save the project and name both the file and the project CalcControl,
as shown in Figure 22-1
Trang 21Figure 22-1 Creating a VB ActiveX control
Now you can add the four calculator functions by right-clicking the CalcControl form, selecting View Code from the pop-up menu, and typing in the VB code shown in Example 22-1
Example 22-1 Implementing the CalcControl ActiveX control
This is the entire code for the control Compile this to the CalcControl.ocx file by choosing
File Make CalcControl.ocx on the Visual Basic 6 menu bar
Trang 22Next open a second project in VB as a standard executable (EXE) Name the form TestForm and name the project CalcTest Save the file and project as CalcTest
Add the ActiveX control as a component by pressing Control-T and choosing CalcControl from the Controls tab, as shown in Figure 22-2
Figure 22-2 Adding the CalcControl to the VB6 toolbox
This action puts a new control on the toolbox, as shown circled in Figure 22-3
Figure 22-3 Locating CalcControl in the Visual Basic 6 toolbox
Drag the new control on to the form TestForm and name it CalcControl Note that the new control will not be visible; this control has no user interface Add two text boxes, four buttons, and one label, as shown in Figure 22-4
Trang 23Figure 22-4 Building the TestForm user interface
Name the buttons btnAdd, btnSubtract, btnMultiply, and btnDivide All that is left is for you to implement methods for handling the button click events of the calculator buttons Each time a button is clicked, you want to get the values in the two text boxes, cast them to double (as required by CalcControl) using the VB6 CDbl function, invoke a CalcControl function, and print the result in the label control Example 22-2 provides the complete source code
Example 22-2 Using the CalcControl ActiveX control in a VB program (TestForm)
Private Sub btnAdd_Click( )
Trang 24Figure 22-5 shows the result of running the CalcTest program, typing in two numbers and clicking the Multiply button
Figure 22-5 Running the CalcTest program
22.1.2 Importing a Control in NET
Now that you've shown that the CalcControl ActiveX control is working, you can copy the
CalcControl.ocx file to your NET development environment Once you have copied it,
register the CalcControl.ocx file using Regsvr32 You're now ready to build a test program in
.NET to use the calculator:
Regsvr32 CalcControl.ocx
To get started, create a Visual C# Windows Form project in Visual Studio NET (see Chapter 13), name the project InteropTest, and design a form (such as the TestForm form you created in VB in the preceding section) by dragging and dropping controls onto it Name the form TestForm A complete sample form is shown in Figure 22-6
Figure 22-6 Building a Windows Form to test the CalcControl ActiveX control
Trang 2522.1.2.1 Importing a control
There are two ways to import an ActiveX control into the Visual Studio NET development environment You can use the Visual Studio NET tools themselves, or import the control
manually using the aximp utility that ships with the NET SDK Framework To use Visual
Studio NET, choose Tools Customize Toolbox from the menu On the COM Components tab find the CalcControl.Calculator object you just registered, as shown in Figure 22-7
Figure 22-7 Importing the CalcControl ActiveX control
Because CalcControl is registered on your NET machine, the Visual Studio NET Customize Toolbox is able to find it When you select the control from this dialog box, it is imported into your application; Visual Studio takes care of the details Alternatively, you can
open a command box and import the control manually using the aximp.exe utility, as shown in
Figure 22-8
Figure 22-8 Running aximp
aximp.exe takes one argument, the ActiveX control you want to import (CalcControl.dll) It
produces three files:
AxCalcControl.dll
A NET Windows control
Trang 26CalcControl.dll
A proxy NET class library
AxCalcControl.pdb
A debug file
22.1.2.2 Adding a control to the Visual Studio toolbox
Once this is done, you can return to the Customize Toolbox window, but this time select NET Framework Components You can now browse to the location at which the NET Windows
control AxCalcControl.dll was generated and import that file into the toolbox, as shown in
Figure 22-9
Figure 22-9 Browsing for the imported control
Once imported, the control appears on the toolbox menu, as shown in Figure 22-10 Note that the control may appear at the bottom of the toolbox
Figure 22-10 Viewing the AxCalcControl calculator after importing it into the toolbox
Trang 27Now you can drag this control onto your Windows Form and make use of its functions, just as you did in the VB6 example
Add event handlers for each of the four buttons The event handlers will delegate their work
to the ActiveX control you wrote in VB6 and imported into NET
The source for the event handlers is shown in Example 22-3
Example 22-3 Implementing event handlers for the test Windows Form
private void btnAdd_Click(object sender, System.EventArgs e)
{
double left = double.Parse(textBox1.Text);
double right = double.Parse(textBox2.Text);
label1.Text = axCalculator1.Add( ref left, ref right).ToString( );
}
private void btnDivide_Click(object sender, System.EventArgs e)
{
double left = double.Parse(textBox1.Text);
double right = double.Parse(textBox2.Text);
label1.Text = axCalculator1.Divide(ref left, ref right).ToString( ); }
private void btnMultiply_Click(object sender, System.EventArgs e)
{
double left = double.Parse(textBox1.Text);
double right = double.Parse(textBox2.Text);
label1.Text = axCalculator1.Multiply(ref left, ref right).ToString( ); }
private void btnSubtract_Click(object sender, System.EventArgs e)
{
double left = double.Parse(textBox1.Text);
double right = double.Parse(textBox2.Text);
label1.Text = axCalculator1.Subtract(ref left, ref right).ToString( ); }
Each implementing method obtains the values in the text fields, converts them to double using the static method double.Parse( ), and passes those values to the calculator's methods The results are cast back to a string and inserted in the label, as shown in Figure 22-11
Figure 22-11 Running the imported ActiveX Control in a Windows Form
22.2 Importing COM Components
Importing ActiveX controls turns out to be fairly straightforward Many of the COM components that companies develop are not ActiveX controls, however; they are standard COM dynamic link library (DLL) files To see how to use these with NET, return to VB6 and