Chapter 19: Advanced Web Services


NOW THAT YOU VE COVERED the basics of ATL Server Web services, you can examine more advanced areas, such as integrating Web services and COM, Web service interoperability issues, custom transports, extensibility mechanisms, and implementation internals. Familiarity with these advanced techniques and implementation details will provide you with tools to take advantage of the flexibility and power of ATL Server Web services to work around almost any of the issues you ll undoubtedly come across in writing new Web services or taking advantage of Web services in existing code.

ATL Server and COM

In this section you ll examine how you can integrate ATL Server Web services with COM. The examples and techniques described in this section apply when you re moving existing COM objects to take advantage of Web services, or when you re writing new objects that function both as COM objects and Web services.

COM Attributes

In addition to the ATL Server attributes described in earlier chapters, Visual C++ .NET provides attributes that simplify the task of creating COM objects. As with the ATL Server attributes, under the covers the COM attributes inject C++ code for the COM objects. You can view the injected code the same way you can view the ATL Server attributed injected code: by using the /Fx switch on the compiler or by going to Project Properties C/C++ Output Files and choosing Yes for Expand Attributed Source.

Listing 19-1 shows a simple attributed ATL COM object.

Listing 19.1: An Attributed COM Object
start example
 // HelloWorld.h : Defines the ATL Server request handler class  //  #pragma once  [ emitidl(true) ];  // disable warning about IRequestHandler not being emitted to IDL  #pragma warning(disable:4096)  [      uuid("14FC0AE5-0900-4D88-AB69-D721B13F5479"),      object,      // Add the following attributes      dual  ]  __interface IHelloWorldService  {      [id(1)] HRESULT HelloWorld([in] BSTR bstrInput, [out, retval] BSTR *bstrOutput);  };  [      request_handler(name="Default", sdl="GenHelloWorld1WSDL"),      soap_handler(          name="HelloWorld1Service",          namespace="urn:HelloWorld1Service",          protocol="soap"      ),      // Add the following attributes      coclass,      progid("HelloWordService.1"),      uuid("8C9D14D0-A82E-424F-9A61-6293AAE11EF0")  ]  class CHelloWorldService :      public IHelloWorldService  {  public:      DECLARE_PROTECT_FINAL_CONSTRUCT()      HRESULT FinalConstruct()      {          return S_OK;      }      void FinalRelease()      {      }      [ soap_method ]      HRESULT HelloWorld(/*[in]*/ BSTR bstrInput, /*[out, retval]*/ BSTR *bstrOutput)      {          CComBSTR bstrOut(L"Hello ");          bstrOut += bstrInput;          bstrOut += L"!";          *bstrOutput = bstrOut.Detach();          return S_OK;      }  };  // HelloWorld.cpp : Defines the entry point for the DLL application.  //  #include "stdafx.h"  // For custom assert and trace handling with WebDbg.exe  #ifdef _DEBUG  CDebugReportHook g_ReportHook;  #endif  #include "HelloWorld.h"  [ module(           name="MyHelloWorld",           type="dll",           // add the following parameters           uuid = "{7BC54312-5149-4D14-9562-0618DA510BC9}",           helpstring = "HelloWorld 1.0 Type Library"           ),  ]  class CDllMainOverride  {  public:      BOOL WINAPI DllMain(DWORD dwReason, LPVOID lpReserved)      {  #if defined(_M_IX86)          if (dwReason == DLL_PROCESS_ATTACH)          {              // stack overflow handler              _set_security_error_handler( AtlsSecErrHandlerFunc );          }  #endif          return __super::DllMain(dwReason, lpReserved);      }  }; 
end example
 

We created Listing 19-1 as a normal ATL Server Web service named HelloWorld and made the following modifications:

  • Stdafx.h was modified to remove the _ATL_NO_COM_SUPPORT macro, as you ll be using COM.

  • [ emitidl(restricted) ]; was removed from HelloWorld.h (it appears after the class definition).

  • [ emitidl(true) ]; was added to the top of HelloWorld.h.

  • The dual attribute was added to the interface definition.

  • The coclass , progid , and uuid attributes were added to the class definition.

  • The uuid and helpstring parameters were added to the module attribute.

