Accessing vSphere SOAP APIs from C++

Thanks to some projects I am doing at work these days I got to play with my original love: C++. More specifically I’m catching up with how to properly write C++ application servers and micro-services in 2018. Part of the the toy project I’m playing with needs to talk to a vCenter server via it’s trusted old SOAP SDK. There is no standard vSphere Web Service client library for C++ so I figured I’ll build the necessary bindings myself using gSOAP. There are a number of articles around the Web and on the VMware community forums but I had issues trying any of these out – either because of my lack knowledge on how to troubleshoot the different issues or because the articles were relying on a different version of gSOAP.

Why C++?

I’ve enjoyed the idea of using C++ for the projects I normally work on for some time. I work on a huge code-base that probably will outlive it’s creators. It’s extremely important to make sure that no bug resurfaces that should have been fixed five years ago. Even in a statically compiled language such as Java you still need to maintain a huge chunk of unit tests for things that the C++ compiler catches. Java has the additional problem of not being a good fit for a micro-service architecture that is deployed in customer’s data-centers – the JVMs eat memory like a soldering gun swallows its solder. I’m also trying to evaluate the potential of C++ for building reactive or event-driven systems. One last reason for using the language is the introduction of a new architecture for writing plugins to the vSphere Client – which we called Remote Plugin Architecture. In this model for extending the vSphere UI the web interface of the plugin can be served directly from the server providing the business APIs. From time to time such application servers are written in C++ and need the ability to talk to the vCenter APIs to implement the plugin functionality.

The State of C++

Things have been pretty exciting with the C++ community in the last 7 years – with the advent of the C++11 standard but in my mind it’s only after Satya Nadella took the reins of Microsoft that a serious push to bring the language forward becomes apparent.

Together with Microsoft’s switch to C++/CX as the default language for the WinRT platform came not one but two package managers that support C++ – first NuGet, and now vcpkg. A lot of interesting other open-source projects were initiated by the company. One of them – the REST SDK for C++ – will be the subject of another post.

Still, things do not look as exciting for less-hyped technologies such as SOAP-based web services. Given that Microsoft kicked off .Net in 2002 with strong emphasis on SOAP you’d expect them to have some kind of solution for native C++ but I didn’t found one. Apache supported C++ with Axis and Axis 2 but the current download locations for these projects are empty. Looks like the tool of choice for the majority of projects today is gSOAP. It is dual-licensed under GPLv2 and a commercial license so keep that under consideration. It generates client and server stubs from WSDL definitions, can output both C and C++ code (including C++11), and is relatively easy to use.

Obtaining gSOAP

There are two paths I experienced so far – going through the package manager and manually downloading the gSOAP deliverable. I’ll start with the first option as it keeps the entropy at bay.

On Fedora 29 the install was as easy as doing:

$ sudo dnf install gsoap gsoap-devel

Once the installer completes you should find the shared libraries available:

$ ldconfig -p | grep gsoap
libgsoapssl-2.8.60.so (libc6,x86-64) => /lib64/libgsoapssl-2.8.60.so
libgsoapssl++-2.8.60.so (libc6,x86-64) => /lib64/libgsoapssl++-2.8.60.so
libgsoapck-2.8.60.so (libc6,x86-64) => /lib64/libgsoapck-2.8.60.so
libgsoapck++-2.8.60.so (libc6,x86-64) => /lib64/libgsoapck++-2.8.60.so
libgsoap-2.8.60.so (libc6,x86-64) => /lib64/libgsoap-2.8.60.so
libgsoap++-2.8.60.so (libc6,x86-64) => /lib64/libgsoap++-2.8.60.so

gSOAP is ready for use and linked into your vSphere-integrated solutions.

Getting the vSphere WSDL Definitions

The API definitions for the vCenter SOAP APIs are available as part of the vSphere Management SDK. You can whatever version of the SDK you want. The sample below can work with all versions of vSphere currently supported by VMware.

Once you download the SDK the WSDL definitions can be found under SDK/vsphere-ws/wsdl. Let’s use ~/vim++/ as our work the directory.
Extract the files under ~/vim++/wsdl.

