Re: Visual C++ General Consume Web Services via C++?
Bill Cumming
I agree with Johan - it is very easy. It automatically uses SOAP and does all the initialization and cleanup for you. Very nice. You will have to deal with all the COM-like interface issues like using BSTR instead of CString (ugh) etc. I'm not sure, but you might need to install MSXML Parser v6.0 to use this (although that is forcibly installed by Studio 2005 anyway, but your target systems might need it).
I created an error decoding routine (see the end of this posting) that decodes all the error messages that you get back from SOAP and the web server, which can really help you track down where problems are. They are still pretty cryptic, but after making a list of all the situations I've encountered I can usually match up a combination of the cryptic errors with (usually) configuration or network (or coworker software) problems.
But I do have a very minor complaint. The auto-generated proxy code hard-codes the URL of the web service that you are referencing. That's OK if it is an Internet web service. But my coworker is developing the web service, and sometimes I have it installed on my box. This becomes worse when considering how to deploy my software since it needs a way to modify that URL to point to the installation wherever that may be. So every time I update the reference to the Web Service (every time my coworker changes the interface during development, which is a lot) I have to manually change the automatically generated constructor from the hard-coded URL:
CService1T(ISAXXMLReader *pReader = NULL)
:TClient(_T("http://localhost/MGate/Service1.asmx"))
{
SetClient(true);
SetReader(pReader);
}
to the following to add the URL as a parameter:
CService1T(LPCTSTR sWebServiceURL = "http://localhost/MGate/Service1.asmx", ISAXXMLReader *pReader = NULL)
:TClient(_T(sWebServiceURL))
{
SetClient(true);
SetReader(pReader);
}
EVERY time the Web reference gets updated. Note also that if you are using Source Safe you need to checkout both the auto-generated proxy file (localhost.h in my case since I had the web service running on localhost i.e. on my box) and the Service1.wsdl file BEFORE you update the web reference. My own habit is to patch the constructor described above, copy in all the Source Safe history comments at the top of the file, save it off to a different filename and use that different filename as the actual include for the project (i.e. ignore the auto-generated one).
It's not a huge deal, but I don't see any automated way around it. I wish their auto-generated proxy code had defaulted to making the URL a default parameter.
I hope our esteemed forum moderator is listening....
Mr. Bill
Handy error decoding routine:
void DecodeSoapErrorMessage(CService1& MGateWebService)
{
//// From VC\atlmfc\include\Atlsoap.h
//enum SOAP_ERROR_CODE
//{
// SOAP_E_UNK=0,
// SOAP_E_VERSION_MISMATCH=100,
// SOAP_E_MUST_UNDERSTAND=200,
// SOAP_E_CLIENT=300,
// SOAP_E_SERVER=400
//};
//// client error states
//enum SOAPCLIENT_ERROR
//{
// SOAPCLIENT_SUCCESS=0, // everything succeeded
// SOAPCLIENT_INITIALIZE_ERROR, // initialization failed -- most likely an MSXML installation problem
// SOAPCLIENT_OUTOFMEMORY, // out of memory
// SOAPCLIENT_GENERATE_ERROR, // failed in generating the response
// SOAPCLIENT_CONNECT_ERROR, // failed connecting to server
// SOAPCLIENT_SEND_ERROR, // failed in sending message
// SOAPCLIENT_SERVER_ERROR, // server error
// SOAPCLIENT_SOAPFAULT, // a SOAP Fault was returned by the server
// SOAPCLIENT_PARSEFAULT_ERROR, // failed in parsing SOAP fault
// SOAPCLIENT_READ_ERROR, // failed in reading response
// SOAPCLIENT_PARSE_ERROR // failed in parsing response
//};
//
CString sTemp, sSoapError;
char* sSoapErrors[11] = {
"No errors.",
"Initialization failed. Possibly caused by an MSXML installation problem.",
"Out of memory.",
"Failed to generate the response.",
"Failed to connect to the server.",
"Failed to send the message.",
"Server error.",
"The server returned a SOAP fault.",
"Failed to parse a SOAP fault.",
"Failed while reading the response.",
"Failed while parsing the response."};
SOAPCLIENT_ERROR scError = MGateWebService.GetClientError();
if((scError >= 0) && (scError < 11))
sSoapError.Format("MGateWebService.GetClientError = %d (%s)", scError, sSoapErrors[scError]); // see SOAPCLIENT_ERROR
else
sSoapError.Format("MGateWebService.GetClientError = %d (unknown code)", scError);
CommandLineErrorLog(sSoapError);
CString strDetail = CW2A(MGateWebService.m_fault.m_strDetail); // CW2A macro: convert wide char to ascii
CString strFaultActor = CW2A(MGateWebService.m_fault.m_strFaultActor);
CString strFaultCode = CW2A(MGateWebService.m_fault.m_strFaultCode);
CString strFaultString = CW2A(MGateWebService.m_fault.m_strFaultString);
sSoapError.Format("Error code %d\n" // from SOAP_ERROR_CODE
"FaultCode: %s\n"
"FaultString: %s",
MGateWebService.m_fault.m_soapErrCode, // oddly enough, this is NOT the same as GetClientError( ) - go figure.
strFaultCode, // text corresponding to SOAP_ERROR_CODE in m_soapErrCode
strFaultString);
if( ! strDetail.IsEmpty())
{
sTemp.Format("Details: %s\n", strDetail);
sSoapError += sTemp;
}
if( ! strFaultActor.IsEmpty())
{
sTemp.Format("FaultActor: %s\n", strFaultActor);
sSoapError += sTemp;
}
if(scError == SOAPCLIENT_SOAPFAULT)
{
sTemp.LoadString(IDS_DLG_WORKLIST_CONFIG_ERR);
sSoapError += sTemp;
}
CommandLineErrorLog(sSoapError);
}