Generic WebService (SOAP) client library for C++
Asked Answered
B

3

14

I'm looking for a simple C++ WebService Client Library that can be easily linked into my application.

Preferably this library:

  • can be used to access any SOAP WebService (so I can pass the URL, the WebService name, the WebService method and all the arguments as arguments to a function call)
  • can be linked statically in a C++ application (so no DLL's)
  • is freeware or available at a low cost
  • can be used royalty-free in my application
  • can query the Web service for its WSDL and return me the available method names, arguments of the methods and their data types

Before anyone of you answers .NET: been there, tried it. My major objections against .NET are:

  • you can generate the proxy but it's impossible to change the WebService name in the generated proxy code afterwards, since .NET uses reflection to check the WebService name (see Dynamically call SOAP service from own scripting language for my question regarding that problem)
  • generating the proxy class on the fly doesn't always seem to work correctly

I already used Google to look up this information, but I couldn't find one.

Thanks

EDIT: To clarify this further, I really want something where I can write code like this (or something in this style):

SoapClient mySoapClient;
mySoapClient.setURL("http://someserver/somewebservice");
mySoapClient.setMethod("DoSomething");
mySoapClient.setParameter(1,"Hello");
mySoapClient.setParameter(2,12345);
mySoapClient.sendRequest();
string result;
mySoapClient.getResult(result);

No dynamic code generation.

Blok answered 26/11, 2010 at 13:16 Comment(0)
B
4

I found a solution using on-the-fly-generated assemblies (which I couldn't get working the previous time). Starting point is http://refact.blogspot.com/2007_05_01_archive.html.

E.g. This is the code to use the PeriodicTable web service:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Web;
using System.Web.Services;
using System.Web.Services.Description;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Xml.Serialization;
using System.IO;
using System.Reflection;

namespace GenericSoapClient
{
class Program
    {
    static void method1()
        {
        Uri uri = new Uri("http://www.webservicex.net/periodictable.asmx?WSDL");
        WebRequest webRequest = WebRequest.Create(uri);
        System.IO.Stream requestStream = webRequest.GetResponse().GetResponseStream();

        // Get a WSDL
        ServiceDescription sd = ServiceDescription.Read(requestStream);
        string sdName = sd.Services[0].Name;

        // Initialize a service description servImport
        ServiceDescriptionImporter servImport = new ServiceDescriptionImporter();
        servImport.AddServiceDescription(sd, String.Empty, String.Empty);
        servImport.ProtocolName = "Soap";
        servImport.CodeGenerationOptions = CodeGenerationOptions.GenerateProperties;

        CodeNamespace nameSpace = new CodeNamespace();
        CodeCompileUnit codeCompileUnit = new CodeCompileUnit();
        codeCompileUnit.Namespaces.Add(nameSpace);

        // Set Warnings

        ServiceDescriptionImportWarnings warnings = servImport.Import(nameSpace, codeCompileUnit);

        if (warnings == 0)
            {
            StringWriter stringWriter =
                 new StringWriter(System.Globalization.CultureInfo.CurrentCulture);

            Microsoft.CSharp.CSharpCodeProvider prov =
              new Microsoft.CSharp.CSharpCodeProvider();

            prov.GenerateCodeFromNamespace(nameSpace,
               stringWriter,
               new CodeGeneratorOptions());

            string[] assemblyReferences =
               new string[2] { "System.Web.Services.dll", "System.Xml.dll" };

            CompilerParameters param = new CompilerParameters(assemblyReferences);

            param.GenerateExecutable = false;
            param.GenerateInMemory = true;
            param.TreatWarningsAsErrors = false;

            param.WarningLevel = 4;

            CompilerResults results = new CompilerResults(new TempFileCollection());
            results = prov.CompileAssemblyFromDom(param, codeCompileUnit);
            Assembly assembly = results.CompiledAssembly;
            Type service = assembly.GetType(sdName);

            //MethodInfo[] methodInfo = service.GetMethods();

            List<string> methods = new List<string>();

            // only find methods of this object type (the one we generated)
            // we don't want inherited members (this type inherited from SoapHttpClientProtocol)
            foreach (MethodInfo minfo in service.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
                {
                methods.Add(minfo.Name);
                Console.WriteLine (minfo.Name + " returns " + minfo.ReturnType.ToString());
                ParameterInfo[] parameters = minfo.GetParameters();
                foreach (ParameterInfo pinfo in parameters)
                    {
                        Console.WriteLine("   " + pinfo.Name + " " + pinfo.ParameterType.ToString());
                    }
                }

            // Create instance of created web service client proxy
            object obj = assembly.CreateInstance(sdName);

            Type type = obj.GetType();

            object[] args0 = new object[] { };
            string result0 = (string)type.InvokeMember(methods[0], BindingFlags.InvokeMethod, null, obj, args0);
            Console.WriteLine(result0);

            object[] args1 = new object[] { "Oxygen" };
            string result1 = (string)type.InvokeMember(methods[1], BindingFlags.InvokeMethod, null, obj, args1);
            Console.WriteLine(result1);
            }
        }
    }
}

In this code I explicitly use methods[0] and methods[1] but in reality you would check the method names of course. In this example I get the names of all elements in the periodic table, then get the atomic weight of oxygen.

This example does not yet contain logic to support a proxy. I still need to add this, but for the moment, it solves my biggest problem, namely, having a generic SOAP client.

EDIT:

I know this code is C# and I was originally asking for a C++ solution, but this code proves that it can work in a .NET environment (which I can still use in limited parts of my application), and I will probably rewrite this code into C++/.NET, which solves my problem.

Blok answered 29/11, 2010 at 13:23 Comment(3)
It's a good point! Do you think it can be ported to C++ Builder environment? Or is it dependent from .NET and Reflection?Nystrom
@bluish, this is really dependent on .Net. The code uses .Net to generate the client class, and then uses the .Net C# compiler to compile it, and use .Net reflection to call the class.Blok
Although I'm happy that the OP's problem is solved, I believe this answer just simply can't be the accepted answer of this question, as it's in C# while the question is specifically about C++...Expense
T
6

Have you looked at gSOAP? I think it will be suitable for your needs.

http://gsoap2.sourceforge.net/

Twibill answered 26/11, 2010 at 13:27 Comment(7)
It looks like gSOAP generates CPP code. This still requires me to generate customer-specific CPP code. What I want is a simple C++ class (or set of classes), where I can simply specify the URL, the web service name, the web service method and the arguments.Blok
I've done a lot of research on C++ SOAP interfaces, and gSOAP is the best out there. sigh Good luck!Twibill
Probably gSOAP is the best if you know the WSDL beforehand. But what if the webservice name, methods and arguments can differ at every customer, and I want to read this information from a configuration file and make the calls depending on the contents of the configuration file?Blok
@Blok - you are going to end up with customer specific code in any case. As per your example, you had to "know" that there were two parameters to send, and the name of the method to call. The construction of the actual XML doc is then very end-use specific, depending on the WSDL returned originally. Or are you trying to make that automatic/discoverable?Sickle
The problem is that I have to make SOAP calls from within my own scripting language. I need to translate the calls in my scripting language to real SOAP calls, but since I cannot know what kind of calls to which kind of web services my customers will execute in their scripts, I cannot create C++ proxies beforehand.Blok
gSOAP has a GPL license scheme, be aware of this if you're developing a commercial app. or you can go for the commercial lic. but it won't be cheap..Quickel
@Didac, I used .Net to dynamically generate stub class. You need to look at the ServiceDescriptionImporter class.Blok
B
4

I found a solution using on-the-fly-generated assemblies (which I couldn't get working the previous time). Starting point is http://refact.blogspot.com/2007_05_01_archive.html.

E.g. This is the code to use the PeriodicTable web service:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Web;
using System.Web.Services;
using System.Web.Services.Description;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Xml.Serialization;
using System.IO;
using System.Reflection;

namespace GenericSoapClient
{
class Program
    {
    static void method1()
        {
        Uri uri = new Uri("http://www.webservicex.net/periodictable.asmx?WSDL");
        WebRequest webRequest = WebRequest.Create(uri);
        System.IO.Stream requestStream = webRequest.GetResponse().GetResponseStream();

        // Get a WSDL
        ServiceDescription sd = ServiceDescription.Read(requestStream);
        string sdName = sd.Services[0].Name;

        // Initialize a service description servImport
        ServiceDescriptionImporter servImport = new ServiceDescriptionImporter();
        servImport.AddServiceDescription(sd, String.Empty, String.Empty);
        servImport.ProtocolName = "Soap";
        servImport.CodeGenerationOptions = CodeGenerationOptions.GenerateProperties;

        CodeNamespace nameSpace = new CodeNamespace();
        CodeCompileUnit codeCompileUnit = new CodeCompileUnit();
        codeCompileUnit.Namespaces.Add(nameSpace);

        // Set Warnings

        ServiceDescriptionImportWarnings warnings = servImport.Import(nameSpace, codeCompileUnit);

        if (warnings == 0)
            {
            StringWriter stringWriter =
                 new StringWriter(System.Globalization.CultureInfo.CurrentCulture);

            Microsoft.CSharp.CSharpCodeProvider prov =
              new Microsoft.CSharp.CSharpCodeProvider();

            prov.GenerateCodeFromNamespace(nameSpace,
               stringWriter,
               new CodeGeneratorOptions());

            string[] assemblyReferences =
               new string[2] { "System.Web.Services.dll", "System.Xml.dll" };

            CompilerParameters param = new CompilerParameters(assemblyReferences);

            param.GenerateExecutable = false;
            param.GenerateInMemory = true;
            param.TreatWarningsAsErrors = false;

            param.WarningLevel = 4;

            CompilerResults results = new CompilerResults(new TempFileCollection());
            results = prov.CompileAssemblyFromDom(param, codeCompileUnit);
            Assembly assembly = results.CompiledAssembly;
            Type service = assembly.GetType(sdName);

            //MethodInfo[] methodInfo = service.GetMethods();

            List<string> methods = new List<string>();

            // only find methods of this object type (the one we generated)
            // we don't want inherited members (this type inherited from SoapHttpClientProtocol)
            foreach (MethodInfo minfo in service.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
                {
                methods.Add(minfo.Name);
                Console.WriteLine (minfo.Name + " returns " + minfo.ReturnType.ToString());
                ParameterInfo[] parameters = minfo.GetParameters();
                foreach (ParameterInfo pinfo in parameters)
                    {
                        Console.WriteLine("   " + pinfo.Name + " " + pinfo.ParameterType.ToString());
                    }
                }

            // Create instance of created web service client proxy
            object obj = assembly.CreateInstance(sdName);

            Type type = obj.GetType();

            object[] args0 = new object[] { };
            string result0 = (string)type.InvokeMember(methods[0], BindingFlags.InvokeMethod, null, obj, args0);
            Console.WriteLine(result0);

            object[] args1 = new object[] { "Oxygen" };
            string result1 = (string)type.InvokeMember(methods[1], BindingFlags.InvokeMethod, null, obj, args1);
            Console.WriteLine(result1);
            }
        }
    }
}

In this code I explicitly use methods[0] and methods[1] but in reality you would check the method names of course. In this example I get the names of all elements in the periodic table, then get the atomic weight of oxygen.

This example does not yet contain logic to support a proxy. I still need to add this, but for the moment, it solves my biggest problem, namely, having a generic SOAP client.

EDIT:

I know this code is C# and I was originally asking for a C++ solution, but this code proves that it can work in a .NET environment (which I can still use in limited parts of my application), and I will probably rewrite this code into C++/.NET, which solves my problem.

Blok answered 29/11, 2010 at 13:23 Comment(3)
It's a good point! Do you think it can be ported to C++ Builder environment? Or is it dependent from .NET and Reflection?Nystrom
@bluish, this is really dependent on .Net. The code uses .Net to generate the client class, and then uses the .Net C# compiler to compile it, and use .Net reflection to call the class.Blok
Although I'm happy that the OP's problem is solved, I believe this answer just simply can't be the accepted answer of this question, as it's in C# while the question is specifically about C++...Expense
C
1

Axis2C : http://axis.apache.org/axis2/c/core/index.html

Axis2C ticks most of the above , please check for static linking. .

EDIT: As per last few messages on the list, static linking is incomplete. The below still holds:

Perhaps I do not understand the question correctly. Any web service you call you need to specify the endpoint URL and the operation & parameters.

Are you referring to dynamically "discovering" the services & presenting the option to call them...? If so I doubt this is possible.

If you are referring to generic framework, SOAP messages are client end responsibility any way... You should not have a problem wrapping them under some of the toolkit API's. WSDL code generation is not mandatory. I have written a few services from scratch, i.e. You can set endpoint, service and craft the SOAP message, parameters, headers etc. as you feel.

Cheers!

Cumin answered 26/11, 2010 at 16:11 Comment(1)
Could you please post some example (or links to it)?Nystrom

© 2022 - 2024 — McMap. All rights reserved.