Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

gSOAP & Web Services


The popularity of XML as the lingua franca of interoperability in many ways has changed how the software industry develops products and services. Closed proprietary standards for data formats have mostly given way to open standards based on XML. Specification bodies such as OASIS and the W3C continue to release XML-based standards for a variety of old and new application domains. Among those are the web services standards supporting the service-oriented architectures for application development and deployment in networked environments.

A critical part of any web services application is the serialization of application data in XML. But writing XML serializers by hand is not an easy task. Fortunately, tools are available that help you write the necessary code. In this article, I show how the open-source gSOAP Web Services Toolkit [1] can help serialize C/C++ data structures in XML with minimal coding effort. The toolkit includes a source-code generator that does most of the coding for you. The toolkit also includes a WSDL and XML schema analyzer to bind schema types directly to C/C++ data types that are intuitive to use.

The gSOAP Toolkit

Because coding XML serializers by hand is tedious, error prone, and difficult to maintain with emerging standards, the gSOAP toolkit grew out of a need for an effective method to implement XML serialization in C/C++. It did not make sense to develop a fixed set of XML libraries for the perpetually changing XML draft standards. Exploiting reflection mechanisms for serializing data would raise too many portability issues. Consequently, I decided to adopt a compiler-based code-generation approach to achieve a C/C++ programming-language binding for XML. This approach serves two important goals:

  • A code generator can be adjusted to emit new XML serialization code in compliance with updated W3C standards.
  • It relieves a programmer from the burden of coding SOAP/XML bindings so he/she can focus on application logic.

Immediately after the public release of SOAP 1.0 in 1999, I designed and implemented (with the help of one of my students) the first prototype of a compiler to generate C/C++ language bindings for SOAP/XML. We dubbed the system "gSOAP" (short for "SOAP generators"). Like many SOAP toolkits at the time, gSOAP only supported the SOAP RPC (remote procedure call) request/response message exchange pattern with the SOAP encoding style for serializing primitive types, structs/classes, arrays, and pointer-based structures in XML. Since then, advances in web services standards have required frequent updates to the system to generate code that meets the requirements for compliance with SOAP 1.1/1.2, SOAP RPC and document/literal style, SOAP with attachments, WSDL 1.1, XML schemas and namespaces, and WS best practices.

Typical Use

Serializing C/C++ data structures in XML with gSOAP is easy. In fact, it does not matter whether you use gSOAP to develop SOAP/ XML web services applications or to generate XML serialization code for other purposes. In practice, gSOAP is mostly used to develop web service applications starting from a service description in WSDL. This works as follows: To bind your application to a WSDL or set of XML schema definitions, you invoke the gSOAP WSDL importer on the WSDL to generate the bindings in C++ or ANSI C. The importer puts the bindings in a C/C++ header file. The gSOAP compiler then takes these bindings and generates XML serializers and service proxies in source-code format; see Figure 1.

Figure 1: The gSOAP WSDL importer produces a service interface definition that is parsed by the gSOAP compiler to generate the proxies and serializers in source-code format.


The intermediate header file specifies the bindings in an intuitive syntax. Primitive XML types such as built-in XSD types and XML schema simpleTypes are mapped to primitive C/C++ data types, and compound XML schema complexTypes are mapped to structs (for C) or classes (for C++). SOAP service operations are mapped to function prototype declarations to implement the service proxies. This intermediate header file is essentially used as an interface definition language with a familiar syntax.

For example, suppose you want to develop a client for the XMethods Temperature Service, which returns the current temperature (in Fahrenheit) for a U.S. zip code region. First you invoke the gSOAP WSDL importer from the command line on the WSDL to generate the bindings:

wsdl2h -o temp.h 
  http://www.xmethods.net/sd/2001/TemperatureService.wsdl

This generates the temp.h file with C++ bindings. To generate C bindings, use the -c option. The generated file is self-documenting and code documentation tools such as Doxygen can produce a set of nicely formatted documents describing the details of the service.

Listing One is the generated temp.h file, where I've removed the nonessential inline documentation. The first thing to notice are the //gsoap comments — directives for the gSOAP compiler. This means that you don't need to worry about the SOAP and XML details such as encoding styles, schema namespaces, and SOAP actions because these are automatically incorporated by the gSOAP compiler in the generated codes to ensure interoperability with the SOAP/XML web service. The service provides a single operation, ns1__getTemp, with one string input parameter zipcode. Because this is a SOAP RPC encoded service, the float output parameter, return_, is defined in a SOAP RPC response struct, ns1__getTempResponse. By convention, all function parameters of the service operation are input parameters except the last, which is a wrapper struct that stores the output parameters returned by the service.

Listing One

//gsoap ns1 service name: TemperatureBinding
//gsoap ns1 service port: 
// http://services.xmethods.net:80/soap/servlet/rpcrouter
//gsoap ns1 service namespace: urn:xmethods-Temperature
//gsoap ns1 service method-style: getTemp rpc
//gsoap ns1 service method-encoding: 
// getTemp http://schemas.xmlsoap.org/soap/encoding/
//gsoap ns1 service method-action: getTemp ""
int ns1__getTemp(
   std::string zipcode,
   struct ns1__getTempResponse& );
