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年8月24日 星期日

Ecplise for c/c++ integrated with cmake and gdb on Ubuntu

Preface

Reference


Installations

a) cmake

apt-get install cmake

b) java

apt-get install sun-java6-jdk
apt-get install sun-java6-jre 
apt-get install sun-java6-plugin
apt-get install sun-java6-font

!!NOTES:

It can be done as follows, too

command >> gnome-app-install

Search "java" with "all available applications" column selected


c) eclipse

apt-get install eclipse

!!NOTES:

Please refer this web site to install WTP, Tomcat and configure WTP

d) Eclipse CDT

-- please go to http://www.eclipse.org/cdt

-- extract the tar file under /usr/lib, and it will be put into /usr/lib/eclipse

e) gdb
f) gcc


Use Ecplise for C/C++ "hello_lib" project step-by-step

1) Run Eclipse

2) Create a new C project


Action: File -> New -> Create "Standard Make C Project"

Ref: (2.a), (2.b)

(2.a)

(2.b)



3) create the following folders

Action: right-click "hello_lib" project -> New ->folder

Ref: (3.a), (3.b)

-- build (the location for cmake to be executed)

-- lib_a (the library for "hello_lib_a")

-- lib_b (the library for "hello_lib_b")

-- src (the source file where main() will be written)


(3.a)

(3.b)


4) Write the needed source files

Action: right-click "hello_lib" project -> New -> header file / source file

Ref: (4.a), (4.b)

lib_a/hello_lib_a.h

#ifndef HELLO_LIB_A_H_
#define HELLO_LIB_A_H_

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

void hello_a();

#ifdef __cplusplus
}
#endif // __cplusplus

#endif /*HELLO_LIB_A_H_*/

lib_a/hello_lib_a.c

#include "hello_lib_a.h"

void hello_a() {
printf ("hello_a\n");
}

lib_b/hello_lib_b.h

#include "hello_lib_b.h"

void hello_b() {
printf ("hello_b\n");
}

lib_b/hello_lib_b.h

#ifndef HELLO_LIB_B_H_
#define HELLO_LIB_B_H_

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

void hello_b();

#ifdef __cplusplus
}
#endif // __cplusplus

#endif /*HELLO_LIB_B_H_*/

src/hello_main.c

#include <hello_lib_a.h>

#include <hello_lib_b.h>

int main () {

hello_lib_a();

hello_lib_b();

return 0;

}



(4.a)


(4.b)

5) Write CMakeLists.txt

Action: right-click "hello_lib" project -> New -> File

Ref: (5.a), (5.b)

lib_a/CMakeLists.txt

SET(CMAKE_C_FLAGS ${CMAKE_C_FLAGS_DEBUG})
ADD_LIBRARY(hello_lib_a SHARED hello_lib_a.c)

lib_b/CMakeLists.txt

SET(CMAKE_C_FLAGS ${CMAKE_C_FLAGS_DEBUG})
ADD_LIBRARY(hello_lib_b SHARED hello_lib_b.c)

src/CMakeLists.txt

INCLUDE_DIRECTORIES(${HELLO_LIB_TEST_SOURCE_DIR}/lib_a)
INCLUDE_DIRECTORIES(${HELLO_LIB_TEST_SOURCE_DIR}/lib_b)
LINK_DIRECTORIES(${HELLO_LIB_TEST_BINARY_DIR}/lib_a)
LINK_DIRECTORIES(${HELLO_LIB_TEST_BINARY_DIR}/lib_b)

SET(CMAKE_C_FLAGS ${CMAKE_C_FLAGS_DEBUG})
ADD_EXECUTABLE(hello hello_main.c)
TARGET_LINK_LIBRARIES(hello hello_lib_a hello_lib_b)

/CMakeLists.txt

PROJECT (HELLO_LIB_TEST)
ADD_SUBDIRECTORY(lib_a)
ADD_SUBDIRECTORY(lib_b)
ADD_SUBDIRECTORY(src)

(5.a)

(5.b)

6) Prepare External Tools for cmake and Run it

Action:

a) Run -> External Tools -> External Tools...

b) New program named "cmake_hello_lib_test"

c) fill the corresponding columns as (6.a) picture

d) Run -> External Tools -> cmake_hello_lib_test

