Serializing Nonserializable Types
When a particular data type is defined elsewhere (such as in a library's header file) and its definition cannot be moved into the gSOAP header without duplicating it, you need to qualify it as volatile
. The volatile
qualification of a data type ensures that the type is not redefined in the gSOAP compiler's output. The gSOAP compiler generates new header files soapStub.h and soapH.h in which all of the data types defined in the gSOAP header file specification are copied, except types qualified as volatile
. If you wouldn't qualify a predefined data type volatile
, compiling the generated source code would result in a redefinition error.
In fact, the volatile
qualifier lets you serialize predefined types that would otherwise not be serializable (that is, should otherwise be declared extern
). For example, suppose you have a class with a public struct
tm
member, as in:
class MyClass { public: struct tm created; // oops, how to serialize? ... };
The tm
struct is declared in <time.h> and its definition may vary from one platform to another. To serialize the member, you define volatile
struct
tm
in the header file with a selection of fields you want serialized:
volatile struct tm { int tm_sec; // seconds (0 - 60) int tm_min; // minutes (0 - 59) int tm_hour; // hours (0 - 23) int tm_mday; // day of month (1 - 31) int tm_mon; // month of year (0 - 11) int tm_year; // year - 1900 int tm_wday; // day of week (Sunday = 0) int tm_yday; // day of year (0 - 365) int tm_isdst; // is summer time in effect? char* tm_zone; // abbreviation of timezone name long tm_gmtoff; // offset from UTC in seconds };
Another use of volatile
can be found for classes. Classes are augmented with virtual serialization methods by the gSOAP compiler (they're redefined with additional methods in the generated soapStub.h file). While this approach provides a powerful mechanism to serialize polymorphic objects, there may be situations in which it is undesirable to change a class definition. The role of the volatile
qualifier is to prevent class redefinition and to keep the original definition intact.
Developing a Web Service
With this basic understanding of XML serialization with gSOAP, take a closer look at developing a web service application with gSOAP. The goal here is to implement an object collision-detection service that takes a collection of geometric objects and determines how many collide with each other on a 2D plane.
Of course, you can implement a collision-detection algorithm locally, but for the sake of exposition, assume you want to expose a collision detector as a service. This example illustrates the use of primitive types, class hierarchies, containers, and operations to send and receive the objects. To keep the system open ended, I define the objects in an extensible class hierarchy starting with the simplest form, a point in 2D space declared in point.h in Listing Three. A Point
object has a serializable x- y-coordinate pair and a virtual radius method to describe the total size of the object in terms of a simple circular shape. The virtual distance method measures the geometric distance from another object's center point. Because the gSOAP compiler does not parse method code, all method implementations must be provided in a separate source-code file point.cpp in Listing Four.
Listing Three
// file: point.h class Point { public: float x, y; Point(); Point(float, float); virtual float radius() const; virtual float distance(const Point&) const; };
Listing Four
// file: point.cpp #include "soapH.h" Point::Point() : x(0.0), y(0.0) {} Point::Point(float x, float y) : x(x), y(y) {} // the radius of a point is always 0.0 float Point::radius() const { return 0.0; } // compute the distance to another point float Point::distance(const Point& p) const { return sqrt((x-p.x)*(x-p.x)+(y-p.y)*(y-p.y)); }
You also need a container to hold the objects. Since Point
is the base class of the hierarchy, you will use an STL vector of pointers to Point
to store and serialize geometric objects. I've added a collisions method to compute the number of collisions among objects in the container and an empty method. Listing Five is the declaration of the Objects
class with the method's source code in Listing Six.
Listing Five
// file: objects.h #import "point.h" // import the point definitions #import "stl.h" // need STL (stl.h is part of gSOAP's package) class Objects { public: std::vector<Point*> object; Objects(); int collisions() const; bool empty() const; };
Listing Six
// file: objects.cpp #include "soapH.h" Objects::Objects() : object() {} // compute the number of collisions int Objects::collisions() const { int hits = 0; for (std::vector<Point*>::const_iterator i = object.begin(); i != object.end(); ++i) for (std::vector<Point*>::const_iterator j = i; j != object.end(); ++j) if (*i != NULL && *j != NULL && *i != *j && (*i)->distance(**j) <= (*i)->radius() + (*j)->radius()) hits++; return hits; } // empty collection? bool empty() const { return object.empty(); }
Objects.h imports the point.h and predefined stl.h definitions. The #import
directive imports a file into the current header file. Any occurrences of #include
and #define
are deferred until C/C++ source code compilation after code generation with the gSOAP compiler. Listing Seven shows the service specification. The service operation cws__detect_collisions
takes a collection of objects as input and sets the integer hits parameter. The return value of an operation is always int
(SOAP_OK
or error code). The cws__
prefix binds the C-style operation to the name and its endpoint port via the //gsoap
cws
service annotations.
Listing Seven
// file: service.h #import "objects.h" // import the object collection // define a name for the 'cws' service //gsoap cws service name: CollisionService // define the endpoint location of the service //gsoap cws service port: http://localhost:8080 // define the 'cws' service operation int cws__detect_collisions(Objects objects, int& hits);
Listing Eight is the complete source code for the implementation of the service. The service runs as a standalone sequential web service accepting SOAP requests at port 8080. The soap_serve
function is generated by gSOAP. It parses the SOAP/XML request and dispatches the request to the service operation cws__detect_collisions
. The service can be multithreaded and incorporate SSL encryption and/or Zlib compression by adding a few lines of code. (The details are available in the gSOAP documentation. Or, you can use the CGI, Fast-CGI, Apache mod, and IIS interfaces.) gSOAP supports document/literal style by default. If you're not familiar with the differences in encoding styles, don't worry because gSOAP generates a WSDL CollisionService.wsdl from this specification with the necessary details.
Listing Eight
// file: service.cpp #include "soapH.h" #include "CollisionService.nsmap" main() { // set up a gSOAP runtime context struct soap *soap = soap_new(); // bind to port 8080 (service endpoint) soap_bind(soap, NULL, 8080, 100); // server loop for (;;) { // accept request soap_accept(soap); if (soap->error != SOAP_OK) { soap_print_fault(soap, stderr); break; } // dispatch the client request if (soap_serve(soap) != SOAP_OK) soap_print_fault(soap, stderr); // remove class instances soap_destroy(soap); // clean up soap_end(soap); } // detach gSOAP context soap_done(soap); // dealloc gSOAP context free(soap); } // service operation int cws__detect_collisions(struct soap *soap, Objects objects, int& hits) { if (objects.empty()) { char *msg = soap_malloc(soap, 256); sprintf(msg, "Empty collection from IP=%d.%d.%d.%d", (int)(soap->ip>>24)&0xFF, (int)(soap->ip>>16)&0xFF, (int)(soap->ip>>8)&0xFF, (int)soap->ip&0xFF); return soap_sender_fault(soap, msg, NULL); } hits = objects.collisions(); return SOAP_OK; }