'WebService'에 해당되는 글 1건

  1. 2007.12.19 WebService C++ SoapClient 2

2004. 02. 19

WebService C++ SoapClient

by code1009@empal.com


Win32 / WinCE.net / Pocket PC Platform 환경에서 C++ Soap Client를 작성하여 WebService를 호출하는 방법을 소개합니다.





목차

  1. 머릿말
  2. WebService 작성
  3. Client의 WebService 호출
  4. Win32 Soap Client 작성
  5. WinCE.net Soap Client 작성
  6. PocketPC Soap Client 작성



머릿말

프로젝트 중에서 서버에 있는 자원을 다양한 클라이언트 플랫폼에서 쉽게 이용할 수 있는 방법을 찾던 중에 웹서비스를 이용하면 될 것 같아서 찾아본 내용을 정리하여 올리는 것입니다.

물론 .net CF를 이용하여 임베디드 시스템에서 웹서비스를 쉽게 호출이 가능하나, .net CF환경에서 개발한 어플리케이션은 조금 느린감이 없지 않아 있고 C++로 만들면 코드가 멋있어 보이는 개인적인 취향 때문에 (^_^;) 만들게 되었습니다.

그리고, 저는 웹서비스에 대해서 자세히 모릅니다.
그냥 Web에서 호출되는 RPC(Remote Procedure Call)이라는 정도 밖에는요.
그래서, 아래 제가 설명하는 부분 중에 틀린 부분도 있을 겁니다.
틀린 부분이 있더라도 너그럽게 봐주시고, 저와 같은 초보분들에게 조금이나마 도움이 되었으면 합니다.



WebService 작성

먼저 클라이언트들이 호출할 간단한 웹서비스를 만들어 보겠습니다. 개발 환경은 아래와 같습니다.

운영체제 Microsoft Windows 2000 SP4
개발도구 Microsoft Visual Studio.net 2003
사용언어 C#


WebService를 생성하기 위해서는 Microsoft Visual Studio.net에서 아래와 같이 프로젝트를 생성합니다.
 

사용자 삽입 이미지



프로젝트를 생성하면 WebService 코드를 XXXX.asmx.cx 파일을 열어 아래와 같이 추가합니다.
아래 코드는 Test()라는 이름으로 생성하여 문자열을 하나 받아서 다시 1차원 문자열 배열로 리턴하는 코드로 작성하였습니다.
 
사용자 삽입 이미지


작성이 완료 되었으면 Compile을 한후에 아래와 같이 확인 하실 수 있습니다.
 
사용자 삽입 이미지
위에 내용을 잘 살펴 보면 WebService는 http로 xml 데이터를 특정 형식에 맞추어 데이터를 보내면 실행 결과를 xml 데이터로 다시 전송해주는 (Soap) 형태를 띄고 있습니다.

이러한 xml 데이터를 http로 전송하면

POST /asp.net/svc/MyWebSvc/MyWebSvc.asmx HTTP/1.1
Host: XXX.XXX.XXX.XXX
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/Test"

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <Test xmlns="http://tempuri.org/">
      <message>웹에서 호출</message>
    </Test>
  </soap:Body>
</soap:Envelope>


http로 이러한 xml 데이터 형태를 받아서 결과를 얻는 것입니다.

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <TestResponse xmlns="http://tempuri.org/">
      <TestResult>
        <string>Result[0]=웹에서 호출</string>
        <string>Result[1]=웹에서 호출</string>
        <string>Result[2]=웹에서 호출</string>
      </TestResult>
    </TestResponse>
  </soap:Body>
</soap:Envelope>




Client의 WebService 호출

 

void CSoapClient_XXXXXDlg::OnButton1() 
{
	// TODO: Add your control notification handler code here
	if (FAILED(CoInitializeEx(NULL,COINIT_MULTITHREADED)))
		return;
	InvokeWebSvc  (GetDlgItem (IDC_LIST1)->GetSafeHwnd ());
	CoUninitialize();	
}

       

