E57 Foundation API v1.1.312  Aug. 10, 2011
Functions
examples/HelloWorld.cpp File Reference

example: the basics More...

Include dependency graph for HelloWorld.cpp:

Functions

int main (int, char **)
 Example use of ImageFile.

Detailed Description

example: the basics

Also see listing at end of this page for source without line numbers (to cut&paste from).

00001 /*** HelloWorld.cpp example: the basics */
00004 #include <iostream>
00005 #include "E57Foundation.h"
00006 using namespace e57;
00007 using namespace std;
00008 
00010 int main(int /*argc*/, char** /*argv*/) {
00011     try {
00012         ImageFile imf("temp._e57", "w");
00013         StructureNode root = imf.root();
00014         
00015         root.set("greeting", StringNode(imf, "Hello world."));
00016 
00017         imf.close(); // don't forget to explicitly close the ImageFile
00018     } catch(E57Exception& ex) {
00019         ex.report(__FILE__, __LINE__, __FUNCTION__);
00020         return(-1);
00021     }
00022 
00023     try {
00024         ImageFile imf("temp._e57", "r");
00025         StructureNode root = imf.root();
00026 
00027         StringNode greeting(root.get("greeting"));
00028         cout << "Value of /greeting = " << greeting.value() << endl;
00029 
00030         imf.close(); // don't forget to explicitly close the ImageFile
00031     } catch(E57Exception& ex) {
00032         ex.report(__FILE__, __LINE__, __FUNCTION__);
00033         return(-1);
00034     }
00035     return(0);
00036 }

This example program writes a very simple E57 file with a single String element in it, then reopens the file for reading, and prints out the String value to the console. Note that this program does not attempt to create a complete E57 file according to the ASTM standard (which requires many elements to be defined with specific names). The numbering in the source listing is not quite sequential (e.g. lines 2 and 3 are missing). This is because some of the lines have documentation directives (for Doxygen) that don't appear in the listings.

Source line 4 includes the iostream header because the example uses cout. The API does not require iostream to be included (stdio.h could be used, or nothing at all). Source line 5 includes E57Foundation.h, the E57 Foundation API header file where all the API classes are declared. All programs that use the E57 Foundation API must include E57Foundation.h It is OK (but wasteful) to include the E57Foundation.h header twice in the same file, as the header has an include guard.

Source line 6 has a using directive which allows all the Foundation API classes and functions to be referenced without having to preceed each reference with e57:: (for example: e57::StructureNode root = imf.root();). The using namespace e57; directive must follow the inclusion of the E57Foundation.h header file. The entire API is declared in the "e57" namespace to eliminate the risk that in a large application a class name conflicts with an existing type. All preprocessor macros defined in E57Foundation.h (which cannot be in a namespace) begin with the prefix "E57", so there is low risk of name clashes. For convenience, the using directive on source line 7 allows all names in the std namespace to be used without qualification (e.g. cout instead of std::cout).

In the main function there are two try blocks, the first creates a new file on disk, and the second reads the file back. The try block is used because the API reports errors by throwing c++ exceptions rather than returning an error code from each function (see E57Exception for more details). On source line 10, the function argument names argc and argv are commented out so that the c++ compiler doesn't complain about unused variables when compiler warnings are enabled.

In the first try block, the variable imf is constructed on source line 12, with the file name to create on the disk and the "w" mode string that indicates that the file is to be written to. The scope of the first imf variable will end at the end of the first try block. It is a good idea to limit the scope of smart handles (such as the ImageFile object), as memory will not be reclaimed until all references to the underlying object are destroyed. The file name is "temp._e57" is used instead of "temp.e57", because the file won't be a complete, legal ASTM E57 Format file, it just exercises some of the primitive E57 element data types. If the file already exists on the disk, the old copy is deleted before a new, empty one is created.

The imf variable is a handle to the ImageFile object, which represents the entire contents of an E57 file. The state of an ImageFile is stored partially in memory and partially on the disk, so the disk file may grow as elements are added to the ImageFile. All the objects in the API are implemented as reference counted handles, which means that there is a level of indirection (using smart pointers). This means that you don't have to manipulate pointers to objects or use the new /delete operators. Also the handles are very small, so copying them (as an argument to a function call, for example) is very cheap. Thirdly, since the handle are "smart" (they do reference counting), you don't have to worry about freeing any objects when you are done with them. Operationally, this means you probably will never need to create a variable with type ImageFile* or ImageFile&.

An E57 file is conceptually a single hierarchical tree of elements. After the imf object is constructed, the tree is empty, with only the top-most root element defined. New elements are added to the ImageFile by attaching them into the tree as either children of the root element directly, or indirectly to children of elements that have been already attached into the tree. It is OK to temporarily have small trees that are not yet attached to an ImageFile. However it is recommended to limit the extent of these unattached sub-trees, as some of the API operations (that write large quantities of data) require that the object be attached to an ImageFile (e.g. writing to a BlobNode). It is also OK (but not recommended) to create E57 element nodes that are never attached to an ImageFile.

