LMX
W3C XML Schema to C++ Data Binding

"LMX is working like a charm"

Home
Have a question? E-mail
LMX XSD XML C++ Binding Overview  Overview  LMX C++ XML XSD Code Generator Quick Intro  Walkthrough  Download LMX XML C++ Data Binding  Download  LMX C++ XML Data Binding Try On-line  Try On-line  LMX Documentation XSD XML C++ Data Binding  Documentation  Buy LMX XML C++ Data Binding  Buy  LMX XML XSD C++ Binding Support  Support 

Bookmark with: Bookmark XML C++ data binding explained on Delicious Bookmark XML C++ data binding explained on Digg Bookmark XML C++ data binding explained on reddit Bookmark C%2b%2b XML data binding explained on Facebook Bookmark XML C++ data binding explained on StumbleUpon

C++ XML Data Binding Explained

This guide explains what C++ XML data binding is, how to use it and how it can help you speed up code development and reduce the potential for bugs.

Note: This guide to C++ XML data binding uses Codalogic's LMX XML C++ Databinder for it's examples, but the principles and concepts described here are common to XML data binding using other languages, such as Java, C# and C. Therefore, for those looking for a more general overview of XML data binding, where we refer to the C++ language, you should be able to substitute your language of choice, such as Java, C# and so on.

What is C++ XML data binding?

Data binding is the process of transferring data from one representation to another, usually in an automated way. For example, in Windows programming, GUI controls can use data binding to load their data from a data base.

In the case of XML data binding with C++ the data is being exchanged between instances of XML documents (which could be in files or in memory) and C++ objects. Specifically in the case of XML data binding, the process of converting XML into programming language objects (e.g. C++ objects) is called 'unmarshalling' and the process of converting programming language objects to XML is called 'marshalling'. The terms 'de-serializing' and 'serializing' are sometimes used in these contexts also.

How does C++ XML data binding work?

XML C++ Binding Concept In C++ XML data binding the C++ objects that the XML data is marshaled to and from are instances of application specific C++ classes. In other words, a set of C++ classes are created that mirror the structure of the XML data. These C++ classes are generated automatically using tools such as Codalogic's LMX XML C++ Databinder during the code development process.

To generate the C++ classes, LMX needs to know the valid set of XML data that the C++ code will be required to handle. To do this, LMX uses a W3C XML Schema (also known as an XSD) description of the valid set of XML data. From this, Codalogic LMX can generate the C++ classes (see diagram). The generated C++ classes also contain all the logic needed to convert C++ object instances to XML, and to convert input XML to a set of C++ objects.

As part of this process the generated C++ code checks that the input XML conforms to the specified range of valid XML input as described by the XML Schema Description (XSD) from which the C++ code was generated.

Referring to the diagram once more, your code can then access the XML data by interfacing to the C++ objects that are instantiated from the generated C++ classes.

Advantages of using C++ XML data binding

We will dig in more depth into how C++ XML data binding works later, but already from the above brief description it's possible to see some of the many benefits of using XML data binding with C++.

For example, your code doesn't need detailed knowledge of XML. Also, programmers on the development team don't need XML knowledge because they can interface to the data using standard C++ class interfaces that they already understand how to do. (Codalogic LMX XML C++ Databinder actually includes a number of features that help developers interface to the generated C++ code. This includes generation of an HTML documentation file, comments in the generated C++ .h file, and, in the case of the Windows WinLMX version, a dialog that allows dragging-and-dropping code snippets into your code.)

And with a number of software development IDEs, because the IDE understands the generated C++ code, you are able to use technology such as Intellisense to speed up the entry of C++ code.

Additionally, the amount of C++ code you have to write is generally less, and more self-documenting than other ways of accessing XML data further easing the software development process.

Hence XML data binding with C++ allows you to operate at a higher level of abstraction, which speeds up the code development process. It also means that more members of your programming team can access the data as they don't need detailed knowledge of XML technology in addition to their C++ skills.

Another benefit of using C++ XML data binding is that, because data is accessed via custom generated methods that are named specific to the XML being accessed, as opposed to using generic methods that have the names of desired elements as string parameters, the C++ compiler is able to do a lot of compile-time checking to ensure that the code you write is correct.

This compile-time checking greatly reduces the potential for accidently introducing bugs into your code, which, for example, can so easily happen as a result of cut-and-paste style errors that so readily happen when code has a large number of very similar code segments. Without these compile-time checks such bugs are only found at run-time, either as a result on extensive and costly testing, or, worse still, by your product's users.

Hence, C++ XML data binding enables you to more readily detect bugs, and also detect them very early in the code development process. Not only does this aspect of C++ XML data binding help reduce the number of bugs in your code, it also helps speed up the code development process.

In summary, the key benefits of using XML data binding with C++ are speeding up code development, and reducing bugs. Both of these gains help you reduce the cost of developing your product, helping you maximize the return on your investment.

