This blog is only to remind myself of what I've learned (from others or by myself) and only for knowledge sharing. External sources will be clearly stated in [Reference] of each article. I hope this blog won't infringe any copyrights and that it can be useful for interested blog readers.

2008年4月15日 星期二

COM client with Connection Point

 

Goal:

Create a project where we use a COM client with Connection Point function.

 

Preface:

(1) VC6 IDE is necessary

 

 

Steps:

(1) Create a Win32 Console Application project

 

(2) create a simple "hello world" application

 

(3) Prepare to create a new class "Sink"

 

(4) "Sink" Class should derive from _IAddEvents, where is used to connect COM server.

 

(5) Just press "ok" when meeting this message

 

(6) Sink.h sample code

// Sink.h: interface for the CSink class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_SINK_H__F90A535B_4A71_433A_9A96_DE06BBEAB53C__INCLUDED_)
#define AFX_SINK_H__F90A535B_4A71_433A_9A96_DE06BBEAB53C__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "../Test_COM_Connection_Point/Test_COM_Connection_Point.h"
#import "../Test_COM_Connection_Point/Test_COM_Connection_Point.tlb" named_guids raw_interfaces_only

#include <stdio.h>

class CSink : public _IAddEvents 
{
private:
    DWORD       m_dwRefCount;
public:
    CSink();
    virtual ~CSink();

    // added by Mark manually -- Start

    STDMETHODIMP ExecutionOver(int Result)
      {
      printf("CSink::ExecutionOver::The result is %d", Result);
      return S_OK;;

      };

//IUnknown member functions -- QueryInteface, AddRef, ReleaseRef

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject)
      {
        if (iid == DIID__IAddEvents)
        {
          m_dwRefCount++;
          *ppvObject = (void *)this;
          return S_OK;
        }
        if (iid == IID_IUnknown)
        {
          m_dwRefCount++;
          *ppvObject = (void *)this;
          return S_OK;
        }
        return E_NOINTERFACE;

      }

    ULONG STDMETHODCALLTYPE AddRef()
      {
        m_dwRefCount++;
        return m_dwRefCount;
      }

    ULONG STDMETHODCALLTYPE Release()
      {
        ULONG l;
        l  = m_dwRefCount--;
        if ( 0 == m_dwRefCount)
           delete this;

        return l;
      }

    // IDispatch member functions -- GetIDsOfNames, GetTypeInfo, GetTypeInfoCount, Invoke
    STDMETHOD(GetIDsOfNames)( REFIID /*riid*/,
                              OLECHAR FAR *FAR *rgszNames,
                              unsigned int cNames,
                              LCID lcid,
                              DISPID FAR* /*rgDispId*/ )
    {
        UNREFERENCED_PARAMETER(rgszNames);
        UNREFERENCED_PARAMETER(cNames);
        UNREFERENCED_PARAMETER(lcid);
        return E_NOTIMPL;
    }

    STDMETHOD(GetTypeInfo)( unsigned int iTInfo,
                            LCID lcid,
                            ITypeInfo FAR *FAR *ppTInfo )
    {
        UNREFERENCED_PARAMETER(iTInfo);
        UNREFERENCED_PARAMETER(lcid);
        UNREFERENCED_PARAMETER(ppTInfo);       
        return E_NOTIMPL;
    }

    STDMETHOD(GetTypeInfoCount)( unsigned int FAR *pctinfo )
    {
        UNREFERENCED_PARAMETER(pctinfo);
        return E_NOTIMPL;
    }
    STDMETHOD(Invoke)( DISPID  dispIdMember,     
                       REFIID  riid,             
                       LCID  lcid,               
                       WORD  wFlags,             
                       DISPPARAMS FAR*  pDispParams, 
                       VARIANT FAR*  pVarResult, 
                       EXCEPINFO FAR*  pExcepInfo, 
                       unsigned int FAR*  puArgErr );

    // added by Mark manually -- End

};

#endif // !defined(AFX_SINK_H__F90A535B_4A71_433A_9A96_DE06BBEAB53C__INCLUDED_)

 

(7) Sink.cpp sample code

 

// Sink.cpp: implementation of the CSink class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Sink.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CSink::CSink()
{
    // added by Mark
    m_dwRefCount =0;
}

CSink::~CSink()
{

}

