Loosely Coupled Transactor Client 1

Loosely Coupled Transactor Client [1]

[1] Portions of the following text are used by permission from Pranish Kumar and Bogdan Crivat, "SOAP: Using ATL Server to Build an Asynchronous SOAP Client in Unmanaged C++," MSDN Magazine , April 2002, http://msdn.microsoft.com/msdnmag/issues/02/04/SOAP/default.aspx.


Provide asynchronous services to those systems whose installed clients can be edited or created without the need for .NET on the server or client, or a messaging system of any kind. These clients include Windows 2000 or Windows XP without the .NET runtime. Utilize multithreading to mimic asynchronous behavior on a client.


The reason for implementing this pattern is that, like the LCT Server, an application can greatly benefit from adding asynchronous-type behavior to a design and can do so in a nonintrusive manner. Fortunately, just as the LCT Server above is a simplified "afterthought," the LCT Client provides the developer with a client-side "add-on" that should not disrupt an existing client implement. However, unlike the LCT Server, this pattern is typically deployed on the client and may require more deployment time. Despite this limitation, this offers a highly controllable means of calling Web services (or any external call) in a loosely coupled fashion. This also provides a solution for those installed applications that do not have the option of changing the server or calling a Web service and do not have .NET installed on the client. Finally, it does not require any messaging software to work.


Use the LCT Server pattern when:

  • Adding asynchronous services to an existing synchronous system.

  • Adding client notification through messaging software is neither practical nor possible.

  • When clients do not have .NET installed or you do not wish to add asynchronous logic at the server level.

  • Client workstations are efficient enough to take advantage of a multithreaded process (not recommended for older desktop footprints).

  • Adding multithreaded features is a supportable option for the environment.


Just like the LCTServer pattern, the structure begins with any client (does not have to be .NET) that wishes to execute a synchronous operation a synchronously. The class diagram (Figure 7.7) shows this as being the LCTClientMain entity. In the sample code, this is a simple console application written in Visual C++ 7.0. It has two main purposes. First it provides an implementation of a generic callback function whose pointer is passed to the LCTClient during preparation and later called during execution. Second, it calls the Web service using an instantiated object declared as a type, employing a generated ATL template. The ATL template is generated from SPROXY.EXE (Visual C++ 7.0) and provides a custom ATL-based proxy by which to call any Web service method from the client. The details of the implementation are in the following section. Other than that, most of the work is done by the ATL framework, the custom CLCTClientSoapSocket class, and the template it uses, called CSoapSocketClientT .

Figure 7.7. LCT Client generic class diagram.


Another feature used in this pattern is that of a ThreadPool object. Although one cannot effectively implement this pattern without the use of mutithreading, employing an actual "thread pool" is optional. However, once again, ATL does most of the work, so why not ? The ThreadPool object is just like any other object used by the LCTClientMain to create and initialize the pool and add each Web service proxy object to it, just like any array objects. The core of this pattern is rather simple but it's the technology behind it that makes this a more advanced pattern implementation. This pattern acts as hybrid between an architecture and an implementation pattern, due to its technological particularities.

Challenge 7.3

Does running an application written in Visual C++ 7.0 require that .NET be installed at runtime?

Even if new ATL features are used from Visual C++ 7.0?

The answer is no for both. Even though the LCTClient is written in Visual C++ 7.0, this sample will run in an environment without .NET!