Generating our bindings

Code generation happens in two stages. First we generated an annotated C++ header from the WSDL definition. This is done using:

$ mkdir bindings
$ wsdl2h -c++11 -O4 -o bindings/vim25.hpp wsdl/vim.wsdl

If everything goes right the output should resemble the one below:

Saving bindings/vim25.hpp
** The gSOAP WSDL/WADL/XSD processor for C and C++, wsdl2h release 2.8.60
** Copyright (C) 2000-2018 Robert van Engelen, Genivia Inc.
** All Rights Reserved. This product is provided "as is", without any warranty.
** The wsdl2h tool and its generated software are released under the GPL.
** ----------------------------------------------------------------------------
** A commercial use license is available from Genivia Inc., contact@genivia.com
** ----------------------------------------------------------------------------
Reading type definitions from type map "/usr/local/share/gsoap/WS/typemap.dat"
Reading 'vim25/vim.wsdl'…
Reading schema 'vim25/reflect-messagetypes.xsd'…
Reading schema 'vim25/core-types.xsd'…
Done reading 'vim25/core-types.xsd'
Reading schema 'vim25/reflect-types.xsd'…
Done reading 'vim25/reflect-types.xsd'
Done reading 'reflect-messagetypes.xsd'
Reading schema 'vim25/query-messagetypes.xsd'…
Reading schema 'vim25/core-types.xsd'…
Done reading 'vim25/core-types.xsd'
Reading schema 'vim25/query-types.xsd'…
Done reading 'vim25/query-types.xsd'
Done reading 'query-messagetypes.xsd'
Reading schema 'vim25/core-types.xsd'…
Done reading 'core-types.xsd'
Reading schema 'vim25/vim-messagetypes.xsd'…
Reading schema 'vim25/reflect-types.xsd'…
Done reading 'vim25/reflect-types.xsd'
Reading schema 'vim25/vim-types.xsd'…
Done reading 'vim25/vim-types.xsd'
Done reading 'vim-messagetypes.xsd'
Done reading 'vim25/vim.wsdl'
Optimization (-O4): removed 1998 definitions of unused schema components (38.8%)
Warning: option -O4 removed type definitions that may be used as xsd__anyType derivatives by type extension inheritance (enabled by default with option -p), use option -P to disable type derivation from xsd__anyType
To finalize code generation, execute:
soapcpp2 bindings/vim25.hpp
Or to generate C++ proxy and service classes:
soapcpp2 -j bindings/vim25.hpp

The tool should have created the following file under ~/vimxx/bindings:

vim25.hpp11 MBAnnotated header capturing all of the service definition from the original WSDL

Note the file sizes above. This is for vSphere 6.7 SDK and may differ if a different version of the WSDL is used.

The size is also affected by the -O4 option used with wsdl2h. This is an optimization that strips all pieces from the WSDL and the associated schemas that are not in used by the service for which we generate bindings. You can read more about the schema slicing optimizations in the following article.

Now it is time to generate the client bindings and all the logic for serializing and deserializing objects over SOAP. Do the following from ~/vimxx:

$ soapcpp2 -Cjrx -c++11 -pvim25 -dbindings bindings/vim25.hpp

What this command is telling the tool is to:

  • C: Generate client bindings-only.
  • -j: Generate C++ proxy class (see the docs about the difference between -j and -i).
  • -r: Generate an output report in the .md format.
  • -x: Do not generate sample SOAP messages. I found no reason for them to exist in my case.
  • -pvim25: Prefix all generated file names with vim25 – of course there would be some exceptions to this rule.
  • dbindings: use ~/vimxx/bindings as the output directory for the generated files.

You should see something akin to the following in the terminal:

 **  The gSOAP code generator for C and C++, soapcpp2 release 2.8.60