Finally, because the generated C++ code is customized to the specific XML data used by your application, the performance is usual better than more general solutions.

When to use C++ XML data binding

C++ XML data binding is a tool and even we have to admit that, like all tools, there are jobs that C++ XML data binding is suited for, and those which it is not.

One of the key things about XML data binding with C++ is that it hides the details of the underlying XML. Therefore, if you want intimate detail about the structure of XML documents, such as information on XML comments and processing instructions, then XML data binding is likely not appropriate. For example, if you are transforming one XML document into another XML document, then there are more appropriate technologies available, such as XSLT. However, if XML is mainly a means to an end, then XML data binding is likely to be ideal.

XML data binding does require an XSD XML schema in order to generate the C++ code. Hence the XML documents' structure does have to be quite rigorously defined. If your XML documents are rather ill-defined, then you may find it difficult applying XML data binding to them. That said, we would recommend using an explicitly defined XML document structure where possible, and you may find that defining an XSD XML schema for your XML structure can be a very useful design exercise which gives structure to your XML data, and may potentially reduce interworking issues.

While XML data binding is useful when looking at XML tags in sequential document order, XML data binding really excels when you want random access to the data and you want to access the data in an order different to that in the original XML document.

This makes XML data binding ideal for machine-to-machine, and process-to-process communication such as communication protocols and web services. Similarly XML data binding is ideal for persisting program data in the same way that Microsoft Word persists Word documents. Similarly, XML data binding is great for storing application configuration information, such as user preferences. Such information can be read into objects at the start of the program, interrogated and manipulated as the program runs, and then saved at program shutdown.

When assessing XML data binding, it may be worth bearing in mind that your application may have a number of uses for XML, and different XML uses may be best served by different techniques. For example, just because part of your project needs more detailed access to XML data than XML data binding provides (such as XML comments and processing instructions), that does not mean that you should rule out XML data binding for the other parts of the project that could benefit from using XML data binding, as this may be the most efficient and expedient technique to handle those particular tasks. In such a scenario adopting XML data binding where it is appropriate may speed up implementation of those particular tasks, allowing you more time to focus on other parts of your project.

Finally, while discussing the suitability of XML data binding using C++ for your task, it's worth mentioning that not all XML data binding solutions are alike. For example, a number of C++ XML data binding solutions build an XML DOM-tree (using libraries such as Xerces) and then generate wrapper objects that interrogate the DOM-tree when a method is called to access a particular XML data item. Others first build a DOM-tree, and then build a C++ object structure from the data in the DOM-tree, discarding the DOM-tree once the C++ object structure is created. Using DOM in this way is inefficient. For a start generic schema-aware DOM parsers are generally large pieces of software, introducing undesirable software bloat into your product. Also, DOM-trees typically consume a large amount of run-time memory, increasing the resources your product needs to run. Accessing XML data via DOM methods can also require relatively large numbers of CPU cycles.

LMX does not use a DOM parser. Instead it has its own light-weight pull-parser. This allows the XML data to be streamed straight into C++ objects without using any intermediate form, increasing efficiency both in memory use and CPU cycles. The LMX XML pull-parser can be light weight because the generated code implements the constraints defined in the XSD XML schema, and therefore the LMX XML pull-parser does not have to be XSD schema aware.

Thus when deciding whether XML data binding is appropriate for your application, it's also worth considering the type of XML data binding technology you want to use.

C++ XML data binding in-depth

The top-level principles of XML data binding with C++ have been described above, along with some of its advantages. This section looks into C++ XML data binding in more detail, by working through an example from concept to running code.

You can either simply read through this example, or, if you prefer, you can download a zip file specific to this example. This doesn't include a copy of LMX XML C++ Databinder, but it does contain pre-generated versions of the C++ code. It also contains the XML Schema for the example (which is presented below) and some example XML instances of the schema.

If you decide to download the zip file, we would recommend downloading a copy of Codalogic LMX XML C++ Databinder so you can generate your own copies of the C++ code from the XML schema. To do that, you can either:

The XML data binding scenario

In order to keep the example short, the example is a little contrived! The scenario is that a number of software development project teams are available to undertake development work. Each team has a name and a number of members. Each member of the team also has a name, and also a property called BrainPower which records how smart they are. The program to be developed will sum together the entire brain power of the team, and using that result a project manager can decide whether this or another team should be assigned to a particular project that requires a particular total brain power.

The XML schema for the example