The LCT Client pattern has the following benefits and liabilities:

  • Abstracts the implementation of a synchronous model from an asynchronous model . Using this pattern, the LCT Client can be easily added to your existing synchronous system without direct impact on the existing system. Like the LCT Server, this is perfect for those systems that have already been implemented and deployed. It also acts as a "wrapper" around client-based synchronous methods by providing another layer that can be called by non-.NET clients synchronously. The synchronous request is then immediately added to a thread pool and called in a background thread that makes the request to the server. Unlike the LCT Server, the abstraction may be limited in the implementation of the callback. Each callback may contain specific business logic, and several callbacks may have to be implemented. The real abstraction lies in the delegated call to the server, using a background thread to mimic asynchronous behavior.

  • Provides a generic facility in which to use callbacks . When dealing with any asynchronous design, the programmer typically has initially two major choices to make on the model. The first is to employ "callbacks," which will call back to an originating interface when the asynchronous method has completed. The other choice is to poll. This is what is employed for the LCT pattern. Each has its advantages and disadvantages. Polling is simpler to implement but can be less efficient, depending on how often the server must be polled to obtain a transaction status.

    Callbacks can be more efficient but they require a client system that has the actual ability to receive the callback. This is where this pattern fits in. Each call runs in a separate thread from the main process and upon completion invokes the callback passed into the LCTClient during preparation. This provides the most efficient means of communicating from a client to the status of the original transaction without the use of messaging software. The only drawback is that it runs on the client, must be installed there, and is more complicated than simple polling, as explained in the previous pattern.

  • May require more deployment investment . Because each client that wishes to take advantage of this new feature must install it, it may require more investment for IT support. For large-scale clients, deploying this may not be the best option.

    Any client installation has its headache , and this is no exception because each client must have the ability to run multiple threads for it to work.


  • LCTClientMain (same) ” This represents any calling client. This client is a simple console application that drives CExternalServiceT by calling GetTransaction() several times to mimic several requests . Each request will then be added to a thread pool and executed asynchronously in a background thread, one by one. As each transaction completes, a callback will be called, which is also implemented here.

  • CExternalServiceT (same) ” This is the generated ATL proxy for the Web service that will be called using LCTClientMain. It will contain all of the method calls implemented in the Web service. This can be any generated ATL proxy calling any Web service. The GetTransaction() method is implemented here and forwarded to the ExternalService Web service. A call to PrepareRequest() is made on this template but forwarded to CLCTClientSoapSocket, which is passed in during the CExternalServiceT's declaration. It is during PrepareRequest() that the thread pool initializes to be handled later. Fortunately, little work has been put forth in creating this class. To create it, simply use SPROXY.EXE or use Visual Studio .NET to generate it from your workspace environment. Once generated, the namespace declaration is removed, along with two helper methods (at the end of the generated source code) that are created and not used in this implementation.


    Keep in mind that this application may need to run in an environment that does not have .NET installed. Using the implementation as is will guarantee that.

  • CLCTClientThreadPool (same) ” This simply represents the thread pool used for this implementation and can be considered optional if pools are not required. However, its implementation is simple enough to keep using it, requiring only initialization and shutdown to be functional.

  • CLCTClientWorker (same) ” This is the actual worker for the thread invoking the Web service through the LCTClient.

  • CLCTClientSoapSocket (same) ” This is the heart of the pattern in that it leverages the ATL template CSoapSocketClientT and ATL thread pooling to invoke any Web service in asynchronous threaded-fashion. It is passed as a declaration parameter to the CExternalServiceT template and used for the preparation and actual asynchronous invocations.

  • CSoapSocketClientT (same) ” A part of ATL, it handles all SOAP-based communications with external Web services and SOAP servers via TCP/IP Sockets. All low-level communications to the Web service use this.


One of the primary benefits of this pattern is its ability to be seamlessly added to an efficient asynchronous callback mechanism for any client. However, this pattern does require some understanding beyond just that of C# or VB.NET. For those aching to get their hands dirty with C++, this can be great way to dive in. To start things off, let's first look the LCTClientMain console application (Listing 7.17).