// added by Mark for Invoke member -- Start
HRESULT CSink::Invoke(        DISPID  dispIdMember,     
                            REFIID  riid,             
                            LCID  lcid,               
                            WORD  wFlags,             
                            DISPPARAMS FAR*  pDispParams, 
                            VARIANT FAR*  pVarResult, 
                            EXCEPINFO FAR*  pExcepInfo, 
                            unsigned int FAR*  puArgErr )
{
    if (!pDispParams)
        return E_POINTER;
    if (!pVarResult)
        return E_POINTER;
    if (pDispParams->cNamedArgs != 0)
        return DISP_E_NONAMEDARGS;

    switch (dispIdMember)
    {
    case 0x01: // 0x01 from COM server's Invoke
        printf("0x01 is invoked from COM server\n");
        break;

//     case DISPID_CAPEVENT_ONPRECHANNELCHANGE:
//         pVarResult->scode = m_pSinkCallback->OnPreChannelChanged(pDispParams->rgvarg[3].lVal, pDispParams->rgvarg[2].lVal, pDispParams->rgvarg[1].bstrVal, pDispParams->rgvarg[0].lVal);
//         break;
//     case DISPID_CAPEVENT_ONCHANNELCHANGE:
//         pVarResult->scode = m_pSinkCallback->OnChannelChanged(pDispParams->rgvarg[3].lVal, pDispParams->rgvarg[2].lVal, pDispParams->rgvarg[1].bstrVal, pDispParams->rgvarg[0].lVal);
        break;

    default:
        break;
    }
    return S_OK;
}

// added by Mark for Invoke member -- End

 

(8) main(..) function

#include "stdafx.h"

// -- Mark:: header files -- Start
#include "Sink.h"
#include <stdio.h>
#include <atlbase.h>
// -- Mark:: header files -- End

HRESULT test_COM_ConnectionPoint();

int main(int argc, char* argv[])
{
    test_COM_ConnectionPoint();
    return 0;
}

HRESULT test_COM_ConnectionPoint() {

    HRESULT hr;

    //call CoInitialize for COM initialisation
    hr =CoInitialize(NULL);
    if(hr != S_OK)
      return -1;

    // create an instance of the COM object
    CComPtr<IAdd> pAdd;
    hr =pAdd.CoCreateInstance(CLSID_Add);
    if(hr != S_OK)
      return -1;

    IConnectionPointContainer  * pCPC;
      IConnectionPoint       * pCP;

      DWORD                  dwAdvise;

      hr = pAdd->QueryInterface(IID_IConnectionPointContainer,
                               (void **)&pCPC);

      if ( !SUCCEEDED(hr) )
      {
        return hr;
      }

      //

      //OK, it does; now get the correct connection point interface

      //in our case IID_IAddEvents

     hr = pCPC->FindConnectionPoint(DIID__IAddEvents,&pCP);

    if ( !SUCCEEDED(hr) )
      {
        return hr;
      }

    //we are done with the connection point container interface

    pCPC->Release();

    IUnknown *pSinkUnk;

    // create a notification object from our CSink class

    //

    CSink *pSink = new CSink;

      if ( NULL == pSink )
      {
        return E_FAIL;
      }

      //Get the pointer to CSink's IUnknown pointer (note we have

      //implemented all this QueryInterface stuff earlier in our

      //CSinkclass

    hr = pSink->QueryInterface (IID_IUnknown,(void **)&pSinkUnk);

    //Pass it to the COM through the COM's _IAddEvents

    //interface (pCP) Advise method; Note that (pCP) was retrieved

    //through the earlier FindConnectoinPoint call

    //This is how the com gets our interface, so that it just needs

    //to call the interface method when it has to notify us

    _IAddEvents *pSinkAddEvents;

    hr = pSink->QueryInterface (DIID__IAddEvents,(void **)&pSinkAddEvents);
    if ( !SUCCEEDED(hr) )
      {
        return hr;
      }
    hr = pCP->Advise(pSinkAddEvents,&dwAdvise);

    //now call the COM's add method, passing in 2 numbers

     hr =  pAdd->Add(2 ,4);
    //do whatever u want here; once addition is here a message box

    //will pop up showing the result

    pCP->Unadvise(dwAdvise); // call this when you need to disconnect from server

    printf("press any key to continue...");
    getchar();

    pCP->Release();

    // Uninitialize COM
    //CoUninitialize();

      return hr;
}

 