As mentioned earlier, in common with most XML data binding tools, Codalogic LMX XML C++ Databinder generates code based on the contents of an XML Schema (sometimes called an XSD or XSD file). For this scenario, the following XML schema has been defined:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns="http://codalogic.com/schemas/team.xsd"
        targetNamespace="http://codalogic.com/schemas/team.xsd"
        elementFormDefault="qualified">

    <xs:element name="ProjectTeam">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="TeamName" type="xs:string"/>
                <xs:element name="Member" type="TeamMember" 
                            minOccurs="0" maxOccurs="unbounded"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="TeamMember">
        <xs:sequence>
            <xs:element name="Name" type="xs:string"/>
            <xs:element name="BrainPower" type="xs:unsignedInt"/>
        </xs:sequence>
    </xs:complexType>

</xs:schema>
This XML schema defines an outer element called ProjectTeam, which contains an Element called TeamName and multiple Member elements. Each member element has a Name for the member and a BrainPower value. (If you are new to XML schema or need a refresher, you may find our XSD Schema Overview useful.)

An example XML instance of this schema is as follows:

<ProjectTeam xmlns="http://codalogic.com/schemas/team.xsd">
    <TeamName>Skywalkers</TeamName>
    <Member>
        <Name>Anakin</Name>
        <BrainPower>115</BrainPower>
    </Member>
    <Member>
        <Name>Luke</Name>
        <BrainPower>108</BrainPower>
    </Member>
    <Member>
        <Name>Leia</Name>
        <BrainPower>128</BrainPower>
    </Member>
</ProjectTeam>
As you can see, this project team is called Skywalkers, and has three members; Anakin, Luke and Leia. Their combined brain power is 351.

Generating C++ code from the XSD schema

Now we have an XML schema, we need to generate the corresponding C++ code; the job of Codalogic LMX XML C++ Databinder. Before actually demonstrating how to generate the code using LMX it is appropriate to mention that there are a number of different executable versions of LMX.

On the Microsoft Windows platform there are two versions. lmx.exe is a command-line version, and winlmx.exe (referred to as WinLMX) is a GUI version. WinLMX allows interactive C++ code generation, whereas as the command-line C++ code generator is useful in scripts, such as you might find in automated builds.

On the Linux platform there are also two versions. linmx is the command-line C++ code generator, and glinmx is the GUI based version.

We'll cover how to generate code with each of these versions, starting with WinLMX.

As mentioned above, WinLMX is the Windows GUI C++ code generator version. When first started up, it looks as follows:

WinLMX XML C++ data binding code generator view

Glinmx, the Linux GUI version, functions much the same as WinLMX. It looks as follows:

Glinmx XML C++ data binding code generator view

One of the simplest ways to generate C++ code using WinLMX is to locate the XML schema file in the Windows File Explorer and drag and drop it onto an instance of WinLMX, or even a shortcut on your desktop. If you do that, WinLMX will end up looking something like:

WinLMX XML data binding with C++ code generator after drag-drop

Notice that the name of the XML schema file has been put in the "Base Schema File:" text box, and the "Output File Base Name:" text box has been automatically filled in with the name of the XML schema file, minus the .xsd file extension.

This is the minimum amount of information WinLMX or any of the other LMX versions need to generate C++ code from an XML schema. The need for the name of the XML schema file in the "Base Schema File:" text box should hopefully be obvious. The "Output File Base Name:" text box is used to specify the base name of the C++ and HTML files that LMX will generate. In the basic case LMX generates a .h C++ header file, a .cpp C++ file and an HTML documentation file. For example, in the case shown, LMX would generate files called c:\Pete\Projects\team-example\test.h, c:\Pete\Projects\team-example\test.cpp and c:\Pete\Projects\team-example\test.html.

In the above case the code for the entire XSD schema is placed in the same .h and .cpp C++ files. LMX supports two other C++ code generator modes, these being: generating a separate .h and .cpp C++ file for each complex type in the schema, and, in the case where there is more than one schema to be compiled at a time, one .cpp C++ file for each XSD file, along with a single .h file.

LMX can also generate a C++ file that can be used to test the generated C++ code. This will have -main.cpp appended to the output base name, and in the example above this file would be called c:\Pete\Projects\team-example\test-main.cpp and would include a main() function and example code to read in an XML instance, unmarshal (or de-serialize) it into C++ objects, and then marshal it out to a separate XML file. Generating this test file is a good way to get a program working quickly so that you can experiment with how to use the generated C++ code.

LMX also allows you to setup and store LMX projects, which can later be re-opened when you want to re-generate your C++ code, perhaps after changing the XML schema. This works particularly well in WinLMX and Glinmx because they maintain a list of recently used projects, making it very quick to re-generate your code.

When LMX project files are used, relative file path names are relative to the name of the LMX project file. Thus, when an LMX project file is used, its useful to edit the base schema name and output file base name text boxes so that they don't include the full path. If you do this, the WinLMX window looks something like:

WinLMX C++ XML data binding code generator after project save

To configure the core parts of WinLMX, you can just flip through the tabs that appear in the main window. If you do this, you'll see the basic options (where the previously mentioned option to generate the test C++ file appears as "Generate Test Framework File"):