Listing 7.17 LCT Client C++ console implementation.
 #include "stdafx.h" #include "ExternalService.h" #include "LCTClient.h" #define CLIENTS_COUNT 8 struct stCallbackParam {    int   iServerIndex;    LPLONGlpCompletionCounter; }; /* //This is the callback that is registered with the LCTClient // class during PrepareAsync */ void LCTCallback(LPARAM lParam, HRESULT hRet) { stCallbackParam*param= reinterpret_cast<stCallbackParam*>(lParam);    ATLASSERT(param);    // increment the shared counter    InterlockedIncrement(param->lpCompletionCounter);    // do something useful with the HRESULT    printf("\n[Client %d] - Async Result : %s",       param->iServerIndex,          SUCCEEDED(hRet)?"SUCCEEDED":"FAILED"); } /* // This just drives the LCT Client to execute GetTransaction from // an external web service pooling up // several clients in the process.  The number of clients is // configurable. */ int _tmain(int argc, _TCHAR* argv[])       LCTClientThreadPoolthreadPool;       tCallbackParam    paramsExternalService[CLIENTS_COUNT];       BSTR   bstrRet;       int      iIndex = 0;       int      iPoolSize = CLIENTS_COUNT;       HRESULT  hRet;       LONG   lCompletionCounter = 0;       int nCallCounter = 0;       CoInitialize(NULL);       // Initialize the tread pool to 4 threads per CPU       threadPool.Initialize(0, -4);    {       // allocate an array of the number of pooled    // clients you want       CExternalServiceT<CLCTClientSoapSocket> <- no line break    proxiesExternalService[CLIENTS_COUNT];    // loop through each imaginary client, prepare request, and    // call asynchronously using LCT client for heavy lifting       for(iIndex = 0; iIndex < iPoolSize; iIndex++)       {          // server index can be replaced by some unique    // cookie/handle for async ops          paramsExternalService[iIndex].iServerIndex =    iIndex + 1;    paramsExternalService[iIndex].lpCompletionCounter =    &lCompletionCounter;          // pass in an initialized threadpool (the number is    // up to you), the callback and the parameters          hRet = proxiesExternalService[iIndex].       PrepareRequest(&threadPool,             LCTCallback,       (LPARAM)&paramsExternalService[iIndex]);          ATLASSERT(SUCCEEDED(hRet));          // just call the sample GetTransaction Web service       // method, the param will be the return       // value OUT of that call          hRet = proxiesExternalService[iIndex]. GetTransaction(&bstrRet);          // if E_PENDING things are working..          ATLASSERT(E_PENDING == hRet);       }       // Request in work, yet       do       {          Sleep(50);          printf(".");       }       while(lCompletionCounter < CLIENTS_COUNT);       // call callbacks have been called we can continue and    // receive the operation return values       for(iIndex=0; iIndex<CLIENTS_COUNT; iIndex++)       {          printf("\nClient %d : ", iIndex + 1);          // Back from the request          // Collect the results now          // Pass only the OUT and IN/OUT params          // The rest will be ignored          hRet =       proxiesExternalService[iIndex].       GetTransaction(&bstrRet);          // now really analyze the results          if(SUCCEEDED(hRet))          {             nCallCounter++;             printf("Requests Succeeded. Result :\n %ls",          bstrRet);             ::SysFreeString(bstrRet);          }          else             printf("Request Failed");       }       if(nCallCounter == CLIENTS_COUNT)       {          ::MessageBox(NULL, _T("All async call s completed."),       _T("Status"),MB_OK);       }    }    // make sure and clean up the threadpool    printf("\n");    threadPool.Shutdown();    CoUninitialize();    return 0; } 

As discussed in the Participants section, any working client can replace this entity. This is only a sample driver of the pattern. Its primary duties are to declare the ExternalServiceT template by passing in the LCTClientSoapSocket class, intializing the thread pool, calling PrepareRequest, executing the Web service iteratively, and implementing a callback to be called upon completion of the original transaction. How this is implemented exactly is up to the developer. The real work happens in the CLCTClientSoapSocket class in Listing 7.18.