(9) Results:

 

 

COM server with Connection Point

 

Purpose:

Demonstrate how COM server (Server Type -- Service (EXE))

callbacks a COM client's API by using Connection Point.

 

!!NOTES:

If COM server's type is DLL, please refer

CodeProject: COM Connection Points

about how to use Connection Point

 

Preface:

(1) VC6 IDE is required

(2) This example only applies to "Service (EXE)" Server Type

(3) This COM server will use Add(..) function to describe how Connection Point is used here

 

Steps:

(1) Create a COM Project

 

(2) Choose Service Type -- Service(EXE)


(3) Add a new ATL Object

 

(4) create a Simple Object

 

(5) Fill in "Names" tab

 

(6) Fill in "Attributes" tab. Remember to check "Support Connection Points"

 

(7) Build to form "connection type" lib first

 

(8) implement Connection Point for CAdd class

 

 

(9) Add _IAddEvents interface

 

(10) Build again

 

(11) fix compile errors by replacing IID__IAddEvents with DIID__IAddEvents

 

(12) Add a method -- Add -- to IAdd interface

 

 

 

(13) Fill in the info of Add method

 

(14) Add a method to _IAddEvents

 

(15) Add a method "ExecutionOver" to _AddEvents interface

 

 

(16) Add a method -- Fire_ExecutionOver(INT Result) -- in CProxy_IAddEvents class

HRESULT Fire_ExecutionOver(INT Result)
{
    CComVariant varResult;
    T* pT = static_cast<T*>(this);
    int nConnectionIndex;
    CComVariant* pvars = new CComVariant[1];
    int nConnections = m_vec.GetSize();
    for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
    {
        pT->Lock();
        CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
        pT->Unlock();
        IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
        if (pDispatch != NULL)
        {
            VariantClear(&varResult);
            pvars[0] = Result;
            DISPPARAMS disp = { pvars, NULL, 1, 0 };

//!!NOTES: 0x1 is an ID to let COM clients tell what to do
            pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL);
        }
    }
    delete[] pvars;
    return varResult.scode;
}

 

(17) fill in CAdd:Add(int, int) to do "Add" when COM client is calling "Add" function

STDMETHODIMP CAdd::Add(int a, int b)
{
    // TODO: Add your implementation code here

//    Sleep(2000);   // to simulate a long process

    //OK, process over now; let's notify the client

    Fire_ExecutionOver(a+b);

    return S_OK;

}

 

(18) type "Test_COM_Connection_Point.exe -Service" in command mode to make sure it

  can be a service as follows

 

(19) type "services.msc" in command mode and enter service Window

 

(20) activate "Test_COM_Connection_Point.exe " as follows

COM related info

 

(1) CodeGuru: Step by Step COM Tutorial

(2) Passing parameters in COM's method

String -- Use BSTR

  (a) How to convert BSTR to WCHAR

    Case I :
      WCHAR *wszTemp = NULL;
      BSTR b_Temp = NULL;
      b_Temp = SysAllocString(L"Convert");
     wszTemp = b_Temp;


    Case II :
      WCHAR wszTemp[10];
      BSTR b_Temp = NULL;
      memset((void*)wszTemp,'\0',sizeof(wszTemp));
      b_Temp = SysAllocString(L"Convert");
      wcscpy(wszTemp,b_Temp);

  (b) How to convert BSTR to LPCTSTR

  -- convert BSTR to WCHAR *

  -- Use wcstombs()

  

Structure -- pointer to structure

!!NOTE:

1) every member variable of a structure had better be 4-byte aligned.

2) there should not be any pointers to any type. It's better to assign necessary memory size in advance.

For example:

typedef struct
{
    unsigned int xProgramId;
    TPslContext  pslContext; // 4-byte aligned structure
    unsigned int networkId; 
    unsigned int platformId; 
    unsigned int uriLength;
    unsigned char ESGProviderURI[128];
}Dvbh_Fun_PSL_Add_Args;

Class -- pointer to class or class reference

Memory buffer -- use "structure" with allocated buffer inside

 

etc.

(3) callback in COM

-- COM Server (DLL) -- refer here

-- COM Server (Service EXE) -- refer here

2008年4月8日 星期二

COM client using COM server's interface