As with the ATL Server SOAP attributes, the COM attributes make use of embedded IDL and are broken up into interface definition and class implementation. Adding Web service support to the example in Listing 19-1 would simply be a matter of adding the soap_handler and request_handler attributes to the class definition and adding soap_method attributes to the methods that are to be exposed via SOAP. You ll examine that technique in more detail in later sections of this chapter, but first we present a brief overview of the COM attributes used in the example:

  • emitidl : This attribute controls whether all attributes following its declaration will be included in the compiler-generated IDL file. atlsoap.h defaults to [emitidl(restricted)]; which can subsequently be overridden to true or false; once an emitidl with true or false has been declared, it can t be overridden.

  • dual : The compiler will include the interface on which this attribute appears in the library block of its generated IDL file.

  • coclass : This attribute marks the class on which it appears as a COM object.

  • progid : This attribute specifies the programmatic identifier (ProgID) for the COM object defined by the class on which it appears.

  • uuid : This attribute defines the universal unique identifier (UUID) for the COM object defined by the class on which it appears or, in the case of the module attribute, for the type library for the DLL.

There are additional attributes for version-independent ProgIDs, control of the threading model, and other functionality. We don t go into further detail on the COM attributes here because they don t apply to the interaction between Web services and COM. Consult the Visual Studio .NET documentation for more information.

All you need to do to add COM support to an ATL Server Web service is add the previously listed attributes. It s also just as easy to add Web service support to an attributed ATL COM object. After you create an ATL project and add a simple ATL object to it using the Add Class Wizard, simply add the soap_handler and request_handler attributes to the coclass definition. Listing 19-2 presents an example.

Listing 19.2: A Dual COM Object and Web Service
start example
 // MyObject.h : Declaration of the CMyObject  #pragma once  #include "resource.h"       // main symbols  #include <atlsoap.h>  [ emitidl(true) ];  // IMyObject  [      object,      uuid("0CA838BD-9423-4B78-8B3A-1E4B9698525B"),      dual,    helpstring("IMyObject Interface"),      pointer_default(unique)  ]  __interface IMyObject : IDispatch  {      [id(1)] HRESULT HelloWorld([in] BSTR bstrIn, [out, retval] BSTR *bstrOut);  };  // CMyObject  [      coclass,      threading("apartment"),      vi_progid("BasicCOM.MyObject"),      progid("BasicCOM.MyObject.1"),      version(1.0),      uuid("D958904B-F324-496A-9DB9-016F01D5BAF3"),      helpstring("MyObject Class"),      // Add these additional attributes      request_handler(name="Default", sdl="GenWSDL"),      soap_handler(name="MyObject")  ]  class ATL_NO_VTABLE CMyObject :      public IMyObject  {  public:      CMyObject()      {      }      DECLARE_PROTECT_FINAL_CONSTRUCT()      HRESULT FinalConstruct()      {          return S_OK;      }      void FinalRelease()      {      }      // add the soap_method attribute to the interface method implementation      [ soap_method ]      HRESULT HelloWorld(BSTR bstrIn, BSTR *bstrOut)      {          CComBSTR bstrRet = L"Hello ";          bstrRet+= bstrIn;          bstrRet+= "!";          *bstrOut = bstrRet.Detach();          return S_OK;      }  public:  }; 
end example
 

The project in Listing 19-2 was generated using the ATL Server Project Wizard and then using the Add Class Wizard to add an ATL simple object named MyObject . To add Web service support, the request_handler and soap_handler attributes were added.

You can now invoke the object through COM:

 Dim obj  set obj = WScript.CreateObject( "BasicCOM.MyObject" )  strRet = obj.HelloWorld("Joe")  WScript.Echo(strRet) 

And you can invoke it through SOAP:

 #define _WIN32_WINNT 0x0500  #include <stdio.h>  #include "MyObject.h"  int main()  {      CoInitialize(NULL);      {          MyObject::CMyObject obj;          CComBSTR bstrRet;          HRESULT hr = obj.HelloWorld(CComBSTR("Joe"), &bstrRet);          if (SUCCEEDED(hr))          {              printf("%ws\n", bstrRet);          }      }      CoUninitialize();  } 

Objects that expose user -defined data types have a little more work to do. You ll examine this in next section.

User-Defined Types

The same type limitations on ATL Server Web services described in the earlier chapters still apply. When you use user-defined types (UDTs), you need to add the export attribute to the type definition if you want the type to be visible in the type library. In Listing 19-3, the enumeration MyEnumeration used by the COM object has the export attribute so that MyEnumeration will appear in the type library.

