gSoap编写代码访问WCF服务


最近给人写个gSoap的C++程序访问WCF服务,结果调用的时候没有返回0(SOAP_OK),而是415。无奈,无法直接在别人的机器上进行调试,那就自己搭一个简单的WCF服务吧。


测试用的WCF服务就是根据Visual Studio的向导生成的代码。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace SimpleServer
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together.
    [ServiceContract]
    public interface IService1
    {

        [OperationContract]
        string GetData(int value);

        [OperationContract]
        CompositeType GetDataUsingDataContract(CompositeType composite);

        // TODO: Add your service operations here
    }


    // Use a data contract as illustrated in the sample below to add composite types to service operations.
    [DataContract]
    public class CompositeType
    {
        bool boolValue = true;
        string stringValue = "Hello ";

        [DataMember]
        public bool BoolValue
        {
            get { return boolValue; }
            set { boolValue = value; }
        }

        [DataMember]
        public string StringValue
        {
            get { return stringValue; }
            set { stringValue = value; }
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace SimpleServer
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
    // NOTE: In order to launch WCF Test Client for testing this service, please select Service1.svc or Service1.svc.cs at the Solution Explorer and start debugging.
    public class Service1 : IService1
    {
        public string GetData(int value)
        {
            return string.Format("You entered: {0}", value);
        }

        public CompositeType GetDataUsingDataContract(CompositeType composite)
        {
            if (composite == null)
            {
                throw new ArgumentNullException("composite");
            }
            if (composite.BoolValue)
            {
                composite.StringValue += "Suffix";
            }
            return composite;
        }
    }
}

启动之后,下载WSDL文件 Service1.wsdl。


下面开始构建 gSoap 客户端。这次用的最新的gsoap_2.8.17.zip。

@echo off

..\..\gsoap-2.8\gsoap\bin\win32\wsdl2h.exe -v -I..\..\gsoap-2.8\gsoap -o Service1.h Service1.wsdl 2>>soap.log

@pause

..\..\gsoap-2.8\gsoap\bin\win32\soapcpp2.exe -C  -j -I..\..\gsoap-2.8\gsoap;..\..\gsoap-2.8\gsoap\import;..\..\gsoap-2.8\gsoap\custom Service1.h 2>>soap.log

@pause


客户端代码:

#include 
#include 

#include "soapBasicHttpBinding_USCOREIService1Proxy.h"
#include "BasicHttpBinding_USCOREIService1.nsmap"

int main()
{
    BasicHttpBinding_USCOREIService1Proxy proxy;

    int value = 2;    

    _tempuri__GetData request;
    request.value = &value;

    _tempuri__GetDataResponse response;

    int ret = proxy.GetData(&request, &response);

    if (ret == SOAP_OK)
    {
        std::cout << *response.GetDataResult << std::endl;
    }
    else
    {
        proxy.soap_stream_fault(std::cerr);
    }

	return 0;
}

执行之后得到的错误信息为:

Error 415 fault: SOAP-ENV:Server[no subcode]
"HTTP Error"
Detail: HTTP/1.1 415 Cannot process the message because the content type 'application/soap+xml; charset=utf-8; action="http://tempuri.org/IService1/GetData"' wa
s not the expected type 'text/xml; charset=utf-8'.

成功复现之前的错误。经过一些搜索,得到了答案。发送的请求中 content type 应该是 'text/xml; charset=utf-8',现在却是 'application/soap+xml; charset=utf-8; action="http://tempuri.org/IService1/GetData"‘。


修正的话,需要在生成C++代码是按照SOAP1.1标准,执行如下命令:

..\..\gsoap-2.8\gsoap\bin\win32\soapcpp2.exe -1 -C  -j -I..\..\gsoap-2.8\gsoap;..\..\gsoap-2.8\gsoap\import;..\..\gsoap-2.8\gsoap\custom Service1.h 2>>soap.log

重新编译之后,能够成功调用,输出

You entered: 2

按照搜索的结果,需要修改WSDL文件或nsmap文件,可能是gSoap版本更新后进行了修正。

后来查找有什么替代的方法,找到了 WWSAPI 的方法,缺点是不能跨平台,这里记录一下实现的方法。

首先生成WSDL对应的C++封装文件:

Wsutil.exe /wsdl:Service1.wsdl /string:WS_STRING

然后写客户端代码(根据MSDN WWSAPI HttpClientExample修改):

//------------------------------------------------------------
// Copyright (C) Microsoft.  All rights reserved.
//------------------------------------------------------------

#ifndef UNICODE
#define UNICODE
#endif
#include 
#include 
#include 
#include 
#include "WebServices.h"
#include "Service1.wsdl.h"

// Print out rich error info
void PrintError(HRESULT errorCode, WS_ERROR* error)
{
    wprintf(L"Failure: errorCode=0x%lx\n", errorCode);

    if (errorCode == E_INVALIDARG || errorCode == WS_E_INVALID_OPERATION)
    {
        // Correct use of the APIs should never generate these errors
        wprintf(L"The error was due to an invalid use of an API.  This is likely due to a bug in the program.\n");
        DebugBreak();
    }

    HRESULT hr = NOERROR;
    if (error != NULL)
    {
        ULONG errorCount;
        hr = WsGetErrorProperty(error, WS_ERROR_PROPERTY_STRING_COUNT, &errorCount, sizeof(errorCount));
        if (FAILED(hr))
        {
            goto Exit;
        }
        for (ULONG i = 0; i < errorCount; i++)
        {
            WS_STRING string;
            hr = WsGetErrorString(error, i, &string);
            if (FAILED(hr))
            {
                goto Exit;
            }
            wprintf(L"%.*s\n", string.length, string.chars);
        }
    }
Exit:
    if (FAILED(hr))
    {
        wprintf(L"Could not get error string (errorCode=0x%lx)\n", hr);
    }
}

// Main entry point
int __cdecl wmain(int argc, __in_ecount(argc) wchar_t **argv)
{
    UNREFERENCED_PARAMETER(argc);
    UNREFERENCED_PARAMETER(argv);
    
    HRESULT hr = NOERROR;
    WS_ERROR* error = NULL;
    WS_CHANNEL* channel = NULL;
    WS_HEAP* heap = NULL;
    WS_ENDPOINT_ADDRESS address;
    WS_STRING serviceUrl = WS_STRING_VALUE(L"http://localhost:15418/Service1.svc");
    
    // Create an error object for storing rich error information
    hr = WsCreateError(
        NULL, 
        0, 
        &error);
    if (FAILED(hr))
    {
        goto Exit;
    }
    
    // Create a heap to store deserialized data
    hr = WsCreateHeap(
        /*maxSize*/ 2048, 
        /*trimSize*/ 512, 
        NULL, 
        0, 
        &heap, 
        error);
    if (FAILED(hr))
    {
        goto Exit;
    }

    //// Create a channel
    hr = WsCreateChannel(
        WS_CHANNEL_TYPE_REQUEST, 
        WS_HTTP_CHANNEL_BINDING, 
        NULL, 
        0, 
        NULL, 
        &channel, 
        error);
    if (FAILED(hr))
    {
        goto Exit;
    }
 
    // Initialize address of service
    address.url = serviceUrl;
    address.headers = NULL;
    address.extensions = NULL;
    address.identity = NULL;
    
    // Open channel to address
    hr = WsOpenChannel(channel, &address, NULL, error);
    if (FAILED(hr))
    {
        goto Exit;
    }


    WS_SERVICE_PROXY * serviceProxy;
    hr = BasicHttpBinding_IService1_CreateServiceProxy(
        NULL,
        NULL,
        0,
        &serviceProxy,
        error);

    if (FAILED(hr))
    {
        goto Exit;
    }

    hr = WsResetMessage(requestMessage, error);

    hr = WsOpenServiceProxy(serviceProxy, &address, NULL, error);

    if (FAILED(hr))
    {
        goto Exit;
    }

    hr = WsResetMessage(requestMessage, error);
    // Send some request-replies
    for (int i = 0; i < 100; i++)
    {     
        WS_STRING res;

        hr = BasicHttpBinding_IService1_GetData(serviceProxy, 2, &res, heap, NULL, 0, NULL, error);
        
        if (FAILED(hr))
        {
            goto Exit;
        }
        
        // Print out confirmation contents
        wprintf(L"The Response is %.*s\n", res.length, res.chars);
        
        // Reset the heap
        hr = WsResetHeap(heap, error);
        if (FAILED(hr))
        {
            goto Exit;
        }
    }
    
Exit:
    if (FAILED(hr))
    {
        // Print out the error
        PrintError(hr, error);
    }
    fflush(stdout);
    
    if (channel != NULL)
    {
        // Close the channel
        WsCloseChannel(channel, NULL, error);
    }

    if (channel != NULL)
    {
        WsFreeChannel(channel);
    }
    
    if (error != NULL)
    {
        WsFreeError(error);
    }
    if (heap != NULL)
    {
        WsFreeHeap(heap);
    }
    fflush(stdout);
    return SUCCEEDED(hr) ? 0 : -1;
}




参考链接:

http://www.blinnov.com/en/2008/01/22/wcf-service-unmanaged-client/

http://coab.wordpress.com/2009/10/15/calling-wcf-services-from-a-linux-c-client-using-gsoap/



你可能感兴趣的:(C/C++,Script,CSharp,Web)