1. Trang chủ
  2. » Công Nghệ Thông Tin

Ivor Horton’s BeginningVisual C++ 2008 phần 9 pptx

139 245 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 139
Dung lượng 2,28 MB

Nội dung

Figure 18-9 Serialization and Printing in CLR Sketcher Serialization is the process of writing objects to a stream, and deserialization is the reverse: reconstruct- ing objects from a stream. The .NET Framework offers several different ways to serialize and deserialize your C++/CLI class objects. XML serialization serializes objects into an XML stream that conforms to a particular XML schema. You can also serialize objects into XML streams that conform to the Simple Object Access Protocol (SOAP) specification and this is referred to as SOAP serialization. A discussion of XML and SOAP is beyond the scope of this book, not because it’s difficult — it isn’t — but because to cover it adequately requires more pages than I can possibly include in this book. I’ll therefore show you how to use the third and perhaps simplest form of serialization provided by the .NET Framework, binary serialization. You’ll also investigate how you can print sketches from CLR Sketcher. With the help given by the Form Designer, this is going to be easy. Understanding Binary Serialization Before you get into the specifics of serializing a sketch, let’s get an overview of what’s involved in binary serialization of your class objects. For binary serialization of your class objects to be possible, you have 1078 Chapter 18: Storing and Printing Documents 25905c18.qxd:WroxPro 2/21/08 9:21 AM Page 1078 to make your classes serializable. You can make a ref class or value class serializable by marking it with the Serializable attribute, like this: [Serializable] public ref class MyClass { // Class definition }; Of course, in practice there may be a little more to it. For serialization to work with MyClass, all the class fields must be serializable too, and often there are fields that are not serializable by default, or fields that it does not make sense to serialize because the data stored will not be valid when it is deserialized, or fields that you just don’t want serialized for security reasons. In this case special measures are necessary to take care of the non-serializable class members. Dealing with Fields That Are Not Serializable Where a class has members that are not serializable, you can mark them with the NonSerialized attrib- ute to prevent them from being serialized. For example: [Serializable] public ref class MyClass { public: [NonSerialized] int value; // Rest of the class definition }; Here the serialization process will not attempt to serialize the value member of a MyClass object. For this to compile, you need a using declaration for the System::Runtime::Serialization namespace. Although marking a data member as non-serializable avoids the possibility of the serialization process failing with types you cannot serialize, it does not solve the problem of serializing the objects unless the non-serialized data can be happily omitted when you come to reconstruct the object when it is deserial- ized. You will typically want to take some special action in relation to the non-serialized fields. You use the OnSerializing attribute to mark a class function that you want to be called when the seri- alization of an object begins. This gives you an opportunity to do something about the non-serialized fields. For example: [Serializable] public ref class MyClass { public: [NonSerialized] int value; 1079 Chapter 18: Storing and Printing Documents 25905c18.qxd:WroxPro 2/21/08 9:21 AM Page 1079 [OnSerializing] void FixNonSerializedData(StreamingContext context) { // Code to do the necessary ) // Rest of the class definition }; When a MyClass object is serialized, the FixNonSerializedData() function will be called for the object and the code in here will deal with the problem of value not being serialized, perhaps by allowing some other data to be serialized that will enable value to be reconstructed when the object is deserialized. The function that you mark with the OnSerializing attribute must have a void return type and a parameter of type StreamingContext. The StreamingContext object that is passed to the function when it is called is a struct containing information about the source and destination but you won’t need to use this in CLR Sketcher. You can also arrange for a member function to be called when an object is deserialized. You just have to mark the function with the OnSerialized attribute. For example: [Serializable] public ref class MyClass { public: [NonSerialized] int value; [OnSerializing] void FixNonSerializedData(StreamingContext context) { // Code to do the necessary ) [OnSerialized] void ReconstructValue(StreamingContext context) { // Code to do the necessary ) // Rest of the class definition }; The ReconstructValue() function will be executed after the deserialization process is complete, so this function will have the responsibility for setting value appropriately. The function that you mark with the OnSerialized attribute must also have a void return type and a parameter of type StreamingContext. To summarize, preparing a class to allow objects of that class type to be serialized involves the following five steps: 1. Mark the class to be serialized with the Serializable attribute. 1080 Chapter 18: Storing and Printing Documents 25905c18.qxd:WroxPro 2/21/08 9:21 AM Page 1080 2. Identify any data members that cannot or should not be serialized and mark them with NonSerialized attributes. 3. Add a public function with a return type of void and a single parameter of type StreamingContext to deal with the non-serializable fields when an object is serialized and mark the function with the OnSerializing attribute. 4. Add a public function with a return type of void and a single parameter of type StreamingContext to deal with the non-serialized fields when an object is deserialized and mark the function with the OnSerialized attribute. 5. Add a using declaration for the System::Runtime::Serialization namespace to the header file containing the class. Serializing an Object Serializing an object means writing it to a stream, so you first must define the stream that is the destina- tion for the data defining the object. A stream is represented by a System::IO::Stream class, which is an abstract ref class type. A stream can be any source or destination for data that is a sequence of bytes; a file, a TCP/IP socket, and a pipe that allows data to be passed between two processes are all examples of streams. You will usually want to serialize your objects to a file and the System::IO::File class contains static functions for creating objects that encapsulate files. You use the File::Open() function to create a new file or open an existing file for reading and/or writing. The Open() function returns a reference of type FileStream^ to an object that encapsulates the file. Because FileStream is a type that is derived from Stream, you can store the reference that the Open() function returns in a variable of type Stream^. The Open() function comes in three overloaded versions, and the version you will be using has the follow- ing form: FileStream^ Open( String^ path, FileMode mode) The path parameter is the path to the file that you want to open and can be a full path to the file, or just the file name. If you just specify an argument that is just a file name, the file will be assumed to be in the current directory. The mode parameter controls whether the file is created if it does not exist, and whether the data can be overwritten if the file does exist. The mode argument can be any of the FileMode enumeration values described in the following table. Continued FileMode Enumerator Description CreateNew Requests that a new file specified by path is created. If the file already exists, an exception of type System::IO::IOException is thrown. You use this when you are writing a new file. Truncate Requests that an existing file specified by path is opened and its con- tents discarded by truncating the size of the file to zero bytes. You use this when you are writing an existing file. 1081 Chapter 18: Storing and Printing Documents 25905c18.qxd:WroxPro 2/21/08 9:21 AM Page 1081 Thus you could create a stream encapsulating a file in the current directory that you can write with the following statement: Stream^ stream = File::Open(L”sketch.dat”, FileMode::Create); The file sketch.dat will be created in the current directory if it does not exist; if it exists the contents will be overwritten. The static OpenWrite() function in the File class will open the existing file that you specify by the string argument with write access and return a FileStream^ reference to the stream you use to write the file. To serialize an object to a file encapsulated by a FileStream object that you have created, you use an object of type System::Runtime::Serialization::Formatters::Binary::BinaryFormatter that you create like this: BinaryFormatter^ formatter = gcnew BinaryFormatter(); You need a using declaration for System::Runtime::Serialization::Formatters::Binary if this statement is to compile. The formatter object has a Serialize() function member that you use to seri- alize an object to a stream. The first argument to the function is a reference to the stream that is the desti- nation for the data and the second argument is a reference to the object to be serialized to the stream. Thus you can write a sketch object to stream with the following statement: formatter->Serialize(stream, sketch); You read an object from a stream using the Deserialize() function for a BinaryFormatter object: Sketch sketch = safe_cast<Sketch>(formatter->Deserialize(stream)); The argument to the Deserialize() function is a reference to the stream that is to be read. The function returns the object read from the stream as type Object^ so you must cast it to the appropriate type. FileMode Enumerator Description Create Specifies that if the file specified by path does not exist, it should be created and if the file does exist it should be overwritten. You use this when you are writing a file. Open Specifies that the existing file specified by path should be opened. If the file does not exist, an exception of type System::IO::FileNotFoundException is thrown. You use this when you are reading a file. OpenOrCreate Specifies that the file specified by path should be opened if it exists and created if it doesn’t. You can use this to read or write a file, depending on the access argument. Append The file specified by path is opened if it exists and the file position set to the end of the file; if the file does not exist, it will be created. You use this to append data to an existing file or to write a new file. 1082 Chapter 18: Storing and Printing Documents 25905c18.qxd:WroxPro 2/21/08 9:21 AM Page 1082 Serializing a Sketch You have to do two things to allow sketches to be serialized in the CLR Sketcher application: Make the Sketch class serializable and add code to enable the File menu items and toolbar buttons to support saving and retrieving sketches. Making the Sketch Class Serializable Add the Serializable attribute immediately before that Sketch class definition to specify that the class is serializable: [Serializable] public ref class Sketch { // Class definition as before }; Although this indicates that the class is serializable, in fact it is not. Serialization will fail because the STL/CLR container classes are not serializable by default. You must specify that the elements class member is not serializable, like this: [Serializable] public ref class Sketch { private: [NonSerialized] list<Element^>^ elements; // Rest of the class definition as before }; The class really is serializable now, but not in a useful way because none of the elements in the sketch will get written to the file. You must provide an alternative repository for the elements in the list<Element^> container that is serializable to get the sketch elements written to the file. Fortunately, a regular C++/CLI array is serializable and even more fortunately, the list container has a ToArray() function that returns the entire contents of the container as an array. You can therefore add a public function to the class with the OnSerializing attribute that will copy the contents of the elements container to an array and that you can arrange to be called before serialization begins. You can also add a public function with the OnSerialized attribute that will recreate the elements container when the array containing the elements is deserialized. Here are the changes to the Sketch class that will accommodate that: [Serializable] public ref class Sketch { private: [NonSerialized] list<Element^>^ elements; array<Element^>^ elementArray; public: Sketch(): elementArray(nullptr) { elements = gcnew list<Element^>(); } 1083 Chapter 18: Storing and Printing Documents 25905c18.qxd:WroxPro 2/21/08 9:21 AM Page 1083 [OnSerializing] void ListToArray(StreamingContext context) { elementArray = elements->to_array(); } [OnDeserialized] void ArrayToList(StreamingContext context) { elements = gcnew list<Element^>(elementArray); elementArray = nullptr; } // Rest of the class definition as before }; You have a new private data member, elementArray, that holds all the elements in the sketch when it is serialized to a file. You initialize this to nullptr in the constructor. When a Sketch object is serialized, the ListToArray() function will be called first, and this function transfers the contents of the list con- tainer to the elementArray array before serialization of the object takes place. The Sketch object con- taining the elementArray object is then written to the file. When a sketch is read back from the file, the Sketch object is recreated containing the elementArray member, after which the ArrayToList() function is called to restore the contents of the elements container from the array. The array is no longer required, so you set it to nullptr in the function. So far, so good but you are not quite there yet. For a sketch to be serializable, all the elements in the sketch must be serializable too, and there’s the small problem of the Curve class that has an STL/CLR container as a member. First though, add the Serializable attribute to the Element class and all its subclasses. You can pull the same trick with the container in the Curve class as you did with the Sketch class con- tainer, so amend the class definition to the following: [Serializable] public ref class Curve : Element { private: [NonSerialized] vector<Point>^ points; array<Point>^ pointsArray; public: Curve(Color color, Point p1, Point p2, float penWidth) : pointsArray(nullptr) { this->penWidth = penWidth; pen = gcnew Pen(color, penWidth); this->color = color; points = gcnew vector<Point>(); position = p1; points->push_back(Point(p2.X-position.X, p2.Y-position.Y)); // Find the minimum and maximum coordinates 1084 Chapter 18: Storing and Printing Documents 25905c18.qxd:WroxPro 2/21/08 9:21 AM Page 1084 int minX = p1.X < p2.X ? p1.X : p2.X; int minY = p1.Y < p2.Y ? p1.Y : p2.Y; int maxX = p1.X > p2.X ? p1.X : p2.X; int maxY = p1.Y > p2.Y ? p1.Y : p2.Y; int width = Math::Max(2, maxX - minX); int height = Math::Max(2, maxY - minY); boundRect = System::Drawing::Rectangle(minX, minY, width, height); } [OnSerializing] void VectorToArray(StreamingContext context) { pointsArray = points->to_array(); } [OnDeserialized] void ArrayToVector(StreamingContext context) { points = gcnew vector<Point>(pointsArray); pointsArray = nullptr; } // Rest of the class definition as before }; The changes are very similar to those in the Sketch class. You have an array member to store the points defining the curve when serializing an object and two functions that take care of creating an array con- taining the points before serialization and restoring the points container after deserialization. Don’t for- get to add a using declaration for the System::Runtime::Serialization namespace to Sketch.h and Elements.h. You are not out of the woods yet on serializing elements. If you try to serialize a sketch now, it will fail because objects of type Pen and Brush (and subclasses of these types) are not serializable. The Element class has a member of type Pen^ and the TextElement class has a member of type SolidBrush^ so nei- ther of these classes is serializable at present. The first step to fixing this is to mark these members with the NonSerialized attribute. You can then add a public function to the Element class to restore the Pen object when an element is deserialized: [OnDeserialized] void CreatePen(StreamingContext context) { pen = gcnew Pen(color, penWidth); } This function is called for the base class whenever an object of any of the derived class types is deserialized. You can add a public function to the TextElement class to restore the SolidBrush object when a text element is deserialized: [OnDeserialized] void CreateBrush() { 1085 Chapter 18: Storing and Printing Documents 25905c18.qxd:WroxPro 2/21/08 9:21 AM Page 1085 brush = gcnew SolidBrush(color); } This recreates the brush when a TextElement object is deserialized. All the classes involved in serializ- ing and deserializing a sketch should now be OK, so it’s time to implement the event handlers for the menu items. Implementing File Operations for a Sketch The menu items and toolbar buttons for file operations are already in place in CLR Sketcher. If you double- click the Click event property in the Properties window for the File > Save, File > Save As and File > Open menu items, you’ll put the event handlers in place for all of them. Then just select the appro- priate event handler from the drop-down list of values for the Click event for each of the toolbar buttons. All you have to do now is supply the code to make them do what you want. Creating Dialogs for File Operations The Toolbox has standard dialogs for opening and saving files and you can use both of these. Drag an OpenFileDialog and a SaveFileDialog from the Toolbox to the Design window for Form1. You can change the (name) property values to saveFileDialog and openFileDialog. Change the values for the Title properties for both dialogs to whatever you want displayed in the title bar. You can also change the FileName property in the openFileDialog to sketch; this is the default file name that will be displayed when the dialog is first used. It’s a good idea to define a folder that will hold your sketches so create one now; you could use something like C:\CLR Sketches. You can specify the default directory as the value for the InitialDirectory property for both dialogs. Both dialogs have a Filter property that specifies the filters for the list of files that are displayed by the dialogs. You can set the value for the Filter property for both dialogs to “CLR Sketches|*.ske| All files|*.*”. The default value of 1 for the FilterIndex property determines that the first file filter applies by default. If you want the sec- ond file filter to apply, set the value for FilterIndex to 2. Verify that the values for the ValidateNames and OverwritePrompt properties for the saveFileDialog have default values of true; this results in the user being prompted when an existing sketch is about to be overwritten. Saving a Sketch You need a BinaryFormatter object to save a sketch and a good place to keep it is in the Form1 class. Add a using declaration for the System::Runtime::Serialization::Formatters::Binary name- space to Form1.h and add a new private member, formatter, of type BinaryFormatter^ to the Form1 class. You can initialize it to gcnew BinaryFormatter() in the initialization list for the Form1 class constructor. Before you get into implementing the event handler for the File > Save menu item, let’s consider what the logic is going to be. When you click the File > Save menu item, what happens depends on whether the current sketch has been saved before. If the sketch has never been saved, you want the file save dialog to be displayed; if the sketch has been saved previously, you just want to write the sketch to the file without displaying the dialog. To allow this to work, you need a way to record whether or not the sketch has been saved. One way to do this is to add a public member of type bool with the name Saved to the Sketch class. You can initialize it to false in the Sketch class constructor and set it to true the first time the sketch is saved. Before you implement the Click event handler, add a using declaration for the System.IO namespace to the Form1.h header file. Add a private String^ member, sketchFilepath, to the Form1 class to store 1086 Chapter 18: Storing and Printing Documents 25905c18.qxd:WroxPro 2/21/08 9:21 AM Page 1086 the file path for a sketch; initialize this to nullptr in the Form1 constructor. You can add the following code to implement the Click event handler for save operations: private: System::Void saveToolStripMenuItem_Click( System::Object^ sender, System::EventArgs^ e) { Stream^ stream; if(!sketch->Saved) { // Sketch not saved so display the dialog if(saveFileDialog->ShowDialog() == System::Windows::Forms::DialogResult::OK) { if((stream = File::Open(saveFileDialog->FileName, FileMode::Create)) != nullptr) { formatter->Serialize(stream, sketch); stream->Close(); sketchFilepath = saveFileDialog->FileName; sketch->Saved = true; } } } else { // Sketch saved previously so just save the sketch stream = File::OpenWrite(sketchFilepath); formatter->Serialize(stream, sketch); stream->Close(); } } There are two courses of action depending on whether or not the sketch has been saved previously. If the sketch hasn’t been saved before, you open the save dialog. If the OK button closes the dialog you call the static Open() function to create a FileStream object for the file using the file name provided by the FileName property for the dialog object. You serialize the sketch to the file by calling the Serialize() function for the BinaryFormatter object and close the stream. You save the file path for use next time around and set the Saved member of the Sketch object to true. The else clause belonging to the first if statement specifies what happens when the sketch has been saved previously. You obtain a reference to a FileStream object that you can use to serialize the sketch by calling the static OpenWrite() function that is defined in the File class. You then serialize the sketch in the same way as before. Finally you call Close() for the stream to close the stream and release the resources. Retrieving a Sketch from a File The Click handler for the File > Open menu item deals with reading a sketch from a file. You can implement it like this: private: System::Void openToolStripMenuItem_Click( System::Object^ sender, System::EventArgs^ e) { if(openFileDialog->ShowDialog() == System::Windows::Forms::DialogResult::OK) 1087 Chapter 18: Storing and Printing Documents 25905c18.qxd:WroxPro 2/21/08 9:21 AM Page 1087 [...]... System::Windows::MessageBox class will be helpful, too.) 1 091 2 590 5c18.qxd:WroxPro 2/21/08 9: 21 AM Page 1 092 2 590 5c 19. qxd:WroxPro 2/21/08 9: 22 AM Page 1 093 19 Writing Your Own DLLs Chapter 9 discussed how a C++/ CLI class library is stored in a dll file Dynamic link libraries (DLLs) are also used extensively with native C++ applications A complete discussion of DLLs in native C++ applications is outside the scope of... the scope of this book DLL Varieties There are three different kinds of DLL that you can build with Visual C++ 2008 using MFC: an MFC extension DLL, a regular DLL with MFC statically linked, and a regular DLL with MFC dynamically linked 1 098 2 590 5c 19. qxd:WroxPro 2/21/08 9: 22 AM Page 1 099 Chapter 19: Writing Your Own DLLs MFC Extension DLL You build this kind of DLL whenever it’s going to include classes... to the DLL form of MFC 1 095 2 590 5c 19. qxd:WroxPro 2/21/08 9: 22 AM Page 1 096 Chapter 19: Writing Your Own DLLs 2 .dll loaded Library.dll 4 ProgramB loaded ProgramC.exe ProgramB.exe ProgramA.exe 1 ProgramA loaded 6 ProgramC loaded ProgramA ProgramB ProgramC Library.dll 5 linkage to dll function Function 3 linkage to dll function 7 linkage to dll function Computer Memory Figure 19- 2 Having a function stored... Library2.dll is required and causes it to be loaded Function 4 The program obtains the address of a function from the DLL and uses it to call the function Computer Memory Figure 19- 3 1 097 2 590 5c 19. qxd:WroxPro 2/21/08 9: 22 AM Page 1 098 Chapter 19: Writing Your Own DLLs Runtime dynamic linking enables a program to defer linking of a DLL until it’s certain that the functions in a DLL are required This allows you... is an integral part of each executable module, as illustrated in Figure 19- 1 2 590 5c 19. qxd:WroxPro 2/21/08 9: 22 AM Page 1 094 Chapter 19: Writing Your Own DLLs Static Library Copy added to each program during linkedit Library function ProgramA.exe ProgramB.exe ProgramC.exe Library function Library function Library function Figure 19- 1 Although this is a very convenient way of using a standard function... output, for instance — are invariably common to most programs and are likely to occupy sizable chunks of memory Having these statically linked would be extremely inefficient 1 094 2 590 5c 19. qxd:WroxPro 2/21/08 9: 22 AM Page 1 095 Chapter 19: Writing Your Own DLLs Another consideration is that a standard function from a static library may be linked into hundreds of programs in your system, so identical copies... a DLL with the same name being overwritten This can interfere with other applications that you have already installed and, in the worst case, can render them inoperable 1 096 2 590 5c 19. qxd:WroxPro 2/21/08 9: 22 AM Page 1 097 Chapter 19: Writing Your Own DLLs Runtime Dynamic Linking The DLL that you’ll create in this chapter is automatically loaded into memory when the program that uses it is loaded into... programs The DLL is a particularly good solution for managing these, especially if some of the programs using your standard facilities are likely to be executing concurrently 1 099 2 590 5c 19. qxd:WroxPro 2/21/08 9: 22 AM Page 1100 Chapter 19: Writing Your Own DLLs ❑ You have a complex application that involves several programs and a lot of code but that has sets of functions or resources that may be shared among... branch in the left pane of the dialog Change the value of the Character Set option to “Use Multibyte Character Set” from the drop-down list in the value column Figure 19- 4 Figure 19- 5 1101 2 590 5c 19. qxd:WroxPro 2/21/08 9: 22 AM Page 1102 Chapter 19: Writing Your Own DLLs Now that the MFC DLL wizard has done its stuff, you can look into the code that has been generated on your behalf If you look at the contents... ExtDLLExample project, select the menu option Project > Add Existing Item and choose the file Elements.cpp from the list box in the dialog box, as shown in Figure 19- 6 1103 2 590 5c 19. qxd:WroxPro 2/21/08 9: 22 AM Page 1104 Chapter 19: Writing Your Own DLLs Figure 19- 6 The project should also include the files containing the definitions of the shape classes and your constants, so repeat the process for Elements.h and . cleanup. 1 091 Chapter 18: Storing and Printing Documents 2 590 5c18.qxd:WroxPro 2/21/08 9: 21 AM Page 1 091 2 590 5c18.qxd:WroxPro 2/21/08 9: 21 AM Page 1 092 19 Writing Your Own DLLs Chapter 9 discussed. programs that link dynamically to the DLL form of MFC. 1 095 Chapter 19: Writing Your Own DLLs 2 590 5c 19. qxd:WroxPro 2/21/08 9: 22 AM Page 1 095 Figure 19- 2 Having a function stored in a DLL introduces the. linkedit Library function Library function Library function Library function ProgramA.exe Static Library ProgramB.exe ProgramC.exe 1 094 Chapter 19: Writing Your Own DLLs 2 590 5c 19. qxd:WroxPro 2/21/08 9: 22 AM Page 1 094 Another consideration is that a standard function from

Ngày đăng: 12/08/2014, 19:20