Listing 7.18 LCT Client "plumbing" implementation.
 #include <atlsoap.h> #define   LCTCLIENT_EXECUTION   101 class CLCTClientSoapSocket; typedef void (*SOAP_ASYNC_CALLBACK)(LPARAM lParam, HRESULT hRet); structstLCTClientRequest {    CLCTClientSoapSocket   *pClient;    SOAP_ASYNC_CALLBACK       pfnCallback;    LPARAM                    lpRequestParam; }; class CLCTClientWorker { public: typedef stLCTClientRequest* RequestType;    CLCTClientWorker() throw()    {    }    ~CLCTClientWorker() throw()    {    }    virtual BOOL Initialize(void *pvParam) throw()    {       // Do any initialization here       return TRUE;    }    virtual void Terminate(void* pvParam) throw()    {       // Do any cleanup here    }    void Execute(stLCTClientRequest *pRequestInfo,    void *pvParam, OVERLAPPED *pOverlapped) throw(); }; typedef CThreadPool<CLCTClientWorker>CLCTClientThreadPool; class CLCTClientSoapSocket : public CSoapSocketClientT<> {    protected:    stLCTClientRequest   m_stRequestInfo;    bool                 m_bInRequest;    HRESULT              m_hRealRet;    CLCTClientThreadPool*m_pThreadPool;       CStringm_strSOAPAction; public:       CLCTClientSoapSocket(LPCTSTR szUrl) :       CSoapSocketClientT<>(szUrl)       {          m_bInRequest = false;          m_pThreadPool = NULL;       }       // Note : this is not thread safe, the caller should take // care       HRESULT PrepareRequest(CLCTClientThreadPool *pThreadPool,       SOAP_ASYNC_CALLBACK pfnCallback, LPARAM lParam)       {          // return false, if already in request          if(m_bInRequest)             return E_PENDING;       if(!pThreadPool)             return E_POINTER;          m_pThreadPool = pThreadPool;          m_stRequestInfo.pClient = this;          m_stRequestInfo.lpRequestParam= lParam;          m_stRequestInfo.pfnCallback=pfnCallback;          m_bInRequest = true;          return S_OK;       }       HRESULT SendRequest(LPCTSTR szAction) {          HRESULT   hRet;          if(m_bInRequest)          {             // launch the async request             m_strSOAPAction = szAction;             ATLASSERT(m_pThreadPool);             if(m_pThreadPool->QueueRequest(&m_stRequestInfo))                hRet = E_PENDING;             else                hRet = E_FAIL;       }       else       {          // post-async request, reading the response.          // Nothing to do, just return hRet from the    // real operation          hRet = m_hRealRet;       }       return hRet;    }    HRESULT InternalSendRequest()    {       m_hRealRet = CSoapSocketClientT<>::SendRequest(m_strSOAPAction);          m_bInRequest = false;          return m_hRealRet;       } }; inline void CLCTClientWorker::Execute(stLCTClientRequest *pRequestInfo, void *pvParam, OVERLAPPED *pOverlapped) throw() {       ATLASSERT(pRequestInfo != NULL);       pvParam;   // unused       pOverlapped;   // unused       CLCTClientSoapSocket *pClient = pRequestInfo->pClient;          SOAP_ASYNC_CALLBACK pfnCallBack =       pRequestInfo->pfnCallback;       LPARAM lParam = pRequestInfo->lpRequestParam;       ATLASSERT(pClient);       HRESULT hRet = pClient->InternalSendRequest();       if(pfnCallBack)       {          pfnCallBack(lParam, hRet);       } } 

This piece of code is rather simple and mainly leverages the use of "thread pools" (see Visual Studio .NET documentation for details on thread pooling) for delegating each synchronous call from the pool onward to the Web service. When a send request is called, what would normally be a synchronous call, instead of immediately calling GetTransaction(), the call is queued using the Thread Pool object. Once on the queue, it will be later called by the Thread worker, using CLCTClientWork::Execute() (Listing 7.18). From here, all the work has already been performed in relation to this pattern, and it is simply delegated to SoapSocketClient::SendRequest() that sends the transaction as it would during a normal synchronous execution. To see how the LCTClient class is passed into the generated ExternalServiceT proxy, you have to look at the generated code to appreciate what is going on. In Listing 7.19, you'll find the generated ATL template for the ExternalService Web service that contains GetTransaction() and any other method exposed publicly from the Web service.