WinLMX C++ XML data binding code generator basic options

The advanced options:

WinLMX XML data binding C++ code generator advanced options

and the naming options:

WinLMX XML C++ data binding code generator namespace options

As you can see, there are quite a number of options that LMX support. To make setting up new projects easier, WinLMX and Glinmx allow you to set up an LMX project and save this as your default project. Then when you start up WinLMX or Glinmx, or do File->New, these settings will be loaded. To save your default LMX project settings, do File->Save As Default Settings as shown here:

WinLMX XML C++ data binding code generator save default menu

Now that the project has been setup, to generate your C++ code, simply press the big Compile... button at the bottom of the window. When the code has been generated (which should be very fast with such a small example), WinLMX will change to display the compilation results similar to the following:

WinLMX XML data binding C++ code generator compilation results

As mentioned above, invoking code generation on this project will generate the files test.h, test.cpp and test.html in the same folder as the LMX project file (in this case called test.lmxprj; the .lmxprj extension being the default extension for an LMX project).

To get similar results using the command-line versions of the LMX code generator, assuming appropriate PATHs had been setup, in the case of Windows you would do:

        lmx test.xsd test
and in the case of Linux you would do:
        linmx test.xsd test
The command-line versions can also use the LMX project files, so if you have a project file already created, perhaps from using WinLMX or Glinmx, you can do:
        lmx test.lmxprj
or:
        linmx test.lmxprj
While discussing the command-line forms, configuration flags to both versions use the Unix - character to mark a flag, rather than using the DOS / character. Hence to instruct command-line versions of LMX to generate the test C++ file mentioned above, you would do:
        lmx -tframework test.xsd test
and in the case of Linux you would do:
        linmx -tframework test.xsd test
Note that once you have more the one argument to an LMX command-line executable, it won't assume that a name ending in .lmxprj is a project file. Therefore if you want to invoke the -tframework option in addition to the settings specified in an LMX project file, you would need to do: lmx -tframework -f test.lmxprj or linmx -tframework -f test.lmxprj.

Before leaving this section, it is important to mention that, while the C++ code has been generated on either a Windows or Linux platform, the generated C++ code is cross-platform, and should run successfully on any platform that has a standard compliant C++ compiler.

Inspecting the generated C++ code

Now we have generated the C++ code, let's have a closer look at it. You can use the links below to show an example of the generated C++ .h file and C++ .cpp file. If you are generating your own C++ code while following this example you may find your C++ code differs in some of the details to that shown here, however it should be sufficiently similar for the following discussion to be valid.

In the remainder of this section we will highlight key parts of the generated C++ .h file and explain their significance. We will not discuss the generated C++ .cpp file in any great detail in this example because it does not have a direct bearing on how you use the generated C++ code.

Show Generated .h File

Show Generated .cpp File

The first thing to notice in the generated C++ .h file is that there are two classes defined, one for c_ProjectTeam and one for c_TeamMember, as in:

class c_ProjectTeam
{
private:
    // Element(s)
    lmx::tlmx_unicode_string m_TeamName;
    bool            present_TeamName;   // Required
    std::vector< c_TeamMember * > m_Member;
...

and:

class c_TeamMember
{
private:
    // Element(s)
    lmx::tlmx_unicode_string m_Name;
    bool            present_Name;   // Required
    lmx::tlmx_uns32 m_BrainPower;
    bool            present_BrainPower; // Required
...

As you might expect, c_ProjectTeam corresponds to the ProjectTeam complex type defined in the XML schema, and c_TeamMember corresponds to the TeamMember complex type defined in the XML schema.

In fact, within the .h file c_TeamMember is defined before c_ProjectTeam. This is because LMX has recognized that c_ProjectTeam depends on c_TeamMember and has generated them in an appropriate order to accommodate this.

Both of the classes have a similar structure. In the above snippets you can see the data members for each class. These mirror the elements declared in the XML schema. You will also see that there are additional data members recording whether some of the primary data members are present. These are used during the unmarshalling to validate that the XML instance corresponds to that described by the XML schema.

Moving down the c_ProjectTeam class, you will see a number of contructors and a destructor, for example:

    LMX_GDECL c_ProjectTeam();
    LMX_GDECL c_ProjectTeam( const c_ProjectTeam &ar_rhs );
    LMX_GDECL c_ProjectTeam & operator =( const c_ProjectTeam &ar_rhs );
    LMX_GDECL void swap( c_ProjectTeam &ar_rhs );
    // Convenience constructors
    LMX_GDECL c_ProjectTeam( const char ac_file_name[], lmx::elmx_error *ap_error );
    #ifdef _MSC_VER
        LMX_GDECL c_ProjectTeam( const wchar_t ac_file_name[], lmx::elmx_error *ap_error );
    #endif
    LMX_GDECL c_ProjectTeam( const char *ap_memory, size_t a_memory_size, lmx::elmx_error *ap_error );
    LMX_GDECL c_ProjectTeam( const std::string &ar_string, lmx::elmx_error *ap_error );
    LMX_GDECL virtual ~c_ProjectTeam();
All generated classes have a standard set of default constructor, copy constructor, a copy operator, and a swap method.

Because c_ProjectTeam is an outer element, and thus a potential XML document element, it also has what we call convenience constructors. These allow construction of a C++ class instance from an XML instance in, say, a file or block of memory. They are particularly useful when constructing const instances of a class from an XML instance. For example, you can do:

    lmx::elmx_error error;
    const c_ProjectTeam my_object( "my_file.xml", &error );

You may have noticed the next to the constructor declarations. These macros can be expanded to appropriate Microsoft __declspecs. On non-Windows platforms the instances of LMX_GDECL can be ignored.

Further down the file we have the accessor methods for accessing the data stored in the class, for example:

    // Element(s)
    //    TeamName --> xs:string
    LMX_GDECL const lmx::tlmx_unicode_string & get_TeamName() const;
    LMX_GDECL lmx::elmx_error set_TeamName( const lmx::tlmx_unicode_string & a );

    //    Member --> TeamMember[0..*]
    LMX_GDECL const c_TeamMember & get_Member( size_t a_index ) const; // For read access
    LMX_GDECL lmx::elmx_error append_Member();               // For write access
    LMX_GDECL c_TeamMember & back_Member();                  // For write access
    LMX_GDECL lmx::elmx_error insert_Member( size_t a_index ); // For write access
    LMX_GDECL c_TeamMember & get_Member( size_t a_index );   // For read/write access
    LMX_GDECL void  delete_Member( size_t a_index );
    LMX_GDECL void  clear_Member();
    LMX_GDECL c_TeamMember & assign_Member( size_t a_index, const c_TeamMember & a ); // Deep copy
    LMX_GDECL size_t size_Member() const;

Firstly, notice the comments that appear before each block of method declarations. These capture in an abbreviated form the name of the XML schema element (or attribute) the methods correspond to, and their type. They also show how many instances of the element (or attribute) are required for the XML isntance to be valid. These comments make writing your code easier as it removes the need to look at the XML schema, or even the generated HTML documentation.

The methods for setting and getting the TeamName are quite straight forward. You will notice that the type used by these methods is lmx::tlmx_unicode_string. This can be typedefed to std::wstring, std::string, or, with possibly a little more work, to any other string type you prefer to use. Swapping between using std::string and std::wstring is particularly easy, and can be done by setting a #define in your project setup.

The accessor methods for Member are a bit more involved. This is mainly because there can be more than one instance of Member and so there are methods to insert and extract instances Member. If not all of these operations are required, the LMX code generator supports a flag that causes only a minimal set of methods to be generated (although often an optimizing compiler will not generate code for methods that are not used).

Once again, as c_ProjectTeam is an outer element its C++ class also has a number of convenience marshalling and unmarshalling methods. Similar to the constructor convenience methods, they allow easy serialization to and from XML instances stored in files and in memory.

    // Convenience marshal/unmarshal functions
    LMX_GDECL lmx::elmx_error marshal( const char ac_file_name[] ) const;
    #if defined( _MSC_VER ) && _MSC_VER >= 1400
        LMX_GDECL lmx::elmx_error marshal( const wchar_t ac_file_name[] ) const;
    #endif
    LMX_GDECL lmx::elmx_error marshal( std::string *ap_string ) const;
    LMX_GDECL lmx::elmx_error marshal( std::ostream &ar_sos ) const;
    LMX_GDECL lmx::elmx_error unmarshal( const char ac_file_name[] );
    #ifdef _MSC_VER
        LMX_GDECL lmx::elmx_error unmarshal( const wchar_t ac_file_name[] );
    #endif
    LMX_GDECL lmx::elmx_error unmarshal( const char *ap_memory, size_t a_memory_size );
    LMX_GDECL lmx::elmx_error unmarshal( const std::string &ar_string )
    {
        return unmarshal( ar_string.data(), ar_string.size() );
    }

For example, they allow marshalling an object to XML simply by doing:

    my_object.marshal( "my_file.xml" );

Towards the end of the class are defined the marshal and unmarshal methods that are common to all generated classes, for example:

    // General marshal/unmarshal functions
    LMX_GDECL lmx::elmx_error marshal( lmx::c_xml_writer &ar_writer, const char *ap_name = "ProjectTeam" ) const;
    LMX_GDECL lmx::elmx_error unmarshal( lmx::c_xml_reader &ar_reader );
    LMX_GDECL lmx::elmx_error unmarshal( lmx::c_xml_reader &ar_reader, const std::string &ar_name );
    LMX_GDECL void reset();

These take instances of lmx::c_xml_writer and lmx::c_xml_reader as their arguments, which store the internal marshalling and unmarshalling state. These do the real marshalling and unmarshalling work. You may find that you need to use these methods if you have more complex C++ XML data binding requirements than the more common cases.

Another method worth noting is:

    LMX_GDECL bool is_occurs_ok() const;
This will return true if the C++ object has the right number of members (for example the team name is present), and false otherwise. This is useful when checking that your class is ready to marshal.

There are a number of other private and protected methods declared in the class, but they are not particularly relevant here.

Before we level this section we will just briefly point out some of the things in the generated C++ .cpp file. The first thing to notice is that the generated assignment operators use an exception safe idiom, for example:

c_ProjectTeam & c_ProjectTeam::operator = ( const c_ProjectTeam & ar_rhs )
{   
    c_ProjectTeam l_temp( ar_rhs );
    swap( l_temp );
    return *this;
}
This is an example of the generated C++ code being designed to be hard exception safe.

Also, in this section of code for reading the BrainPower integer value (slightly reformatted from the original), notice the checks that are made to ensure that the value is valid. For example, you can see that the value to checked to see it is a valid integer, and that it's value is within any appropriate range:

if( ar_reader.get_current_event() == e_1000_BrainPower )
{   
    bool l_is_empty_element;
    if( ar_reader.get_simple_type_value( *ap_name, lmx::EXWS_COLLAPSE, 
            &ar_reader.value, ap_error, &l_is_empty_element ) )
    {   
        if( ! ( lmx::is_valid_integer( ar_reader.value ) ) && 
                (*ap_error = ar_reader.handle_error( 
                     lmx::ELMX_VALUE_BAD_FORMAT, *ap_name, __FILE__, __LINE__ )) != lmx::ELMX_OK )
            return false;
        if( ! ( ar_reader.value >= limit_7_min ) && 
                (*ap_error = ar_reader.handle_error( 
                    lmx::ELMX_VALUE_EXCEEDS_MIN, *ap_name, __FILE__, __LINE__ )) != lmx::ELMX_OK )
            return false;
        if( ! ( ar_reader.value <= limit_7_max ) && 
                (*ap_error = ar_reader.handle_error( 
                    lmx::ELMX_VALUE_EXCEEDS_MAX, *ap_name, __FILE__, __LINE__ )) != lmx::ELMX_OK )
            return false;
        lmx::v_to_o( m_BrainPower, ar_reader.value );
        present_BrainPower = true;
    }
Further, note the calls to ar_reader.handle_error() when an error is detected. This method allows you to customize how errors are handled. For example, you can cause the method to simply return an error code (the default case), throw an exception, or maybe even ignore the error in certain circumstances.

Worth mentioning also is that ar_reader.handle_error() is a very handy place to put a breakpoint when debugging your XML.

Using the generated C++ code in a project

Now we've generated the C++ code, we need to add it to a project. Much as the appropriate method for generating C++ code depends on whether we are running under Windows or Linux, how we use the generated C++ code also depends on whether we are operating under Windows or Linux. Equally, you may choose to run the generated C++ code on another platform. In the latter case we hope that you will be able to use the information we present here to build your project.

To use the generated C++ in a project, in common with other XML data binding tools, it is important to be aware that there are two sets to the code that are relevant to the C++ XML data binding operation. The first set of code is the C++ code that was generated in the above section. This is custom generated and varies from project to project. The second part of code is common to all projects. In the case of Codalogic LMX XML C++ Databinder, this code includes the XML parser, a set of C++ classes representing some of the more complex XML schema types, and various other bits and pieces such as string conversion classes. In the case of Codalogic LMX XML C++ Databinder, we call the latter set the "Supporting Software".

An aside about LMX Editions

We mentioned earlier that LMX is available in a number of Editions. It's probably worth a little digression to explain what these Editions are. In addition to the free Express Edition mention earlier, there is also a Standard Edition and a Professional Edition. The difference between the Standard Edition and the Professional Edition is down to the form in which the Supporting Software is supplied. In the Standard Edition the Support Software is supplied in binary form; specifically Microsoft Visual Studio Windows and x86 GCC Linux binaries. For the Professional Edition, the Supporting Software is available in C++ source code form. Thus when speaking about the Professional Edition, we often speak about the Supporting Software Source Code. Having the Professional Edition gives you access to all of the run-time source code necessary to port the C++ XML data binding operation to any C++ compliant platform.

By contrast, the free Express Edition is a stripped down version of the Standard Edition. Or more precisely, an Express Edition license file disables many of the features that would be enabled if a Standard or Professional Edition license file were used. Also, while the Standard Edition includes 64-bit binaries, 64-bit binaries are not included with the free Express Edition.

Incidentally, we would recommend having the Professional Edition even if you are running on the Windows or Linux platforms that we support in binary form. This gives you complete visibility of the code that is running in your project allowing you to do code inspections and customizations if necessary, and also makes debugging easier as there are no 'black holes' to go into while tracing through code.

Now back to our C++ XML data binding example

Codalogic XML C++ data binding project files In the example's zip file, in addition to the XML schema for the example, we have also put some Visual Studio project files (teamvc8.sln for VS 2005 and teamvc9.sln for VS 2008), and also a makefile (called makefile.gcc) suitable for building using GCC.

The screenshot to the right shows the file structure of one of the projects loaded into Visual Studio. Now we are building some running code, there are three sets of code that are relevant; the two sets of code mentioned above that are part of the LMX XML C++ data binding solution, plus the code that actually uses it.

In the screenshot, team-example.cpp is the C++ code that actually uses the xml data binding. team.h and team.cpp are the C++ files generated by Codalogic LMX XML C++ Databinder. The C++ header files and the .lib file in the 'LMX Support Files' section are part of the LMX XML C++ Databinder Supporting Software files. (The .lib file is one of many different types of library and DLL file that Codalogic LMX XML C++ Databinder ships with.) (Note that in the Visual Studio projects, the Debug non-DLL Multi-thread LMX library is used in order to reduce the number of files that need to be included in the zip file.)

It should be pointed out that normally the .lib file would be specified via the project's project settings dialog. We've chosen to display it as part of the project file structure so that its presence is more visible.

We've already looked at team.h, and mentioned team.cpp in the sections above. We now need to show you the contents of team-example.cpp so that you can see how team.h and team.cpp are used. The contents of team-example.cpp is as follows:

#include <iostream>

#include "team.h"

int main( int argc, char *argv[] )
{
    const char *xml_file = "team.xml";

    if( argc >= 2 )
        xml_file = argv[1];

    lmx::elmx_error error;
    c_ProjectTeam project_team( xml_file, &error );

    if( error != lmx::ELMX_OK )
    {
        std::cout << "Unable to unmarshal\n";
        LMX_OUTPUT_DEBUG_ERROR( std::cout );    // Will only output debug info in debug mode
        return 1;
    }

    unsigned int total_brain_power = 0;
    for( size_t i=0; i < project_team.size_Member(); ++i )
        total_brain_power += project_team.get_Member( i ).get_BrainPower();

    std::cout <<
            "The total brain power for project team " <<
            lmx::convert_to_narrow( project_team.get_TeamName() ) <<
            " is " << total_brain_power << "\n";

    #ifdef _MSC_VER     // Allow for operation inside IDE
    std::cout << "----Press <Return> to continue----\n";
    std::cin.get();
    #endif

    return 0;
}
As mentioned earlier, ignoring any error checking, the code opens an XML files specified on the command line, sums the brain power of all the team members and prints the result to std::cout.

Looking at the file in more detail, after including iostream to enable console I/O, we have:

#include "team.h"
This is the generated C++ header file. When using the convenience methods (as opposed to the more advanced marshalling and unmarshalling methods), this is the only C++ XML data binding related header file that needs to be included in the code, which makes code easy to setup.

After performing some checking whether the command-line includes a file name argument, an XML instance is unmarshalled using the selected file name, as in:

    lmx::elmx_error error;
    c_ProjectTeam project_team( xml_file, &error );
As you can see, this is a very simple operation, at the end of which the project_team C++ object will be populated with the data from the XML instance.

If, after unmarshalling the XML instance, you didn't want to change the contents of project_team you could unmarshal to a const instance of project_team, for example, by changing the second line to:

    const c_ProjectTeam project_team( xml_file, &error );

The error variable retrieves any error code if there are problems with the unmarshalling operation. The next section of code shows an example of how that error code can be used:

    if( error != lmx::ELMX_OK )
    {
        std::cout << "Unable to unmarshal\n";
        LMX_OUTPUT_DEBUG_ERROR( std::cout );    // Will only output debug info in debug mode
        return 1;
    }
Note that if you prefer, you can configure LMX to throw a C++ exception if an error occurs, rather than returning an error code.

The convenience methods that we are using here are intentionally light-weight in the amount of error information they return. To help debugging, in Debug mode, by default, a global instance of a debug error structure called debug_error is populated when an error occurs. In Debug mode the LMX_OUTPUT_DEBUG_ERROR() macro will output this debug information to the specified stream. In Release mode it will do nothing. The addition of macros like this help speed up your code development and also help reduce the clutter associated with adding debug code to your project.

The next task is to sum the brain power for the entire team. This is done using the following code:

    unsigned int total_brain_power = 0;
    for( size_t i=0; i < project_team.size_Member(); ++i )
        total_brain_power += project_team.get_Member( i ).get_BrainPower();
As there are multiple instances of Member within project_team, project_team.size_Member() is used to return how many instances there are, and project_team.get_Member( i ) returns a C++ reference to the zero based i-th instance. The get_BrainPower() method can then be called on this reference, and the brain power value is returned. Note that get_BrainPower() returns an actual C++ integer value and not a string containing the text version of an integer. Hence you do not to do a manual conversion from text to integer in your code, which again speeds up code writing.

You can see here that we are using application-specific named methods to access the data we want. The advantage of this is that if you type the wrong name into the code, a compile-time error will occur and you can fix it quickly. This is highly favorable to using generic methods that take the name of the desired elements and attributes as string parameters. If such string parameters are typed wrong, the error is only detected at run-time, which, with code that has a number of execution paths may only be detected a long time after the code is initially entered. The LMX XML data binding with C++ approach therefore significantly reduces the potential for introducing such accidental bugs.

And, depending on the IDE you are using, another benefit is that IDE features such as intelliSense and automatic code completion can be used, making it not only easier to find out what child element and attribute methods are accessible for your current complex type, but also speeding up code entry.

When the total team brain power is calculated, it is printed to the screen. This is done using the following lines:

    std::cout <<
            "The total brain power for project team " <<
            lmx::convert_to_narrow( project_team.get_TeamName() ) <<
            " is " << total_brain_power << "\n";
Note the lmx::convert_to_narrow(). LMX is fully Unicode compliant, and out-of-the-box string values are returned as instances of std::wstring. However, LMX can be easily configured to return strings as instances of std::string. This can be done by setting an appropriate C++ #define in your project. We've chosen not to do this in this example because we wanted to keep the configuration side as transparent as possible for you. There are however, many such things that can be configured in LMX.

That's really it for this C++ XML data binding example. The lines that follow just give you an opportunity to see the output generated by the program when running inside an IDE.

If you want to experiment further, you can add the following lines after the result of the brain power calculation is output to std::cout.

    project_team.append_Member();
    project_team.back_Member().set_Name( L"Obi Wan" );
    project_team.back_Member().set_BrainPower( 200 );
            
    std::cout << "Revised XML:\n";
    if( project_team.marshal( std::cout ) != lmx::ELMX_OK )
    {
        std::cout << "Unable to marshal revised XML\n";
        LMX_OUTPUT_DEBUG_ERROR( std::cout );    // Will only output debug info in debug mode
        return 2;
    }
The first three lines add a new member to the project_team. The first of these actually adds a new c_TeamMember instance to the project_team collection. The following two set the name and brain power of the member using the project_team's back_Member() method which returns a reference to the last member in the collection in much the same way that C++ STL containers back() methods do.

The project_team.marshal( std::cout ) line marshals the project team object to std::cout. The lines that follow just check that the operation was successful.

Concluding the C++ XML data binding example

That just about concludes our XML data binding with C++ example. We've gone into a lot of depth with the intention of giving you a detailed understanding of how XML data binding works. However, when all is done, we hope that you can see that XML data binding with C++ is very simple to use. C++ code generation can be as simple as dragging a file over an application and pressing a 'Complile...' button, marshalling and unmarshalling XML instances can be as simple as one line of code, and getting and setting data is very simple and intuitive. This all makes using C++ XML data binding quick and easy to use.

The approach of using application-specific named methods also dramatically reduces the potential for introducing accidental coding bugs by allowing the C++ compiler to do more checking at compile-time, rather than, hopefully, detecting such errors at run-time.

Further exploring C++ XML data binding

Codalogic LMX XML C++ Databinder has many features, such a the ability to augment the generated classes with your own code, the ability to customize the names of methods, the ability to set and manage C++ and XML namespaces, the ability to customize string handling plus other aspects of XML to C++ type mapping, and many more.

To further explore XML data binding with C++ further we would suggest downloading the evaluation version of Codalogic LMX XML C++ Databinder. This can be done from the download page. If you enter your e-mail address on the download page we can then send you a 30-day evaluation license that enables full functionality.

Or you could obtain a free Express Edition license. You can obtain one of these from the LMX purchase page.

You can also download our basic C++ XML binding example, or our XML C++ SOAP data binding example.

Another option, if you have a small schema to experiment with, is to try Codalogic LMX XML C++ code generation on-line.

Lastly, if you have any questions about Codalogic LMX XML C++ Databinder, or C++ XML data binding in general, we are here to help. Just send us an e-amil at:



LMX XSD XML C++ Binding Overview  Overview  LMX C++ XML XSD Code Generator Quick Intro  Walkthrough  Download LMX XML C++ Data Binding  Download  LMX C++ XML Data Binding Try On-line  Try On-line  LMX Documentation XSD XML C++ Data Binding  Documentation  Buy LMX XML C++ Data Binding  Buy  LMX XML XSD C++ Binding Support  Support 
Copyright © 2003-2014, Codalogic Ltd. All Rights Reserved.