Ref: (6.a)

(6.a)



7) Prepare make environment

Action:

a) right-click "hello_lib" project -> Properties as (7.a)

b) fill in "build_directory" with "/hello_lib/build"

Ref: (7.a)

(7.a)


8) Debug environment

Action:

a) Run -> Debug

b) New "C++ Local Application" as (8.a) shows

c) Fill in "C/C++ Application" columns as (8.b) shows

d) Press "Apply" button

e) Press "Debug" button as (8.c) shows

f) Press F5 to Step-Into or F6 to Step-Over


Ref: (8.a), (8.b)

(8.a)


(8.b)


(8.c)


2008年7月5日 星期六

DirectShow a/v sync notes

Preface

Reference DVB-H CRTPSourceFilter source code

Direct Show

!!NOTES:

when checking a/v sync, there are three cases to be tested.

a) From no signal to having signal (e.g. Mute->unMute)

b) fast-backward/re-wind, fast-forward (DISCONTINUITY)

c) Device lost (ex: Ctrl-Alt-Del)

4) buffering mechansim

1) a/v PTS

Real a/v PTS should come from a source filter,

ex: FileSourceFilter, DVBHRTPSourceFilter

a/v PTS should be used in MediaSample::SetTime(..) and

MediaSample::SetSyncPoint(..) with 100ns-based unit.


Pseudo code:

HRESULT CRTPSourcePin::FillBuffer(IMediaSample *pSample) {

// Notes:

// (1) pSample may be audio or video samples.

// (2) m_rtTimeBase and pPacket->pts are micro-second based.

1 micro-sec = 1000 nano-sec.

rtStart = (m_pSourceFilter->m_rtTimeBase + pPacket->pts) * 10 ;
rtEnd = rtStart + 1 ;

pSample->SetTime( &rtStart, &rtEnd );
pSample->SetSyncPoint( TRUE ) ;

if ( pPacket->pts > m_pFilter->m_rtCurrMaxPTS) {
m_pFilter->m_rtCurrMaxPTS = pPacket->pts;
}

}

!!NOTES:

MediaSample PTS value has to always move upward.

Otherwise, there might be frame drops.

2) DirectShow System Clock

When running a DirectShow Graph, System clock will also move forward

together with MediaSample PTS.


There will be following different cases

a) When PTS of MediaSample is equal to or bigger than System Clock

Audio MedisaSample PTS

|----------------|-------------|----------------------

100 200 300 or 500 (meant to be assigned)

Video MediaSample PTS

|----------------|-------------|-----------------------

100 200 300 or 500 (meant to be assigned)

System clock

|----------------|-------------|-----------------------

1000 1100 1200

Result: a/v will be in sync as long as PTS for MediaSample is assigned synchronously for a/v.


b) The speed of MediaSample is slower than System Clock

Audio MedisaSample PTS

|----------------|-------------|----------------------

100 200 210 (meant to be assigned)

Video MediaSample PTS

|----------------|-------------|-----------------------

100 200 210 (meant to be assigned)

System clock

|----------------|-------------|-----------------------

1000 1100 1200

Result:

might have video frame drops even though a/v PTS is in sync

because System Clock runs faster


3) Discontinuity

When discontinuity happens, what should be done

a) Use the last MAX PTS as the next PTS base

(ps: this is to make sure PTS value is moving upward)

b) Calculate how many seconds are passed between discontinuity event and

the last packet PTS

(ps: this is to make sure the following MedisaSample PTS value still

follows System Clock speed)

Pseudo code:

s = GetMediaPacket(...);

if (s == REAL_PACKET) {

lastPacket_tickCount = GetTickCount();

} else if (s == DISCONTINUITY) {

m_pSourceFilter->m_rtTimeBase += pRTPSourceFilter->m_rtCurrMaxPTS;

// extra seconds to add

m_pSourceFilter->m_rtTimeBase += (GetTickCount() - lastPacket_tickCount ) *1000;
m_pSourceFilter->m_rtCurrMaxPTS = 0;

}

 

4) Buffering mechanism

When it comes to DVB-T, buffering may not be needed.

But when it comes to DVB-H burst streaming, deciding how many packets should be buffered

in advance is very important because it will affect the playback quality.

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.