Listing 7.19 LCT Client "Plumbing" Implementation “ Template Code.
 #include <atlsoap.h> struct TransactorHeader {    __int64 lTransactorId; }; template <typename TClient = CSoapSocketClientT<> > class CExternalServiceT :    public TClient,    public CSoapRootHandler { public:          //          // SOAP headers  used for passed transaction id for ease          //          TransactorHeader TransactorHeader0; protected:    const _soapmap ** GetFunctionMap();    const _soapmap ** GetHeaderMap();    void * GetHeaderValue();    const wchar_t * GetNamespaceUri();    const char * GetServiceName();    const char * GetNamespaceUriA();    HRESULT CallFunction(void *pvParam,       const wchar_t *wszLocalName, int cchLocalName,       size_t nItem);    HRESULT GetClientReader(ISAXXMLReader **ppReader); public:    . . .    CExternalServiceT(ISAXXMLReader *pReader = NULL)       :TClient(_T("http://localhost/. . ./ExternalService/ExternalService.asmx"))    {       SetClient(true);       SetReader(pReader);       //       // initialize headers       //       memset(&TransactorHeader0, 0x00, sizeof(TransactorHeader));    }    ~CExternalServiceT()    {       Uninitialize();    }    void Uninitialize()    {       UninitializeSOAP();       //       // uninitialize headers       //       AtlCleanupValueEx(&TransactorHeader0, GetMemMgr());    }    HRESULT InvokeTimeConsumingMethod(BSTR* InvokeTimeConsumingMethodResult);    HRESULT GetTransaction(BSTR* GetTransactionResult); }; typedef CExternalServiceT<> CExternalService; __if_not_exists(__TransactorHeader_entries) { extern __declspec(selectany) const _soapmapentry __TransactorHeader_entries[] = {       {          0xC0792CDA,          "lTransactorId",          L"lTransactorId",          sizeof("lTransactorId")-1,          SOAPTYPE_LONG,          SOAPFLAG_FIELD,          offsetof(TransactorHeader, lTransactorId),          NULL,          NULL,          -1       },       { 0x00000000 } }; extern __declspec(selectany) const _soapmap __TransactorHeader_map = {          0x8AA088EA,          "TransactorHeader",          L"TransactorHeader",          sizeof("TransactorHeader")-1,          sizeof("TransactorHeader")-1,          SOAPMAP_STRUCT,          __TransactorHeader_entries,          sizeof(TransactorHeader),          1,          -1,          SOAPFLAG_NONE,          0xC2E575C3,          "http://tempuri.org/",          L"http://tempuri.org/",          sizeof("http://tempuri.org/")-1 }; } struct __CExternalService_InvokeTimeConsumingMethod_struct {       BSTR InvokeTimeConsumingMethodResult; }; . . . struct __CExternalService_GetTransaction_struct {       BSTR GetTransactionResult; }; extern __declspec(selectany) const _soapmapentry __CExternalService_GetTransaction_entries[] = {       {          0xE7350D25,          "GetTransactionResult",          L"GetTransactionResult",          sizeof("GetTransactionResult")-1,          SOAPTYPE_STRING,          SOAPFLAG_NONE  SOAPFLAG_OUT  SOAPFLAG_PID  SOAPFLAG_DOCUMENT  SOAPFLAG_LITERAL  SOAPFLAG_NULLABLE, offsetof(__CExternalService_GetTransaction_struct, GetTransactionResult),          NULL,          NULL,          -1,       },       { 0x00000000 } }; extern __declspec(selectany) const _soapmap __CExternalService_GetTransaction_map = {       0x88540AB5,       "GetTransaction",       L"GetTransactionResponse",       sizeof("GetTransaction")-1,       sizeof("GetTransactionResponse")-1,       SOAPMAP_FUNC,       __CExternalService_GetTransaction_entries,       sizeof(__CExternalService_GetTransaction_struct),       1,       -1, SOAPFLAG_NONE  SOAPFLAG_PID  SOAPFLAG_DOCUMENT  SOAPFLAG_LITERAL,       0xC2E575C3,       "http://tempuri.org/",       L"http://tempuri.org/",       sizeof("http://tempuri.org/")-1 }; extern __declspec(selectany) const _soapmap * __CExternalService_funcs[] = {       &__CExternalService_InvokeTimeConsumingMethod_map,       &__CExternalService_GetTransaction_map,       NULL };    // cleanup any in/out-params and out-headers from previous calls    Cleanup();    __atlsoap_hr = BeginParse(__atlsoap_spReadStream);    if (FAILED(__atlsoap_hr))    {       SetClientError(SOAPCLIENT_PARSE_ERROR);       goto __cleanup;    } . . . template <typename TClient> inline HRESULT CExternalServiceT<TClient>::GetTransaction(BSTR* GetTransactionResult) { HRESULT __atlsoap_hr = InitializeSOAP(NULL);    if (FAILED(__atlsoap_hr))    {       SetClientError(SOAPCLIENT_INITIALIZE_ERROR);       return __atlsoap_hr;    }    CleanupClient(); CComPtr<IStream> __atlsoap_spReadStream;    __CExternalService_GetTransaction_struct __params;    memset(&__params, 0x00, sizeof(__params));    __atlsoap_hr = SetClientStruct(&__params, 1);    if (FAILED(__atlsoap_hr))    {       SetClientError(SOAPCLIENT_OUTOFMEMORY);       goto __skip_cleanup;    }    __atlsoap_hr = GenerateResponse(GetWriteStream());    if (FAILED(__atlsoap_hr))    {       SetClientError(SOAPCLIENT_GENERATE_ERROR);       goto __skip_cleanup;    }    __atlsoap_hr = SendRequest(_T("SOAPAction: \"http:// . . ./GetTransaction\"\r\n"));    if (FAILED(__atlsoap_hr))    {       goto __skip_cleanup;    }    __atlsoap_hr = GetReadStream(&__atlsoap_spReadStream);    if (FAILED(__atlsoap_hr))    {       SetClientError(SOAPCLIENT_READ_ERROR);       goto __skip_cleanup;    }    // cleanup any in/out-params and out-headers from previousCleanup();    __atlsoap_hr = BeginParse(__atlsoap_spReadStream);    if (FAILED(__atlsoap_hr))    {       SetClientError(SOAPCLIENT_PARSE_ERROR);       goto __cleanup;    }    *GetTransactionResult = __params.GetTransactionResult;    goto __skip_cleanup; __cleanup:    Cleanup(); __skip_cleanup:       ResetClientState(true);       memset(&__params, 0x00, sizeof(__params));       return __atlsoap_hr; } template <typename TClient> ATL_NOINLINE inline const _soapmap ** CExternalServiceT<TClient>::GetFunctionMap() {       return __CExternalService_funcs; } . . .    static const _soapmapentry __CExternalService_GetTransaction_atlsoapheader_entries[] =    {       { 0x00000000 }    };    static const _soapmap __CExternalService_GetTransaction_atlsoapheader_map =    {       0x88540AB5,       "GetTransaction",       L"GetTransactionResponse",       sizeof("GetTransaction")-1,       sizeof("GetTransactionResponse")-1,       SOAPMAP_HEADER,    __CExternalService_GetTransaction_atlsoapheader_entries,       0,       0,       -1,       SOAPFLAG_NONE  SOAPFLAG_PID  SOAPFLAG_DOCUMENT  SOAPFLAG_LITERAL,       0xC2E575C3,       "http://tempuri.org/",       L"http://tempuri.org/",       sizeof("http://tempuri.org/")-1    };    static const _soapmap * __CExternalService_headers[] =    {       . . . &__CExternalService_GetTransaction_atlsoapheader_map,       NULL    };    return __CExternalService_headers; } template <typename TClient> ATL_NOINLINE inline void * CExternalServiceT<TClient>::GetHeaderValue() {    return this; } template <typename TClient> ATL_NOINLINE inline const wchar_t * CExternalServiceT<TClient>::GetNamespaceUri() {    return L"http://tempuri.org/"; } template <typename TClient> ATL_NOINLINE inline const char * CExternalServiceT<TClient>::GetServiceName() {    return NULL; } template <typename TClient> ATL_NOINLINE inline const char * CExternalServiceT<TClient>::GetNamespaceUriA() { return "http://tempuri.org/"; } template <typename TClient> ATL_NOINLINE inline HRESULT CExternalServiceT<TClient>::CallFunction(void *,    const wchar_t *, int,    size_t) {    return E_NOTIMPL; } template <typename TClient> ATL_NOINLINE inline HRESULT CExternalServiceT<TClient>::GetClientReader(ISAXXMLReader **ppReader) {    if (ppReader == NULL)    {       return E_INVALIDARG;    }    CComPtr<ISAXXMLReader> spReader = GetReader();    if (spReader.p != NULL)    {       *ppReader = spReader.Detach();       return S_OK;    }    return TClient::GetClientReader(ppReader); } 

Although this pattern utilizes ATL and C++ and can be considered advanced for those with primarily C# or VB.NET skills, the generic applicability of this implementation allows any Web service to be represented, threaded, and executed. With the new ATL features, threading and thread pooling have never been easier. This provides the conduit within which to build any asynchronous model using client code. As long as the clients are relatively low in number and simple in complexity, this pattern can be applied to many areas. For those who do not yet have .NET installed, it can provide a tremendous bridge from the "old unmanaged world" to the "new world" of .NET.

.NET Patterns. Architecture, Design, and Process
.NET Patterns: Architecture, Design, and Process
ISBN: 0321130022
EAN: 2147483647
Year: 2003
Pages: 70

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