Goals:

(1) how to get instance of COM service

(2) how to get access of COM service APIs

(3) Usage


Sample code:

#include <iostream.h>

//!!NOTES: this part is to get CLSID and IID info

#import "..\Test_COM_service/Test_COM_service.tlb" named_guids raw_interfaces_only
using namespace TEST_COM_SERVICELib; // refer to Test_COM_service_i.c

#include <stdio.h>


void main(void)
{
// Declare and HRESULT and a pointer to
// the Simple_ATL interface
HRESULT hr;
IZTEDll *pZTEDll = NULL;

// Now we will intilize COM
hr = CoInitialize(0);

// Use the SUCCEEDED macro and see if
// we can get a pointer
// to the interface
if(SUCCEEDED(hr))
{
//NOTES: don't know why CLSCTX_INPROC_SERVER won't work

hr = CoCreateInstance(CLSID_ZTEDll, NULL, CLSCTX_ALL, IID_IZTEDll, (void**) &pZTEDll) ;

if(SUCCEEDED(hr))
{
UTC_time_info timeInfo;
timeInfo.year = 2008;
timeInfo.month = 4;
timeInfo.date = 8;
timeInfo.hour = 14;
timeInfo.minute = 0;
timeInfo.second = 0;

HRESULT res = S_OK;
res = pZTEDll->initializeKda(&timeInfo);
cout << "after initializeKda: " << res << endl;
cout << "year: " << timeInfo.year << endl;
cout << "month: " << timeInfo.month << endl;
cout << "date: " << timeInfo.date << endl;
cout << "hour: " << timeInfo.hour << endl;
cout << "minute: " << timeInfo.minute << endl;
cout << "second: " << timeInfo.second << endl;

res = pZTEDll->terminateKda();
cout << "after terminateKda: " << res << endl;

cout << "press any key to continue... " << endl;
getchar();

pZTEDll->Release();
}
else
{
cout << "CoCreateInstance Failed." << endl;
}
}
// Uninitialize COM
CoUninitialize();
}


Usage:

(1) open a "command console"
(2) Go to the directory where "Test_COM_client.exe" is located
(3) execuate Test_COM_service.exe
(4) check the result

COM-server related info

What to know:

(1) Use VC6 to create a "service-type" EXE file

(2) How to make COM server as a background service when OS boot-up

(3) How to debug COM server

---------------------------------------------------------

Use VC6 to create a "service-type" EXE file

(1) New ATL COM APP Wizard

(2) Choose Service-Type

(3) Wizard finished

(4) Add a new ATL Object

(5) Choose "Simple Object" to create an interface

(6) Fill in "Short Name" of the new simple object

(7) The result of New created Object (Interface)

(8)

(9) Add a new method "initialize" with UTC_time_info structure

(10) add a new method "un-initialize"

(11) The results of created new methods

(12) add UTC_time_info structure in IDL file

(13) Compile

How to make COM server as a background service when OS boot-up

(1) Open a "command console" window

(2) Go to the directory where "Test_COM_service.exe" is located

(3) enter command "Test_COM_service.exe -Service"

(4) enter "services" window

(5) check the availability of "Test_COM_service"

(6) manually activate it

(7) check the correctness of COM service startup

(8) check the COM service startup correctness in task bar

How to debug COM server

(1) de-activate COM service from "services" window

(2) Open a "command console" window

(3) Go to the directory where "Test_COM_service.exe" is located

(4) enter command "Test_COM_service.exe -regserver"

(5) Open COM service VC6 project

(6) press "F5" and add some breakpoints for debugging


!!NOTES:

(1) Debugging

according to testing, it seems that if "Test_COM_service.exe -regserver" is used

instead of "Test_COM_service.exe -Service", COM service will be closed

when COM client is Release its allocated COM server instance.

(2) COM startup and release

During COM server startup, the following steps will happen

(a) COM server will initialize itself

(b) COM server will enter CServiceModule::Run() and wait for messages in

the following while-loop

MSG msg;
while (GetMessage(&msg, 0, 0, 0))
DispatchMessage(&msg);

So any code related initialization can be placed before this while-loop.

(c) When COM server is closed automatically or manually, COM server will leave

the above while loop and leave CServiceModule::Run() subsequently.

So any code related to un-initilazation can be placed after "GetMessage" while loop.