** Copyright (C) 2000-2018, Robert van Engelen, Genivia Inc.
** All Rights Reserved. This product is provided "as is", without any warranty.
** The soapcpp2 tool and its generated software are released under the GPL.
** ----------------------------------------------------------------------------
** A commercial use license is available from Genivia Inc., contact@genivia.com
** ----------------------------------------------------------------------------
bindings/vim25.hpp(535): WARNING: identifier 'ns1__XmlToCustomizationSpecItemRequestType' starts with or embeds 'Xml' character sequence, which is exclusively reserved for and by the W3C XML standards (for enum constants: please ignore this warning)
bindings/vim25.hpp(7288): WARNING: identifier '_ns1__XmlToCustomizationSpecItemResponse' starts with or embeds 'Xml' character sequence, which is exclusively reserved for and by the W3C XML standards (for enum constants: please ignore this warning)
bindings/vim25.hpp(47277): WARNING: identifier 'xml' starts with or embeds 'xml' character sequence, which is exclusively reserved for and by the W3C XML standards (for enum constants: please ignore this warning)
bindings/vim25.hpp(99951): WARNING: identifier '__ns1__XmlToCustomizationSpecItem' starts with or embeds 'Xml' character sequence, which is exclusively reserved for and by the W3C XML standards (for enum constants: please ignore this warning)
bindings/vim25.hpp(99952): WARNING: identifier 'ns1__XmlToCustomizationSpecItem' starts with or embeds 'Xml' character sequence, which is exclusively reserved for and by the W3C XML standards (for enum constants: please ignore this warning)
bindings/vim25.hpp(99953): WARNING: identifier 'ns1__XmlToCustomizationSpecItemResponse' starts with or embeds 'Xml' character sequence, which is exclusively reserved for and by the W3C XML standards (for enum constants: please ignore this warning)
Using project directory path: bindings/
Saving bindings/vim25Readme.md report
Saving bindings/vim25Stub.h annotated copy of the source interface file
Saving bindings/vim25H.h serialization functions to #include in projects
Using ns1 service name: VimBinding
Using ns1 service style: document
Using ns1 service encoding: literal
Using ns1 schema namespace: urn:vim25
Saving bindings/vim25VimBindingProxy.h client proxy class
Saving bindings/vim25VimBindingProxy.cpp client proxy class
Saving bindings/VimBinding.nsmap namespace mapping table
Saving bindings/vim25C.cpp serialization functions
Compilation successful (6 warnings)

You should find the following new files under ~/vimxx/bindings:

vim25C.cpp36 MBSOAP Serialization / Deserialization function implementations
vim25H.h19 MB
SOAP Serialization / Deserialization function declarations
vim25Readme.md21 MBReport about the generated binding. Added since the soapcpp2 tool was invoked with the -r option.
vim25Stub.h
11 MBAnnotated copy of vim25.hpp.
vim25VimBindingProxy.cpp1.8 MBC++ Proxy object implementation.
vim25VimBindingProxy.h513 KBC++ Proxy object declaration.
VimBinding.nsmap648 BIn actuality a C/C++ header holding an array of used namespaces. Needs to be included in any compilation using the gSOAP bindings.

Again the sizes above may differ if a different WSDL version is used.

Making Our First Call

We have everything we need to talk to vCenter over SOAP. So let’s doooo it! First, a little bit of preparation. We need to set up a naïve SSL context since a very simple tutorial can justify us being oblivious about all things security.

In a file called ~/vimxx/test.cpp type the following code:

#include "bindings/VimBinding.nsmap"
#include "bindings/vim25VimBindingProxy.h"

using namespace std;

int main() {
    // Proxy object from which all remote calls will be invoked.
    VimBindingProxy vim("https://vcenter.example.com/sdk");

    // Initialize OpenSSL
    soap_ssl_init();
    auto res = soap_ssl_client_context(
            vim.soap,
            SOAP_SSL_NO_AUTHENTICATION,
            nullptr,
            nullptr,
            nullptr,
            nullptr,
            nullptr);
    if (res) {
        cerr << "SSL: ";
        // Dump the last error from gSOAP.
        soap_print_fault(vim.soap, stderr);
        return 1;
    }

    cout << "Success!" << endl;
    return 0;
}