모든 클라이언트들은 다이얼로그 프로젝트로 리스트박스 컨트롤과 버튼을 하나씩 가지고 있습니다.
버튼이 클릭했을때 클라이언트 프로그램은 InvokeWebSvc() 함수를 호출하여 IDC_LIST1의 리스트 컨트롤에 가져온 데이터를 채워넣는 구조를 가지고 있습니다.



Win32 Soap Client 작성

운영체제 Microsoft Windows XP SP1
개발도구 Microsoft Visual Studio 6.0 SP5
사용언어 C++(MFC)
SDK - Soap Toolkit 3.0 [Download]
(http://www.microsoft.com/downloads/details.aspx?familyid=c943c0dd-ceec-4088-9753-86f052ec8450&languageid=f49e8428-7071-4979-8a67-3cffcb0c2524&displaylang=en)

- Microsoft XML 2.0 이상

버튼이벤트에 대한 내용은 위에서 설명한 내용과 동일하여 생략하고 WebService를 호출하는 부분만 코드로 주석을 이용하여 설명하겠습니다.

       

// COM 관련 클래스를 사용하기 위해 해더 파일을 인클루드합니다.
#include <comdef.h>
#include <atlbase.h>

// MSXML과 MSSoap30 COM의 스마트 포인터를 위해 임포트 시킵니다.
#import "C:\Windows\System32\msxml3.dll"
#import "C:\Program Files\Common Files\MSSoap\Binaries\mssoap30.dll" \
         named_guids exclude("IStream", "IErrorInfo", "ISequentialStream", \
         "_LARGE_INTEGER", "_ULARGE_INTEGER", "tagSTATSTG", "_FILETIME")

// 네임스페이스를 무시합니다.
using namespace MSXML2;
using namespace MSSOAPLib30;

void InvokeWebSvc (HWND hWnd)
{
	BSTR    bstrWsdl   = SysAllocString (L"http://XXX.XXX.XXX.XXX/asp.net/svc/MyWebSvc/MyWebSvc.asmx?WSDL");
	                     // WSDL파일 URL경로를 지정
	BSTR    bstrMethod = SysAllocString (L"Test");       // 웹서비스 메소드 이름
	BSTR    bstrParam  = SysAllocString (L"From Win32"); // 웹서비스 메소드 파라메터
	VARIANT varResult;                                   // 웹서비스 메소드의 리턴값

	CComPtr<ISoapClient> pSoapClient; // MSSoap30 객체의 스마트 포인터

	HRESULT    hr;
	DISPPARAMS dispparams;   // IDispatch::Invoke() 함수를 부르기 위한 파라메터들
	EXCEPINFO  excepinfo;
	DISPID     dispid;
	UINT       uArgErr;

	UINT uDim        = 0;    // 결과값 문자열 배열의 차원 정보
	long lLBound     = 0l;   // 결과값 문자열 배열의 낮은 인덱스
	long lUBound     = 0l;   // 결과값 문자열 배열의 높은 인덱스
	long nBoundIndex = 0l;   // 결과값 문자열 배열 인덱스로 값을 읽어올때 사용합니다.
	BSTR bstrElement = NULL; // 결과값 문자열 배열의 하나의 값을 읽어올때 사용합니다.


#if !(defined(_UNICODE) || defined(OLE2ANSI)) // UNICODE / MBCS 설정에 관계없이 사용하기 위해서 추가했습니다.
	USES_CONVERSION;
#endif

	// IDispatch::Invoke() 파라메터/리턴값 초기화
	VariantInit(&varResult);
	ZeroMemory (&excepinfo,  sizeof(EXCEPINFO));
	ZeroMemory (&dispparams, sizeof(DISPPARAMS));

	try
	{
		hr = pSoapClient.CoCreateInstance (__uuidof(SoapClient30)); // MSSoap30 객체를 생성합니다.

		hr = pSoapClient->MSSoapInit (bstrWsdl, L"", L"", L"");     // WSDL파일 URL경로를 이용하여
		                                                            // MSSoap30 객체를 초기화합니다.

		dispparams.rgvarg             = new VARIANTARG[1];          // IDispatch::Invoke()의 파라메터
		dispparams.cArgs              = 1;                          // 값을 설정합니다.
		dispparams.cNamedArgs         = 0;
		dispparams.rgdispidNamedArgs  = NULL;	 
		dispparams.rgvarg[0].vt       = VT_BYREF|VT_BSTR;
		dispparams.rgvarg[0].pbstrVal = &bstrParam;                 // L"From Win32" 값을 설정합니다.

		// Test()의 이름으로 메소드 아이디를 얻습니다.
		hr = pSoapClient->GetIDsOfNames (IID_NULL, &bstrMethod, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
		// Test() 메소드를 수행합니다.
		hr = pSoapClient->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD,
                                 &dispparams, &varResult, &excepinfo, &uArgErr);

		// 결과값이 문자열 배열인지 확인합니다.
		if ((VT_BSTR|VT_ARRAY)==varResult.vt)
		{		
			uDim = SafeArrayGetDim    (varResult.parray);                 // 몇 차원배열인지 확인합니다.
			hr   = SafeArrayGetLBound (varResult.parray, uDim, &lLBound); // 배열의 낮은 인덱스를 구합니다.
			hr   = SafeArrayGetUBound (varResult.parray, uDim, &lUBound); // 배열의 높은 인덱스를 구합니다.

			// 1차원 배열임을 보증합니다.
			ASSERT (uDim == 1);

			// 배열의 갯수 만큼 반복
			for (nBoundIndex=lLBound; nBoundIndex<=lUBound; nBoundIndex++)
			{
				// 배열로 부터 인덱스에 해당하는 문자열을 읽어옵니다.
				hr = SafeArrayGetElement(varResult.parray, &nBoundIndex, &bstrElement);

				// 문자열을 리스트박스 컨트롤에 추가합니다.
#if !(defined(_UNICODE) || defined(OLE2ANSI))
				SendMessage (hWnd, LB_ADDSTRING, 0, (LPARAM)W2A(bstrElement));
#else
				SendMessage (hWnd, LB_ADDSTRING, 0, (LPARAM)bstrElement);
#endif
				SysFreeString (bstrElement);
				bstrElement = NULL;
			}
		}
	}
	catch (CException* pE)
	{
		// 오류가 발생할 경우 에러메세지를 출력합니다.
		TCHAR szErrMessage[512];

		ZeroMemory (szErrMessage, sizeof(TCHAR[512]));
		pE->GetErrorMessage(szErrMessage, sizeof(TCHAR[512]), NULL);
		AfxMessageBox (szErrMessage);
		delete pE;
	}
	catch (...)
	{
		AfxMessageBox (TEXT("Unknown Exception!"));
	}

	// 사용된 리소스를 해제 합니다.
	VariantClear (&varResult);		
	if (bstrElement)       SysFreeString (bstrElement);
	if (bstrParam)         SysFreeString (bstrElement);
	if (bstrMethod)        SysFreeString (bstrElement);
	if (bstrWsdl)          SysFreeString (bstrElement);
	if (pSoapClient)       pSoapClient.Release();
	if (dispparams.rgvarg) delete [] dispparams.rgvarg;
}

       

실행결과
사용자 삽입 이미지




WinCE.net Soap Client 작성

운영체제 Microsoft Windows CE .net 4.20 Emualtor
개발도구 Microsoft embedded Visual C++ 4.0 SP 2
사용언어 C++(MFC)
SDK - MSSoap 1.0

WinCE.net안에 내장되어있는 MSSoap1.0을 이용하여 WinCE.net에서 Soap Client를 작성하는 방법을 알아 보겠습니다.
위에서 사용한 MSSoap3.0과 달리 1.0은 메소드를 실행하는 것이 아니라 맨 첫 부분에 보았던 xml 문서를 파싱해서 처리해야합니다.

아래 코드 중에 hr로 받은 값이 실패면 에러처리를 해야하는데 귀찮은 관계로(-_-;;) 안했습니다.
잘 알고 계시겠지만 혹시 모르시는 분들을 위해 WinCE 계열 플랫폼에서 코드를 작성할때 문자열은 유니코드로 작성해야됩니다.
그 이유는 제가 WinCE를 안 만들어서 잘모릅니다.(^o^;;)

설명하는 부분은 위 Win32와 동일합니다.

[참고] 일반 WinCE.net 플랫폼 빌더(? 이름이 맞는지 기억이 잘 안나여 -_-;)를 이용해서 PDA를 운영체제를 만들때 운영체제의 용량을 작게하기 위해서 이 Soap 컴포넌트를 빼는 경우가 있습니다. 제가 회사에서 굴러다니는 WinCE.net용 PDA에서 테스트 해본결과 이 컴포넌트가 없어서 에뮬레이터에서 테스트 했습니다.

  

// COM 관련 클래스를 사용하기 위해 해더 파일을 인클루드합니다.
#include <Atlbase.h>
#include <ComDef.h>
#include <Mssoap.h>
#include <Msxml2.h>

void InvokeWebSvc(HWND hWnd)
{
	CComVariant varWsdl         (L"http://XXX.XXX.XXX.XXX/asp.net/svc/MyWebSvc/MyWebSvc.asmx?WSDL");
	                            // WSDL파일 URL경로를 지정
	CComVariant varSoapAction   (L"http://tempuri.org/Test"); // SoapAction 지정
	CComBSTR    bstrUri         (L"http://tempuri.org/");     // uri 지정
	CComBSTR    bstrMethod      (L"Test");          // 웹서비스 메소드 이름
	CComBSTR    bstrReqParamName(L"message");       // 파라메터 이름
	CComBSTR    bstrReqParamVal (L"From WinCE420"); // 파라메터 값

	CComPtr<ISoapSerializer> pSerializer   = NULL; // Soap 직렬 객체
	CComPtr<ISoapConnector>  pConnector    = NULL; // Soap 연결 객체
	CComPtr<ISoapReader>     pReader       = NULL; // Soap 읽기 객체
	CComPtr<IStream>         pIStream      = NULL; // Soap 입력 스트림
	CComPtr<IStream>         pOStream      = NULL; // Soap 출력 스트림
	CComPtr<IXMLDOMDocument> pDOMDocument  = NULL; // xml 결과 문서 객체
	CComPtr<IXMLDOMElement>  pDOMElement   = NULL; // xml 결과 부분의 항목
	CComPtr<IXMLDOMNodeList> pDOMChildNodes= NULL; // xml 결과 부분의 노드리스트
	CComPtr<IXMLDOMNode>     pDOMChildNode = NULL; // xml 결과 부분의 노드
	
	HRESULT      hr;
	VARIANT_BOOL vbSuccess;
	CComVariant  varTemp;
	CComBSTR     bstrResult;

	BSTR bstrElement=NULL;
	long lCount     =0l;
	long lIndex     =0l;


	// Soap 관련 COM 객체를 생성합니다.
	hr = pConnector .CoCreateInstance(__uuidof(HttpConnector));
	hr = pSerializer.CoCreateInstance(__uuidof(SoapSerializer));
	hr = pReader    .CoCreateInstance(__uuidof(SoapReader));
	

	// Soap 객체의 초기화를 합니다.
	hr = pConnector ->Reset();
	hr = pSerializer->reset();
	

	// Soap 객체의 속성을 지정합니다.
	hr = pConnector->put_Property(CComBSTR(L"EndPointURL"), varWsdl);
	hr = pConnector->put_Property(CComBSTR(L"SoapAction"),  varSoapAction);
	
	
	// Soap 연결 객체를 초기화하고 전송할 SOAP 메세지를 작성합니다.
	hr = pConnector->Connect();
	hr = pConnector->BeginMessage();
	
	hr = pConnector->get_InputStream(&pIStream);
	varTemp = pIStream;
	hr = pSerializer->Init(varTemp);
	
	hr = pSerializer->startEnvelope(NULL, NULL, NULL);
	hr = pSerializer->startBody    (NULL);
	hr = pSerializer->startElement (bstrMethod, NULL, NULL, NULL);
	hr = pSerializer->SoapAttribute(CComBSTR(L"xmlns"),NULL,  bstrUri, NULL);
	hr = pSerializer->startElement (bstrReqParamName, NULL, NULL, NULL);
	hr = pSerializer->writeString  (bstrReqParamVal);
	hr = pSerializer->endElement   ();
	hr = pSerializer->endElement   ();
	hr = pSerializer->endBody      ();
	hr = pSerializer->endEnvelope  ();
	
	hr = pConnector->EndMessage();
	

	// SOAP 읽기 객체로 부터 응답 메세지를 받습니다.
	hr      = pConnector->get_OutputStream(&pOStream);
	varTemp = pOStream;
	
	hr = pReader->load(varTemp, CComBSTR(L""), &vbSuccess);


	// 디버깅 창에 받은 메세지를 출력합니다.
	hr = pReader     ->get_DOM (&pDOMDocument);	
	hr = pDOMDocument->get_xml (&bstrResult);
	TRACE (L"\n# Response\n{\n%s\n}\n\n", bstrResult);


	hr = pReader       ->get_RPCResult (&pDOMElement);    // xml 결과 부분의 항목을 가져옵니다.
	hr = pDOMElement   ->get_childNodes(&pDOMChildNodes); // xml 결과 부분의 노드리스트를 가져옵니다.
	hr = pDOMChildNodes->get_length    (&lCount);
	for (lIndex=0; lIndex<lCount; lIndex++)
	{
		// xml 결과 데이터를 하나씩 읽습니다.
		hr = pDOMChildNodes->nextNode (&pDOMChildNode);
		hr = pDOMChildNode ->get_text (&bstrElement);
		
		// 리스트 박스에 결과 데이터를 추가합니다.
		SendMessage (hWnd, LB_ADDSTRING, 0, (LPARAM)bstrElement);

		SysFreeString (bstrElement);
		bstrElement = NULL;

		pDOMChildNode.Release ();
	}
}

       

실행결과
사용자 삽입 이미지




PocketPC Soap Client 작성

운영체제 한글 Pocket PC 2003
장치 iPAQ 5450
개발도구 Microsoft embedded Visual C++ 4.0 SP 2
사용언어 C++(MFC)
SDK - PocketSoap v1.5 beta 1 [Downlaod]
(http://www.pocketsoap.com/pocketsoap/)

Pocket PC에서의 Client는 MS가 만들어준 SOAP SDK가 없었습니다.
MSDN에서 찾아보니 PocketSoap이라는 외국에 어떤분께서 만들어 공개한걸 이용하라는 것만 있었습니다.
그래서, 샘플 프로그램을 만들어본 결과 WinCE.net에서 Soap Client를 만든었던것과 거의 동일하게 코드를 작성하시면 됩니다.

PocketSoap을 인스톨하고 단말기에 싱크할여 설치하면 아래의 파일들이 인스톨이 됩니다.
참고 하시고 혹시 이 PocketSoap을 이용하여 프로그램을 개발 후 배포하실때 같이 CAB으로 묶어서 배포하시면 됩니다.
  • xmltok.dll
  • xmlparse.dll
  • psoap.dll
  • pszlib.dll
  • pockethttps.dll

    그리고, 컴파일시에 필요한 파일들은 제가 따로 분리해 놓았습니다.
    그 파일들은 PocketPC 소스 폴더 하위 폴더에 있습니다.

  •  
      
    // COM 관련 클래스를 사용하기 위해 해더 파일을 인클루드합니다.
    #include <atlbase.h>
    
    // 제가 분리해놓은 파일중 이 파일 하나만 인클루드 하면됩니다.
    #include "PocketSOAP\pSOAP.h"
    
    void InvokeWebSvc (HWND hWnd)
    {
    	CComBSTR    bstrWsdl        (L"http://XXX.XXX.XXX.XXX/asp.net/svc/MyWebSvc/MyWebSvc.asmx?WSDL");
    	                            // WSDL파일 URL경로를 지정
    	CComBSTR    bstrUri         (L"http://tempuri.org/");      // uri 지정
    	CComBSTR    bstrSoapAction  (L"http://tempuri.org/Test");  // SoapAction 지정
    	CComBSTR    bstrMethod      (L"Test");        // 웹서비스 메소드 이름
    	CComBSTR    bstrReqParamName(L"message");     // 파라메터 이름
    	CComVariant varReqParamVal  (L"From PPC2K3"); // 파라메터 값
    
    	CComPtr<ISOAPEnvelope>     pEnvelope;   // PocketSoap 객체
    	CComPtr<IHTTPTransportAdv> pHttpTrans;  // Http 전송 객체
    	CComPtr<ISOAPNodes>        pReqParams;  // Request 노드 리스트
    	CComPtr<ISOAPNode>         pReqParam;   // Request 노드
    	CComPtr<ISOAPNodes>        pResParams;  // Response 노드 리스트
    	CComPtr<ISOAPNode>         pResParam;   // Response 노드 리스트
    	CComPtr<ISOAPNodes>        pChildNodes; // 결과값 노드 리스트
    	CComPtr<ISOAPNode>         pChildNode;  // 결과값 노드
    
    	HRESULT     hr;
    	CComBSTR    bstrReqXMLData; // Request  xml 문자열
    	CComBSTR    bstrResXMLData; // Response xml 문자열
    	CComVariant varValue;
    
    	long        lChildNodeCount = 0;
    	long        lIndex          = 0;
    
    
    	// Soap 관련 COM 객체를 생성합니다.
    	hr = pEnvelope .CoCreateInstance(__uuidof(CoEnvelope));
    	hr = pHttpTrans.CoCreateInstance(__uuidof(HTTPTransport));
    
    	// Soap 객체에 속성을 지정합니다.
    	hr = pEnvelope->put_EncodingStyle (CComBSTR(L""utf-8"));
    	hr = pEnvelope->SetMethod (bstrMethod, bstrUri);
    
    	hr = pEnvelope ->get_Parameters(&pReqParams); // Soap 객체에 파라메터를 지정합니다.
    	hr = pReqParams->Create(bstrReqParamName, varReqParamVal, bstrUri, NULL, NULL, NULL);
    
    	hr = pHttpTrans->put_SOAPAction(bstrSoapAction);
    
    
    	// Soap 보낼 xml 데이터를 작성합니다.
    	hr = pEnvelope ->Serialize (&bstrReqXMLData);
    	TRACE (L"# Request\n{\n%s\n}\n\n", bstrReqXMLData); // 보낼 xml 데이터 디버깅 창에 출력합니다.
    
    	// xml 데이터를 보냅니다.
    	hr = pHttpTrans->Send (bstrWsdl, bstrReqXMLData);
    
    
    	// xml 데이터를 받습니다.
    	hr = pEnvelope ->Parse(CComVariant(pHttpTrans), NULL);
    
    	hr = pEnvelope ->get_Parameters(&pResParams);
    	hr = pResParams->get_Item      (0, &pResParam);
    
    
    	hr = pEnvelope ->Serialize (&bstrResXMLData);
    	TRACE (L"# Response\n{\n%s\n}\n\n", bstrResXMLData); // 받은 xml 데이터 디버깅 창에 출력합니다.
    
    
    	hr = pResParam  ->get_Nodes (&pChildNodes);
    	hr = pChildNodes->get_Count (&lChildNodeCount);
    	for (lIndex=0; lIndex<lChildNodeCount; lIndex++)
    	{
    		// 데이터를 하나씩 읽습니다.
    		hr = pChildNodes->get_Item  (lIndex, &pChildNode);
    		hr = pChildNode ->get_Value (&varValue);
    
    		// 리스트 박스에 결과 데이터를 추가합니다.
    		SendMessage (hWnd, LB_ADDSTRING, 0, (LPARAM)varValue.bstrVal);
    
    		pChildNode.Release ();
    		varValue  .Clear   ();
    	}
    }         
    
           

    실행결과
    사용자 삽입 이미지



    Posted by 셈말짓기 :