struct ns1__getTempResponse
{  float return_;
};

Next, you invoke the gSOAP compiler from the command line to generate the proxy and XML serialization routines in source-code format:

soapcpp2 temp.h

The compiler generates a C++ client proxy declared in soapTemperatureBindingProxy.h. The compiler also generates the XML serialization code needed to marshal the input parameter and demarshal the output parameter. Also generated are sample SOAP request and response messages and a namespace mapping table TemperatureBinding.nsmap needed by the gSOAP engine to bind XML namespace prefixes such as ns1 to the namespace URLs of the XML schemas. Listing Two is the client program that prints the temperature reading for a given zip code.

Listing Two

#include "soapTemperatureBindingProxy.h" // get proxy class
#include "TemperatureBinding.nsmap" // get XMLns mapping
main()
{  // create a proxy object
   TemperatureBinding proxy;
   // and an object to store the result
   ns1__getTempResponse result;
   // invoke the ns1__getTemp service operation
   if (proxy.ns1__getTemp("32309", result) == SOAP_OK)
      cout << "Temperature=" << result.return_ << "F" << endl;
}

The name of the TemperatureBinding proxy is inherited from the service name defined by the bindings in Listing One. The proxy has one method, ns1__getTemp. Its signature is declared in the header file specification as a global function prototype (to support C as well as C++). The proxy method takes a zip code, sends the SOAP/XML request message with the zip code to the service, parses the response message from the service, and demarshals the return value into the response struct. The method returns SOAP_OK when successful. To complete the build of the example client program you need to compile and link the generated files soapClient.cpp and soapC.cpp, and the gSOAP runtime engine stdsoap2.cpp that comes with the gSOAP distribution package.

This simple example demonstrated the import of a WSDL and the subsequent steps in the code-generation process to build a fully functional client program. The process is the same for larger real-world web services.

XML Serialization

If you plan on developing your own web services but don't have a WSDL to start with, you can use the gSOAP compiler directly to generate the proxies, serialization code, and a WSDL document to advertise your service. If you have an existing C/C++ program and want to expose it as a web service, you may want to start by defining the program's data types to be serialized in a separate C/C++ header file for processing by the gSOAP compiler, then add the set of function prototypes of the intended web-service operations to the header file. The header file is parsed by the gSOAP compiler to generate the proxies and serializers for the specified data types and a WSDL that bundles the information; see Figure 2. Thus, in contrast to the previous development steps, you omit the WSDL import stage and start with the specification of the operations and data structures in the intermediate header file. The following data types can be declared for serialization:

Figure 2:The header file is parsed by the gSOAP compiler to generate proxies and serializers, and a WSDL that bundles the information.


  • Primitive types are serialized by value. This includes all primitive numeric C types and bool, size_t, time_t, char*, wchar_t*, std::string, and std::wstring.
  • Enumerations. Enumeration constants are serialized by name.
  • Structs. The fields of a struct are recursively serialized.
  • Classes. Public data members are recursively serialized. To support single inheritance, all class definitions are augmented by the gSOAP compiler with virtual serialization methods.
  • Pointers. The target object of a nonNULL pointer is serialized. This includes arbitrary graph structures. Also, pointers to dynamic arrays and void* are serialized (under certain conditions).
  • STL containers std::set, std::list, std::vector, and std::deque.
  • Templates with a single typename parameter are serialized. Templates are assumed to be STL-like sequences (forward containers) and must define begin, end, clear, and insert methods and should define an iterator.
  • Fixed-size arrays.
  • Typedefs.

The gSOAP compiler accepts unions, but they cannot be serialized due to the lack of a union discriminator. Other STL types not listed here and templates with more than one typename parameter cannot be used in the header file specification. To serialize the sequence of values of a dynamic array pointed to by a pointer field in a struct or a class, the runtime size information of the array must be accessible by the gSOAP engine. To provide this information, you simply need to add one or more int __sizeX fields. The field is placed directly preceding the pointer field:

class MyClass
{ public:
   int __sizeOfVals;	// holds the array size
   float *vals;   	// points to an array of floats
   ...
};

STL containers provide a cleaner alternative to the construct shown previously. But when you are coding in C, or if you can't use STL, there is no other alternative.

Transient Types

The keywords extern and volatile are reserved and have a special meaning. Entire type declarations or specific struct fields and class members qualified as extern are considered transient and won't be serialized. As mentioned earlier, public data members of a class are always serialized, unless you explicitly indicate otherwise. In some cases, you may want to prevent the serialization of a public data member without changing its access permissions. To do this, you need to qualify it as extern. For example:

class MyClass
{ public:
   extern int secret; // secret is public, but don't serialize!
   ...
};

Entire nonserializable data types can be declared extern. This enables the gSOAP compiler to parse the header file without complaining about undefined types. For example, to include a public iostream data member in a class, you declare the iostream type extern without further details (that is, the details of its definition are external and of no concern to gSOAP):

extern class iostream;
class MyClass
{  ...
   iostream stream; // has transient type; not serialized 
   ...
};

If you hadn't declared the iostream type extern, then gSOAP couldn't compile MyClass without generating compile errors.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.