Now it’s time to compile:

$ g++ -o test test.cpp bindings/vim25VimBindingProxy.cpp vim25C.cpp -lgsoapssl++ -lssl -lcrypto

Compilation will take a few minutes given the sheer size of the generated bindings and will spit out a number of notices. In the end you should be able to run the resulting executable:

$ ./test
Success!

We’re ready to move to the next step: Making our first call. The first thing we need to do is get the list of base vCenter Managed Objects a.k.a. the service content. To do that we add the following lines after the OpenSSL initialization:

	ns1__ManagedObjectReference moref;
	moref.__item = "ServiceInstance";
	moref.type = new string("ServiceInstance");
	
	ns1__RetrieveServiceContentRequestType retrieveServiceContentReq;
	retrieveServiceContentReq._USCOREthis = &moref;
	_ns1__RetrieveServiceContentResponse retrieveServiceContentResp;

	res = vim.RetrieveServiceContent(
			&amp;retrieveServiceContentReq,
			retrieveServiceContentResp);
	if (res != SOAP_OK) {
		soap_print_fault(vim.soap, stderr);
		return 1;
	}
	cout << "RetrieveServiceContent - OK" << endl;

	ns1__ServiceContent sc = *retrieveServiceContentResp.returnval;

Notice all generated types and methods are prefixed with ns1__ or _ns1__. This is the default naming convention of gSOAP and is aimed at distinguishing WSDL namespaces when bindings for multiple services are used. The mapping between these prefixes and the actual XML namespaces is stored in ~/vimxx/bindings/VimBinding.nsmap:

#include "stdsoap2.h"
/* This defines the global XML namespaces[] table to #include and compile */
SOAP_NMAC struct Namespace namespaces[] = {
        {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/*/soap-envelope", NULL},
        {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", "http://www.w3.org/*/soap-encoding", NULL},
        {"xsi", "http://www.w3.org/2001/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance", NULL},
        {"xsd", "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/*/XMLSchema", NULL},
        {"ns1", "urn:vim25", NULL, NULL},
        {NULL, NULL, NULL, NULL}
    };

Now let’s go thropugh the code. Lines 27 through 29 hand-craft the managed object reference a.k.a. the ID of the root managed object in the vCenter API. Since the object has one and the same ID we don’t need to figure out what its name is.

ns1__ManagedObjectReference moref;
moref.__item = "ServiceInstance";
moref.type = new string("ServiceInstance");

In the terms of the vCenter API model ServiceInstance object has a single method that can be invoked – RetrieveServiceContent() with no parameters. In SOAP this is modeled as an operation accepting a inbound message that has a single field _this accepting the ManagedObjectReference constructed above, and an outbound message holding managed object references to all global singleton managed objects.

How this is reflected in the generated gSOAP bindings? On line 31 we create a new instance of ns1__RetrieveServiceContentRequestType. On line 32 we set the _this parameter – generated as _USCOREthis by gSOAP for reasons I am yet to comprehend – to point to the service instance MORef.

	ns1__RetrieveServiceContentRequestType retrieveServiceContentReq;
	retrieveServiceContentReq._USCOREthis = &moref;

On the next line 33 we create an output variable for the operation. What is left is to make the actual remote call on lines 35 to 37:

	res = vim.RetrieveServiceContent(
			&amp;retrieveServiceContentReq,
			retrieveServiceContentResp);

Again, I am yet to figure out why the request is passed by pointer while the response is passed by reference. I guess it is possible to pass a nullptr for the request?

Skipping the banalities of error handling, we reach line 44 where we store the service content for further use:

ns1__ServiceContent sc = *retrieveServiceContentResp.returnval;

Now Let’s Do Something More Interesting

Anybody in IT can second it: if it’s interesting it is secured and requires authentication. We need to use the SessionManager.Login() API. Continuing from where we left off:

	ns1__LoginRequestType loginReq;
	loginReq._USCOREthis = sc.sessionManager;
	loginReq.userName = "user@example.com";
	loginReq.password = "pass1234";
	_ns1__LoginResponse loginResp;

	res = vim.Login(&amp;loginReq, loginResp);
	if (res != SOAP_OK) {
		soap_print_fault(vim.soap, stderr);
		return 1;
	}
	cout << "Login - OK" << endl;

As you can see the same pattern is followed as with invoking ServiceContent.RetrieveServiceContent(). There are two differences. First, the request parameter has more data, namely the user credentials (lines 48 and 49). Second, we don’t hand-craft the MORef of the SessionManager but instead pass the value obtained with the service content (line 47).

That’s it. Every subsequent API call will pass the authentication cookie returned by the server. Let’s try it out. We are going to query the ExtensionManager using it’s FindExtension() method to find one of the built-in vCenter extensions – the Update Manager service:

	ns1__FindExtensionRequestType findExtensionReq;
	findExtensionReq._USCOREthis = sc.extensionManager;
	findExtensionReq.extensionKey = "com.vmware.vcIntegrity";
	_ns1__FindExtensionResponse findExtensionResp;

	res = vim.FindExtension(&amp;findExtensionReq, findExtensionResp);
	if (res != SOAP_OK) {
		soap_print_fault(vim.soap, stderr);
		return 1;
	}
	cout << "FindExtension - OK" << endl;

	ns1__Extension ext = *findExtensionResp.returnval;
	cout << ext.server[0]->url << endl;

Finally we should be good citizens and log out from vCenter:

ns1__LogoutRequestType logoutReq;
logoutReq._USCOREthis = sc.sessionManager;
_ns1__LogoutResponse logoutResp;

res = vim.Logout(&amp;logoutReq, logoutResp);
if (res != SOAP_OK) {
    soap_print_fault(vim.soap, stderr);
    return 1;
}
cout << "Logout - OK" << endl;

That’s all. After you recompile and run the code you should see the following output:

$ test
RetrieveServiceContent - OK
Login - OK
FindExtension - OK
https://vcenter.example.com:8084/vci/sdk
Logout - OK
Success!

Putting it all together

The final sample looks like this:

#include "bindings/VimBinding.nsmap"
#include "bindings/vim25VimBindingProxy.h"

using namespace std;

int main() {
	// Proxy object from which all remote calls will be invoked.
	VimBindingProxy vim("https://vcenter.example.com/sdk");

	// Initialize OpenSSL
	soap_ssl_init();
	auto res = soap_ssl_client_context(
			vim.soap,
			SOAP_SSL_NO_AUTHENTICATION,
			nullptr,
			nullptr,
			nullptr,
			nullptr,
			nullptr);
	if (res) {
		cout << "SSL:";
		// Dump the last error from gSOAP.
		soap_print_fault(vim.soap, stderr);
		return 1;
	}

	ns1__ManagedObjectReference moref;
	moref.__item = "ServiceInstance";
	moref.type = new string("ServiceInstance");
	
	ns1__RetrieveServiceContentRequestType retrieveServiceContentReq;
	retrieveServiceContentReq._USCOREthis = &moref;
	_ns1__RetrieveServiceContentResponse retrieveServiceContentResp;

	res = vim.RetrieveServiceContent(
			&amp;retrieveServiceContentReq,
			retrieveServiceContentResp);
	if (res != SOAP_OK) {
		soap_print_fault(vim.soap, stderr);
		return 1;
	}
	cout << "RetrieveServiceContent - OK" << endl;

	ns1__ServiceContent sc = *retrieveServiceContentResp.returnval;

	ns1__LoginRequestType loginReq;
	loginReq._USCOREthis = sc.sessionManager;
	loginReq.userName = "user@example.com";
	loginReq.password = "pass1234";
	_ns1__LoginResponse loginResp;

	res = vim.Login(&amp;loginReq, loginResp);
	if (res != SOAP_OK) {
		soap_print_fault(vim.soap, stderr);
		return 1;
	}
	cout << "Login - OK" << endl;

	ns1__FindExtensionRequestType findExtensionReq;
	findExtensionReq._USCOREthis = sc.extensionManager;
	findExtensionReq.extensionKey = "com.vmware.vcIntegrity";
	_ns1__FindExtensionResponse findExtensionResp;

	res = vim.FindExtension(&amp;findExtensionReq, findExtensionResp);
	if (res != SOAP_OK) {
		soap_print_fault(vim.soap, stderr);
		return 1;
	}
	cout << "FindExtension - OK" << endl;

	ns1__Extension ext = *findExtensionResp.returnval;
	cout << ext.server[0]->url << endl;

	ns1__LogoutRequestType logoutReq;
	logoutReq._USCOREthis = sc.sessionManager;
	_ns1__LogoutResponse logoutResp;

	res = vim.Logout(&amp;logoutReq, logoutResp);
	if (res != SOAP_OK) {
		soap_print_fault(vim.soap, stderr);
		return 1;
	}
	cout << "Logout - OK" << endl;

	cout << "Success!" << endl;

	return 0;
}

To simplify things you can use the following Makefile:

VIM_WSDL_HOME=wsdl
VIM_PREFIX=vim
VIM_WSDL=$(VIM_PREFIX).wsdl
BUILD_DIR=bindings
BINDING_PFX=vim25
NSMAP=VimBinding.nsmap

all: test
.PHONY: all

test:   $(BUILD_DIR)/$(NSMAP) \
        $(BUILD_DIR)/$(BINDING_PFX)VimBindingProxy.h \
        $(BUILD_DIR)/$(BINDING_PFX)VimBindingProxy.cpp \
        $(BUILD_DIR)/$(BINDING_PFX)H.h \
        $(BUILD_DIR)/$(BINDING_PFX)C.cpp \
        $(BUILD_DIR)/$(BINDING_PFX)Stub.h
	g++ -o test \
		test.cpp \
		$(BUILD_DIR)/$(BINDING_PFX)VimBindingProxy.cpp \
		$(BUILD_DIR)/$(BINDING_PFX)C.cpp \
		-lgsoapssl++ -lssl -lcrypto

$(BUILD_DIR)/$(BINDING_PFX).hpp: $(VIM_WSDL_HOME)/$(VIM_PREFIX)Service.wsdl $(VIM_WSDL_HOME)/$(VIM_PREFIX).wsdl $(VIM_WSDL_HOME)/$(VIM_PREFIX)-types.xsd $(VIM_WSDL_HOME)/$(VIM_PREFIX)-messagetypes.xsd $(VIM_WSDL_HOME)/core-types.xsd
	mkdir -p $(BUILD_DIR)
	wsdl2h -c++11 -O4 -o $(BUILD_DIR)/$(BINDING_PFX).hpp $(VIM_WSDL_HOME)/$(VIM_WSDL)

$(BUILD_DIR)/$(BINDING_PFX)H.h $(BUILD_DIR)/$(BINDING_PFX)VimBindingProxy.h $(BUILD_DIR)/$(NSMAP) $(BUILD_DIR)/$(BINDING_PFX)VimBindingProxy.cpp $(BUILD_DIR)/$(BINDING_PFX)Stub.h $(BUILD_DIR)/$(BINDING_PFX)C.cpp : $(BUILD_DIR)/$(BINDING_PFX).hpp
	soapcpp2 -Cjrx -c++11 -p$(BINDING_PFX) -d$(BUILD_DIR) $(BUILD_DIR)/$(BINDING_PFX).hpp

clean:
	rm -Rf $(BUILD_DIR)
	rm test

What about Installing Manually?

The GPL version of gSOAP is available on SourceForge. To build against it the Makefile in this case would look like this (assuming gsoap is downloaded and unpacked under ~/gsoap2-code):

GSOAP_HOME=../gsoap2-code/gsoap
VIM_WSDL_HOME=wsdl
VIM_PREFIX=vim
VIM_WSDL=$(VIM_PREFIX).wsdl
BUILD_DIR=bindings
BINDING_PFX=vim25
NSMAP=VimBinding.nsmap

all: test
.PHONY: all

test:   $(BUILD_DIR)/$(NSMAP) \
        $(BUILD_DIR)/$(BINDING_PFX)VimBindingProxy.h \
        $(BUILD_DIR)/$(BINDING_PFX)VimBindingProxy.cpp \
        $(BUILD_DIR)/$(BINDING_PFX)H.h \
        $(BUILD_DIR)/$(BINDING_PFX)C.cpp \
        $(BUILD_DIR)/$(BINDING_PFX)Stub.h
	g++ -DWITH_OPENSSL -DWITH_COOKIES \
		-o test \
		test.cpp \
		$(BUILD_DIR)/$(BINDING_PFX)VimBindingProxy.cpp \
		$(BUILD_DIR)/$(BINDING_PFX)C.cpp \
		$(GSOAP_HOME)/stdsoap2.cpp \
		-lssl -lcrypto

$(BUILD_DIR)/$(BINDING_PFX).hpp: $(VIM_WSDL_HOME)/$(VIM_PREFIX)Service.wsdl $(VIM_WSDL_HOME)/$(VIM_PREFIX).wsdl $(VIM_WSDL_HOME)/$(VIM_PREFIX)-types.xsd $(VIM_WSDL_HOME)/$(VIM_PREFIX)-messagetypes.xsd $(VIM_WSDL_HOME)/core-types.xsd
	mkdir -p $(BUILD_DIR)
	wsdl2h -c++11 -O4 -o $(BUILD_DIR)/$(BINDING_PFX).hpp $(VIM_WSDL_HOME)/$(VIM_WSDL)

$(BUILD_DIR)/$(BINDING_PFX)H.h $(BUILD_DIR)/$(BINDING_PFX)VimBindingProxy.h $(BUILD_DIR)/$(NSMAP) $(BUILD_DIR)/$(BINDING_PFX)VimBindingProxy.cpp $(BUILD_DIR)/$(BINDING_PFX)Stub.h $(BUILD_DIR)/$(BINDING_PFX)C.cpp : $(BUILD_DIR)/$(BINDING_PFX).hpp
	soapcpp2 -Cjrx -c++11 -p$(BINDING_PFX) -d$(BUILD_DIR) $(BUILD_DIR)/$(BINDING_PFX).hpp

clean:
	rm -Rf $(BUILD_DIR)
	rm test

The parts that differ are highlighted. In short:

  • We conditionally compile with two additional macroses: WITH_OPENSSL and WITH_COOKIES.
  • We compile with ~/gsoap2-code/gsoap/stdsoap2.cpp.
  • We don’t link with libgsoapssl++.so.

This is the process Genivia oulined for building the Amazon S3 SOAP bindings and this is what my research started from.

In Conclusion

Getting to where I am was not an easy task (for me at least). I started consuming gSOAP directly from source than needed to figure out how to just build a single binding example. The biggest pain point was to get the wsdl2h, soapcpp2, ang g++ configuration to align. For example one thing I was not able to figure out is how to build the project with all code nested within a C++ namespace and either remove the type prefixes or prefix the operations with vim__. Each of my attempts ended up with cryptic linker errors about missing symbols. There are also options to modify generated stubs through a file called typemap.dat but I am yet to look into this – hopefully it would prove as a way to beautify the code demonstrated currently.

The testing was really problematic until I figured out that I should slice the schema to only the necessary SOAP calls (something I jokingly call slim.wsdl) or use the -O4 flag of wsdl2h. If I didn’t do any of these things the object file resulting from vim25C.cpp – itself coming around 120 MB – is also of the respectable size of 50 MB. As you can imagine, linking this takes forever. Further down the line I will spend some time to blog about the particular optimizations that are possible with the vSphere SOAP APIs to minimize bindings size and runtime overhead (on Java, at least).

Another interesting topic is getting the gSOAP bindings and getting a Rust client using them which was recently tested by a colleague of mine.

トニー

  •  
  •  
  •  
  •  
  •  
  •  
  •  

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.