Listing 19.3: Web Service Using an Enumeration
start example
 // EnumObject.h : Declaration of the CEnumObject  #pragma once  #include "resource.h"       // main symbols  #include <atlsoap.h>  [ emitidl(true) ];  [ export ]  enum MyEnumeration  {      Value1,      Value2,      Value3  };  // IEnumObject  [      object,      uuid("F3AF9E86-98ED-4B2F-8A5B-7439745ED7E9"),      dual,    helpstring("IEnumObject Interface"),      pointer_default(unique)  ]  __interface IEnumObject : IDispatch  {      [id(1)] HRESULT EnumTest(          [in] MyEnumeration eIn,          [out, retval] MyEnumeration *eOut);  };  // CEnumObject  [      coclass,      threading("apartment"),      vi_progid("EnumExample.EnumObject"),      progid("EnumExample.EnumObject.1"),      version(1.0),      uuid("CADF04A4-6023-490A-827D-7E35F069FEFA"),      helpstring("EnumObject Class"),      request_handler(name="Default", sdl="GenWSDL"),      soap_handler(name="EnumObject")  ]  class ATL_NO_VTABLE CEnumObject :      public IEnumObject  {  public:      CEnumObject()      {      }      DECLARE_PROTECT_FINAL_CONSTRUCT()      HRESULT FinalConstruct()      {          return S_OK;      }      void FinalRelease()      {      }      [ soap_method ]      HRESULT EnumTest(MyEnumeration eIn, MyEnumeration *eOut)      {          *eOut = eIn;          return S_OK;      }  public:  }; 
end example
 

Similarly, structs used in the COM object should have the export attribute if they re to appear in the type library. In Listing 19-4, the struct MyStruct used by the COM object has the export attribute.

Listing 19.4: Web Service Using Structs
start example
 // StructObject.h : Declaration of the CStructObject  #pragma once  #include "resource.h"       // main symbols  #include <atlsoap.h>  [ emitidl(true) ];  [ export ]  struct MyStruct  {      int n;      BSTR s;  };  // IStructObject  [      object,      uuid("4933C2D4-EA9A-4988-A163-E19BA560AE16"),      dual,    helpstring("IStructObject Interface"),      pointer_default(unique)  ]  __interface IStructObject : IDispatch  {      [id(1)] HRESULT StructTest(          [in] MyStruct tIn,          [out, retval] MyStruct *tOut);  };  // CStructObject  [      coclass,      threading("apartment"),      vi_progid("StructExample.StructObject"),      progid("StructExample.StructObject.1"),      version(1.0),      uuid("CD55DAD9-597B-4652-BAC7-2E8CD5AA7BB0"),      helpstring("StructObject Class"),      request_handler(name="Default", sdl="GetWSDL"),      soap_handler(name="StructObject")  ]  class ATL_NO_VTABLE CStructObject :      public IStructObject  {  public:      CStructObject()      {      }      DECLARE_PROTECT_FINAL_CONSTRUCT()      HRESULT FinalConstruct()      {          return S_OK;      }      void FinalRelease()      {      }      [ soap_method ]      HRESULT StructTest(MyStruct tIn, MyStruct *tOut)      {          tOut->n = tIn.n;          tOut->s = tIn.s ? SysAllocString(tIn.s) : NULL;          return S_OK;      }  public:  }; 
end example
 

IDispatch Types

The majority of IDispatch types are supported; however, common types such as VARIANT , IDispatch* , IUnknown* , and SAFEARRAY s aren t supported, as they fall into one of the categories of unsupported types described in earlier chapters. It s best to deal with VARIANT and SAFEARRAY in the same way you do other unsupported data types: Try to convert them into a supported data type for the SOAP interface. For IDispatch* and IUnknown* , there s no general workaround. When the IDispatch object is effectively a data object (such as a scripting data object), it can be converted into a supported type (such as a struct) before making the call to SOAP.

In Listings 19-5 and 19-6, the fields of MyStruct are converted from the properties of the passed-in IDispatch object before being passed to the exposed SOAP method. They re then converted back into an IDispatch object with the appropriate properties.

