In general, when you write an object that supports both SOAP and COM, it s preferable to avoid having to write code for special-case situations for a particular invocation method. However, there are situations when this may be necessary, and it s necessary to determine how the object was invoked.
As we mentioned in the previous section, ATL Server defaults to assuming COM invocation and only switches to using the ATL Server ISAPI-provided perthread allocator when invoked through SOAP. You can use the same method to provide a convenient way to retrieve the information about the object s invocation method. Listing 19-7 shows how to encapsulate this functionality in a base class.
// WhichInvoke.h : Declaration of the CWhichInvoke #pragma once #include "resource.h" // main symbols #include <atlsoap.h> [ emitidl(true) ]; // IWhichInvoke [ object, uuid("D15E83BF-35BB-496F-9BE9-E0260443A292"), dual, helpstring("IWhichInvoke Interface"), pointer_default(unique) ] __interface IWhichInvoke : IDispatch { [id(1)] HRESULT Test([out, retval] BSTR *InvokeType); }; // CWhichInvoke [ coclass, threading("apartment"), vi_progid("COM_OR_SOAP.WhichInvoke"), progid("COM_OR_SOAP.WhichInvoke.1"), version(1.0), uuid("8777423D-7594-4116-94B9-70CCEE66DE27"), helpstring("WhichInvoke Class"), request_handler(name="Default", sdl="GenWSDL"), soap_handler(name="WhichInvoke") ] class ATL_NO_VTABLE CWhichInvoke : public IWhichInvoke { private: BOOL m_fCOMInvoke; public: CWhichInvoke() { // default to COM m_fCOMInvoke = TRUE; } // override InitializeHandler HTTP_CODE InitializeHandler( AtlServerRequest *pRequestInfo, IServiceProvider *pProvider) { HTTP_CODE hcErr = __super::InitializeHandler(pRequestInfo, pProvider); if (hcErr == HTTP_SUCCESS) { m_fCOMInvoke = FALSE; } return hcErr; } DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct() { return S_OK; } void FinalRelease() { } [ soap_method ] HRESULT Test(BSTR *InvokeType) { if (m_fCOMInvoke) *InvokeType = SysAllocString(L"COM!"); else *InvokeType = SysAllocString(L"SOAP!"); return S_OK; } public: };
In Listings 19-8 and 19-9, the boolean variable m_fCOMInvoke defaults to TRUE . The ATL Server InitializeHandler method is overridden to set m_fCOMInvoke to FALSE , as the InitializeHandler method will only be called if the object is being invoked through SOAP. The member function IsCOMInvoke accesses the m_fCOMInvoke member.
// test.js var obj = WScript.CreateObject("COM_OR_SOAP.WhichInvoke"); var strInvoke = obj.Test(); WScript.Echo(strInvoke);
// test.cpp #define _WIN32_WINNT 0x0500 #include "WhichInvoke.h" int main() { CoInitialize(NULL); { WhichInvoke::CWhichInvoke obj; CComBSTR bstrInvoke; HRESULT hr = obj.Test(&bstrInvoke); if (SUCCEEDED(hr)) printf("%ws\n", bstrInvoke); } CoUninitialize(); }
Running test.js will output COM! and running test.cpp will output SOAP!
Earlier you learned how to return custom SOAP faults from ATL Server Web services. COM has its own error-reporting mechanisms, however, and it isn t possible to directly return SOAP faults through COM. In this section you ll examine how to combine custom SOAP fault generation with COM s IErrorInfo error reporting mechanism.
You can accomplish combining SOAP faults and COM errors by adding ISupportErrorInfo to your COM object using the support_error_info attribute. You can then call AtlReportError when generating errors to provide custom error text. To generate SOAP faults based on this custom error text, you override the GenerateAppError method for CSoapHandler , which then uses the IErrorInfo object to retrieve the custom error information and inserts it into the SOAP fault. Listing 19-10 shows how to return a custom COM error using IErrorInfo.
// ErrorExample.h : Declaration of the CErrorExample #pragma once #include "resource.h" // main symbols #include <atlsoap.h> [ emitidl(true) ]; // IErrorExample [ object, uuid("CB3722F0-D016-409C-81F8-804520B9AB1D"), dual, helpstring("IErrorExample Interface"), pointer_default(unique) ] __interface IErrorExample : IDispatch { [id(1)] HRESULT ReturnError(); }; // CErrorExample [ coclass, threading("apartment"), vi_progid("ErrorFault.ErrorExample"), progid("ErrorFault.ErrorExample.1"), version(1.0), uuid("C1457735-C0FD-4858-9A2D-42B60A4A93DE"), helpstring("ErrorExample Class"), support_error_info("IErrorExample"), request_handler(name="Default", sdl="GenWSDL"), soap_handler(name="ErrorExample") ] class ATL_NO_VTABLE CErrorExample : public IErrorExample { public: CErrorExample() { } DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct() { return S_OK; } void FinalRelease() { } void GenerateError(HRESULT hr, LPCWSTR wszErr) { AtlReportError(GUID_NULL, wszErr); } // override GenerateAppError HRESULT GenerateAppError( IWriteStream *pStream, HRESULT hr ) { CComPtr<IErrorInfo> spErr; HRESULT hrInternal = GetErrorInfo(0, &spErr); if (SUCCEEDED(hrInternal)) { CComBSTR bstrErr; hrInternal = spErr->GetDescription(&bstrErr); if (SUCCEEDED(hrInternal) && bstrErr.m_str) { return SoapFault(SOAP_E_SERVER, bstrErr, bstrErr.Length()); } } return SoapFault( SOAP_E_SERVER, L"Unexpected Error", sizeof("Unexpected Error")-1); } [ soap_method ] HRESULT ReturnError() { GenerateError(0x80040200, L"Custom Failure Text"); return 0x80040200; } public: };
Listing 19-11 shows an example of how to retrieve the information from a SOAP client.
// test.cpp #define _WIN32_WINNT 0x0500 #include "ErrorExample.h" int main() { CoInitialize(NULL); { ErrorExample::CErrorExample obj; HRESULT hr = obj.ReturnError(); if (FAILED(hr)) printf("%ws\n", obj.m_fault.m_strDetail); } CoUninitialize(); }
When called, the program will output Custom Failure Text.