In source line 13, a handle to the predefined root element of the ImageFile is fetched and stored in variable root. The root element of an ImageFile is always a StructureNode and that is the type that ImageFile::root() returns.

In source line 15, a new StringNode element is created and attached as a child to the root StructureNode. There are three API calls (one is inserted silently by the compiler) in this line. First a StringNode is created with a value of "Hello world.". The first argument of the StringNode constructor in source line 15 is the handle to the ImageFile where the StringNode will eventually be attached. The StringNode constructor returns a handle, which is used in the call to StructureNode::set(). Since we don't need the StringNode handle for anything else, we don't have to save it in a variable, which would take another line of code.

The second function call in source line 15 is inserted automatically by the compiler. The actual argument passed to StructureNode::set in the second position is a StringNode type. But StructureNode::set is only defined to take generic Node handles in the second position. There is a function in the API that can safely convert from a StringNode handle to a generic Node handle. This function can automatically be applied by the C++ compiler. This process is called "upcasting", which saves a lot of code duplication (functions that can handle all eight types of Nodes don't need to be defined for each specific type).

The third function call in source line 15 attaches the new StringNode element as a child of the root StructureNode. The new child is given the element name "greeting", and after attachment to the ImageFile, the element has an absolute pathname of "/greeting". It would be an error if the root node already had a child with the element name "greeting", because the design of the API forbids any element from being set twice.

The statement on source line 17 explicitly closes the file on the disk and the ImageFile referred to by the handle imf enters the closed state. All data in memory is written into the disk file, and the file is closed. No further input/output operations are possible with the file after it is closed. It is important to explicitly close the file using the ImageFile::close() function rather than have it closed in the ImageFile destructor since errors are reported using exceptions, which are impossible to throw from a destructor in C++.

The catch statement at the end of the first try block will be invoked if an error occurs in one of the API function calls inside the try block. For example, an exception would occur if there was not enough disk space and the file could not be completely written. In this example program, the error is reported by printing a few lines of helpful explanation to the console, using the E57Exception::report function. The three arguments to the E57Exception::report function allow the position in the code where the exception is handled to be reported as well as where the exception was thrown. It is possible that calls to E57 Foundation API functions can produce exceptions other than E57Exception (for example: bad_alloc when there is insufficient memory). Production code would probably have more handlers for other classes of exceptions. A non-zero value is returned in source line 20 to indicate to the caller that the program did not succeed.

In the second try block, the file is reopened in read mode. This time the tree of elements in the ImageFile is not empty, since E57 elements have been read in from the disk file. In source line 25 the root element of the ImageFile is fetched.

In source line 27, two API calls are invoked to get a handle on the StringNode that was written in the first try block. The first obvious call is to StructureNode::get(const ustring&) const which fetches a handle of a child element of the root node. If root didn't have a child element named "greeting", an exception would be thrown. But since we just wrote the file in the first try block above, we can be sure that child element does exist. Another way to be sure that a child element exists with a certain name is if it is required by the ASTM standard. If the element is optional in the ASTM standard, then existence must be queried by using StructureNode::isDefined() before fetching with StructureNode::get(const ustring&) const.

The second, not so obvious, API call in source line 27 is to a type conversion: from a Node handle (returned by StructureNode::get(const ustring&) const) to a StringNode handle stored in the variable greeting. This conversion is required since a child of a StructureNode can be any E57 element type. Here again, we know that the node type must be StringNode since we wrote the file in the previous try block. In an E57 file, the type of a given element may be mandated. If the type of "greeting" was not StringNode, then the conversion would throw an exception to indicate an error condition. If the ASTM standard allows several types for a given element, then the actual type in the file may be queried using Node::type() before a type conversion is requested.

The value of the StringNode is printed to the console on source line 28. As on source line 17, the ImageFile is explicitly closed on source line 30. After the ImageFile is closed, no further I/O is possible with the imf handle.

The following console output is produced:

The XML section of the temp._e57 E57 file produced by this example program is as follows:

Here is the source code without line numbers to cut&paste from:

/*** HelloWorld.cpp example: the basics */
#include <iostream>
#include "E57Foundation.h"
using namespace e57;
using namespace std;

int main(int /*argc*/, char** /*argv*/) {
    try {
        ImageFile imf("temp._e57", "w");
        StructureNode root = imf.root();
        
        root.set("greeting", StringNode(imf, "Hello world."));

        imf.close(); // don't forget to explicitly close the ImageFile
    } catch(E57Exception& ex) {
        ex.report(__FILE__, __LINE__, __FUNCTION__);
        return(-1);
    }

    try {
        ImageFile imf("temp._e57", "r");
        StructureNode root = imf.root();

        StringNode greeting(root.get("greeting"));
        cout << "Value of /greeting = " << greeting.value() << endl;

        imf.close(); // don't forget to explicitly close the ImageFile
    } catch(E57Exception& ex) {
        ex.report(__FILE__, __LINE__, __FUNCTION__);
        return(-1);
    }
    return(0);
}
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Defines