Dressing It Up
To illustrate extensibility based on inheritance, add another type of object to the collision service. Changing the code requires recompilation of the service program. It also affects the WSDL, because a new WSDL is generated with the new type. The new type of object appears as a schema extension of the base Point
type. The neat thing about XML schema extensibility is that client programs developed with the old WSDL will still talk to the new service.
Listing Nine shows the new Circle
type derived from Point
. >Listing Ten shows the method code for Circle
. You override the radius
method to return the actual radius of the circle. The data member r
is public so it can be serialized. To include the new circle object in the collection, import circle.h in service.h to make its definition available to the gSOAP compiler.
Listing Nine
// file: circle.h #import "point.h" class Circle: public Point { public: float r; virtual float radius() const; Circle(); Circle(float,float,float); };
Listing Ten
// file: circle.cpp #include "soapH.h" Circle::Circle() : Point(), r(1.0) {} Circle::Circle(float x, float y, float r) : Point(x, y), r(r) {} float Circle::radius() const { return r; }
Developing a gSOAP Client
Listing Eleven is a C++ client program for the service. The client is directly developed from the service.h header file specification of the CollisionDetection
web service. I could have used the generated WSDL and run it through the gSOAP WSDL importer. However, the WSDL notation does not preserve the method definitions of the Point
and Circle
classes, because only the serializable members are part of the generated WSDL schema type definitions.
Listing Eleven
// file: client.cpp #include "soapCollisionServiceProxy.h" #include "CollisionService.nsmap" main() { // create a proxy CollisionService proxy; // create two points and a circle Point p, q(1.0, 1.0); Circle c(1.0, 1.0, 2.0); // create a collection with the objects Objects collection; collection.objects.push_back(&p); collection.objects.push_back(&q); collection.objects.push_back(&c); // compute the hits remotely and print the count int hits; if (proxy.cws__detect_collisions(collection, hits) == SOAP_OK) cout << "Hits=" << hits << endl; else soap_print_fault(proxy.soap, stderr); }
The client prints a fault message upon failure. The failure can be due to connection issues or application-related issues such as an empty collection, which the service explicitly prohibits by returning a sender-side fault (see Listing Eight).
Saving and Retrieving XML Data
Suppose you want to retrieve a collection of geometric objects from a file or stream instead of hard coding it in the client program (Listing Eleven). You do this by using the XML serializers directly. For every nontransient type declared in the intermediate header file specification, gSOAP generates a serializer and deserializer. Let X
be the name of a primitive type, enum
, struct
, or typedef
, then the serializers and deserializers for type X
are:
void soap_serialize_X(struct soap*, const X *obj);
int soap_out_X(struct soap*, const char *tag, int id, const X *obj, const char *xsitype);
X *soap_in_X(struct soap*, const char *tag, X *obj, const char *xsitype);
For a class X
, gSOAP generates serialization and deserialization methods:
void X::soap_serialize(struct soap*) const;
int X::soap_out(struct soap*, const char *tag, int id, const char *xsitype) const;
void *X::soap_in(struct soap*, const char *tag, const char *xsitype);
The soap_serialize
functions and methods ensure that pointer-based object graphs are serialized in a format that preserves the logical graph structure. Coreferenced data is serialized with XML id-refs
. This enables the serialization of arbitrary object graphs without loss of structural integrity. The soap_serialize
function or method is called before soap_out
. The soap_out
function or method emits the data in XML. The soap_in
function or method parses XML and returns a pointer to the newly instantiated data. Both functions/methods accept an XML tag name and an optional xsi
type value referring to the qualified XML schema typename.
To read a collection of objects from a file, add this code to the client program:
proxy.soap->is = new ifstream("data.xml"); // bind gSOAP runtime to an istream if (proxy.soap->is == NULL || soap_begin_recv(proxy.soap) != SOAP_OK // start reading || collection.soap_in(proxy.soap, "objects", NULL) == NULL || soap_end_recv(proxy.soap) != SOAP_OK) // stop reading exit(1); proxy.soap->is->close(); delete proxy.soap->is;
The proxy object is managed by a gSOAP runtime context, proxy.soap. The context is used to parse and populate the collection from a file and to manage its memory allocation and deallocation.
Memory Management
Memory management is crucial. The gSOAP runtime context manages the lifetime of an object from allocation and instantiation to destruction. Objects managed by a proxy's runtime context are automatically deallocated when the proxy object is destroyed. At the server side, the destruction of objects and temporary data is explicitly performed with the soap_destroy
and soap_end
calls; see Listing Eight. The soap_malloc
function allocates a temporary string in the service operation, which is deallocated by soap_end
after message serialization.
Conclusion
The gSOAP Web Services Toolkit serializes almost any type of C/C++ data directly to and from XML. This feature of the C/C++ language binding to XML, combined with the powerful gSOAP WSDL importer, lets you rapidly develop and deploy web services in C or C++.
References
[1] http://gsoap2.sourceforge.net/.
Robert van Engelen is an associate professor in the Department of Computer Science at Florida State University. He is also president of Genivia Inc., a company dedicated to the research and development of XML products and services. He can be contacted at [email protected] or at [email protected].