Listing 19.5: Web Service Functioning as a Scriptable Object in JScript
start example
 // test.js  function MyStruct(n, s)  {      this.s = s;      this.n = n;  }  var obj = WScript.CreateObject("DispExample.DispWrapperObject");  var tIn = new MyStruct(1, "Joe");  var tOut = obj.DispTest(tIn);  WScript.Echo( tOut.n + ", " + tOut.s ) 
end example
 
Listing 19.6: Web Service Functioning as a Scriptable Object
start example
 // DispWrapperObject.h : Declaration of the CDispWrapperObject  #pragma once  #include "resource.h"       // main symbols  #include <atlsoap.h>  [ emitidl(true) ];  [ export ]  struct MyStruct  {      int n;      BSTR s;  };  // IDispWrapperObject  [      object,      uuid("879E3783-0AF9-43D2-85C3-66AF07CFC4BD"),      dual,    helpstring("IDispWrapperObject Interface"),      pointer_default(unique)  ]  __interface IDispWrapperObject : IDispatch  {      [id(1)] HRESULT StructTest(          [in] MyStruct tIn,          [out, retval] MyStruct *tOut);      [id(2)] HRESULT DispTest(          [in] IDispatch *tIn,          [out, retval] IDispatch **tOut);  };  // CDispWrapperObject  inline HRESULT GetDispProperty(      IDispatch *pDispatch,      LPCWSTR wszName,      VARIANT *pvOut)  {      HRESULT hr;      if (!pDispatch  !wszName)          return E_INVALIDARG;      if (!pvOut)          return E_POINTER;      DISPID dispid;      hr = pDispatch->GetIDsOfNames(          IID_NULL, (LPOLESTR *)&wszName, 1, NULL, &dispid);      if (FAILED(hr))          return hr;      DISPPARAMS EmptyParams;      EmptyParams.cArgs = 0;      EmptyParams.cNamedArgs = 0;      EmptyParams.rgdispidNamedArgs = NULL;      EmptyParams.rgvarg = NULL;      hr = pDispatch->Invoke(          dispid, IID_NULL, NULL, DISPATCH_PROPERTYGET,          &EmptyParams, pvOut, NULL, NULL);      if (FAILED(hr))          return hr;      return S_OK;  }  class CDispatchBag : public IDispatch, public CComObjectRoot  {  private:      struct CProp      {          int m_nCnt;          CComVariant m_var;          CStringW m_str;          CProp(LPCWSTR wsz, int nCnt)              : m_str(wsz), m_nCnt(nCnt)          {          }      };      CAtlArray<CProp> m_props;  public:      BEGIN_COM_MAP(CDispatchBag)          COM_INTERFACE_ENTRY(IDispatch)      END_COM_MAP()      VARIANT * AddProperty(LPCWSTR wszName)      {          VARIANT *pRet = NULL;          _ATLTRY          {              size_t nCount = m_props.GetCount();              CProp prop(wszName, (int) nCount);              size_t nIndex = m_props.Add(prop);              if (nCount < m_props.GetCount())              {                  pRet = &(m_props[nIndex].m_var);              }          }          _ATLCATCH( e )          {              e;          }          return pRet;      }      virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(          UINT *pctinfo)      {          if (!pctinfo)              return E_INVALIDARG;          *pctinfo = 0;          return S_OK;      }      virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(          UINT iTInfo,          LCID lcid,          ITypeInfo **ppTInfo)      {          if (!ppTInfo)              return E_INVALIDARG;          *ppTInfo = NULL;          return DISP_E_BADINDEX;      }      virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(          REFIID riid,          LPOLESTR *rgszNames,          UINT cNames,          LCID lcid,          DISPID *rgDispId)      {          HRESULT hr = S_OK;          for (UINT i=0; i<cNames; i++)          {              bool bFound = false;              for (size_t nIndex=0; nIndex<m_props.GetCount(); nIndex++)              {                  if (m_props[nIndex].m_str == rgszNames[i])                  {                      rgDispId[i] = (DISPID)nIndex;                      bFound = true;                      break;                  }              }              if (!bFound)              {                  rgDispId[i] = DISPID_UNKNOWN;                  hr = DISP_E_UNKNOWNNAME;              }          }          return hr;      }      virtual HRESULT STDMETHODCALLTYPE Invoke(          DISPID dispIdMember,          REFIID riid,          LCID lcid,          WORD wFlags,          DISPPARAMS *pDispParams,          VARIANT *pVarResult,          EXCEPINFO *pExcepInfo,          UINT *puArgErr)      {          if (dispIdMember < 0  dispIdMember > (long)m_props.GetCount())              return DISP_E_MEMBERNOTFOUND;          if (wFlags != DISPATCH_PROPERTYGET)              return DISP_E_MEMBERNOTFOUND;          if (!pVarResult  !pDispParams)              return E_INVALIDARG;          return VariantCopy(pVarResult, &m_props[dispIdMember].m_var);      }  }; // class CDispatchBag  [      coclass,      threading("apartment"),      vi_progid("DispExample.DispWrapperObject"),      progid("DispExample.DispWrapperObject.1"),      version(1.0),      uuid("99843466-E53E-49C3-9191-0E41A3D427DF"),      helpstring("DispWrapperObject Class")  ]  class ATL_NO_VTABLE CDispWrapperObject :      public IDispWrapperObject  {  public:      CDispWrapperObject()      {      }      DECLARE_PROTECT_FINAL_CONSTRUCT()      HRESULT FinalConstruct()      {          return S_OK;      }      void FinalRelease()      {      }      [ soap_method ]      HRESULT StructTest(MyStruct tIn, MyStruct *tOut)      {          tOut->n = tIn.n;          tOut->s = tIn.s ? SysAllocString(tIn.s) : NULL;          return S_OK;      }      // NOTE: the soap_method attribute is omitted here      //       so this method is not exposed via SOAP      HRESULT DispTest(IDispatch *tIn, IDispatch **tOut)      {          CComVariant vInt;          CComVariant vStr;          MyStruct structIn;          MyStruct structOut;          *tOut = NULL;          HRESULT hr = GetDispProperty(tIn, L"n", &vInt);          if (FAILED(hr))              return hr;          if (vInt.vt != VT_INT && vInt.vt != VT_UI1 &&              vInt.vt != VT_I2 && vInt.vt != VT_I4 &&              vInt.vt != VT_UI2 && vInt.vt != VT_UI4 &&              vInt.vt != VT_UINT)              return E_INVALIDARG;          structIn.n = vInt.intVal;          hr = GetDispProperty(tIn, L"s", &vStr);          if (FAILED(hr))              return hr;          if (vStr.vt != VT_BSTR)              return E_INVALIDARG;          structIn.s = vStr.bstrVal;          hr = StructTest(structIn, &structOut);          if (FAILED(hr))              return hr;          CComObject<CDispatchBag> *pDispBag = new CComObject<CDispatchBag>;          if (pDispBag)          {              pDispBag->AddRef();              VARIANT *pVar = pDispBag->AddProperty(L"n");              if (pVar)              {                  pVar->vt = VT_INT;                  pVar->intVal = structOut.n;                  pVar = pDispBag->AddProperty(L"s");                  if (pVar)                  {                      pVar->vt = VT_BSTR;                      pVar->bstrVal = structOut.s;                      *tOut = static_cast<IDispatch*>(pDispBag);                      return S_OK;                  }              }              pDispBag->Release();          }          if (structOut.s)              SysFreeString(structOut.s);          return E_OUTOFMEMORY;      }  public:  }; 
end example
 

Allocation

Earlier, you learned the proper way to allocate data for Web services using the GetMemMgr()- > Allocate function. When it s invoked through SOAP, GetMemMgr()- > Allocate will allocate data using a per-thread heap provided by the ATL Server ISAPI infrastructure. However, when an object is invoked through COM, data needs to be allocated differently. COM allocations must be done through the COM allocator functions: CoTaskMemAlloc , CoTaskMemFree , and CoTaskMemRealloc . Luckily, no special-case code is required to make this work. ATL Server defaults to using the COM allocator functions and only changes to the ATL Server ISAPI per-thread heap when explicitly invoked through SOAP. Users should continue to use the GetMemMgr()- > Allocate and GetMemMgr()- > Free functions for memory management, and ATL Server will ensure the correct allocator is used for each invocation method.

This brings up the issue of how ATL Server determines whether an object is being invoked through COM or whether it s being invoked through SOAP. We address this issue in the next section.




ATL Server. High Performance C++ on. NET
Observing the User Experience: A Practitioners Guide to User Research
ISBN: B006Z372QQ
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net