Trace RPC Calls and Notify the COM+ Events to Your Program

Introduction

Some time back, I was reading about Performance Monitor Programming, and found some articles—not many, but some. Then, I read something about Spying the componenets and services that run under COM+ Consoles. In both cases, it was capturing COM+ Events managment internal data, to get it out for Spying, and to add counters in Performance monitor.

All of these work by using two major concepts of COM+ interfaces: Instrumentation interfaces and Administration interfaces. I found less help in understanding their relation, but when I had understood all these, I got a good start to make it simple and useful for all.

But now the question arises: Why should we put in all this effort? Part of ths reason this is that you can get a performance log of your component methods, without changing the component code, like some running application in production that may not have proper logging. Or, it may have a performance issue, but you not able to track which Methods. So, you can write such a small service to log your Production components' performance by their method name, which would be good for diagnostics—and lots more if you have an idea to more such interfaces.

History

Let me first define both from the Microsoft documentation.

Instrumentation interfaces

The COM+ Instrumentation service enables you to build your own COM+ event management and logging programs when you want to display various performance metrics for your COM+ components. By subscribing to the events published by the system events publisher, clients can implement the COM+ Instrumentation interfaces to receive notifications for a variety of COM+ performance metrics, such as information about specific COM+ objects, COM+ applications, and COM+ services. The metrics are published to the client by using the COM+ Events service, a loosely coupled events (LCE) system that stores event information from different publishers in an event store in the COM+ catalog.

Note: COM+ Instrumentation does not guarantee the delivery of an event.

Administration interfaces

The COM+ administration interfaces enable you to access and manipulate all of the COM+ configuration data accessible through the Component Services administration tool. You can use these interfaces to automate all tasks in COM+ administration. The administration interfaces also enable you to read and write information that is stored in the COM+ catalog, the underlying data store that holds all COM+ configuration data. Information in the catalog is structured as collections, which hold items that expose properties. In the Component Services administration tool, these collections correspond to folders, items in folders, and property sheets for those items.

Note: Not all of the collections and items that are available in the COM+ catalog are available in the Component Services administration tool.

These are the standard definitions as documented in the Microsoft documentation. Let me try to explain both in simple words.

Administration Interfaces are the interfaces by which you can get and set all the information programmatically, what you can see in the component console. Instrumentation interfaces are the interfaces that you can implement programmatically to receive all the events of com+ Event management, even to the components method call, Object pooling, transaction, and so forth.

These are the same interfaces that you can use to spy your COM+ components also if you have to receive information about which method has been called, with which parameters, and when it has been returned with what return value. You can get all these in an another application that could just monitor the components without making any communication and interference or without loading the components in its process.

All this information is available in COM+ Events service, a loosely coupled events system that stores event information from different components or services of COM+, in an event store in the COM+ catalog.

Sample

Now, the efforts come into the picture to make it practical, instead of going on theory. To understand all these, you need to make three simple applications; I'll explain them by their names (I will continue to use the same names).

  1. Publisher: You can make a simple Component that may have some test method, Or, you can use some existing component's DLL to configure in a component console.
  2. Subscriber: (attached is the source as well as a DLL) An ATL-based simple component DLL that implements the instrumentation interface. You can make any of the instrumentation interfaces available. I have implemented IComMethodEvents, which is used to get notify on method call, on method return and on method exception for the Publisher methods.
  3. Administrator: (attached is the source as well as an EXE) An MFC application that uses COM+ Admin Interfaces to configure a COM+ catalog to subscribe Subscriber to an COM+ Event service to be notified for the method calls of Publisher. This is the application that does all magic; COM+ Event service sends the notification to your Subscriber whenever there is some activity in the Publisher Component.

Now, move on to the pieces of code that I have written to make it all work.

YOU MUST HAVE INSTALLED MS PLATEFORM SDK TO COMPILE IT.

First the Subscriber component. It is nothing, just a simple ATL Object that implements IComMethodEvents. Here is the class I have made to implement IComMethodEvents; the same class is derived to an ATL Object.

class CComMethodEventsImpl : public IComMethodEvents
{
   public:
      CComMethodEventsImpl() {}

      STDMETHOD(OnMethodCall)(COMSVCSEVENTINFO * pInfo, ULONG64 oid,
                               REFCLSID cid, REFIID rid, ULONG iMeth);
      STDMETHOD(OnMethodReturn)(COMSVCSEVENTINFO * pInfo, ULONG64 oid,
                                REFCLSID cid, REFIID rid, ULONG iMeth,
                                HRESULT hr);
      STDMETHOD(OnMethodException)(COMSVCSEVENTINFO * pInfo,
                                   ULONG64 oid, REFCLSID cid,
                                   REFIID rid, ULONG iMeth);
      //These are the method that are helper methods to get the
      //method name and other information (taken from some of the
      //sample code of the SDK)
      HRESULT GetClsidOfTypeLib2 (IID * piid, UUID * puuidClsid);
      HRESULT GetMethodName (REFIID riid, int iMeth,
                             _TCHAR** ppszMethodName);

   public:
};

Now, the class is derived to ATL Object. All are just simple required steps to make a component with the implementation of IComMethodEvents. (I expect that you are well familiar with ATL.)

///////////////////////////////////////////////////////////////////
// CComPlusMethodEvent
class ATL_NO_VTABLE CComPlusMethodEvent :
   public CComObjectRootEx<CComMultiThreadModel>,
   public CComCoClass<CComPlusMethodEvent,
                      &CLSID_ComPlusMethodEvent>,
   public IDispatchImpl<IComPlusMethodEvent, &IID_IComPlusMethodEvent,
                        &LIBID_SUBSCRIBERLib>,
   public CComMethodEventsImpl

{
public:
   CComPlusMethodEvent()
   {

   }

DECLARE_REGISTRY_RESOURCEID(IDR_COMPLUSMETHODEVENT)
DECLARE_NOT_AGGREGATABLE(CComPlusMethodEvent)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CComPlusMethodEvent)
COM_INTERFACE_ENTRY(IComPlusMethodEvent)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IComMethodEvents)
END_COM_MAP()

// IComPlusMethodEvent
public:
   STDMETHOD(Test)();
   STDMETHOD(GetInterfaceUnknown)( /*[out,retval]*/ VARIANT
                                   *PVar_IUnknown);
};

Now, the implementation for the pure virtual methods of IComMethodEvents.

STDMETHODIMP CComMethodEventsImpl::OnMethodCall(
             /* [in] */ COMSVCSEVENTINFO *pInfo,
             /* [in] */ ULONG64 oid,
             /* [in] */ REFCLSID guidCid,
             /* [in] */ REFIID guidRid,
             /* [in] */ ULONG iMeth)
{

   TCHAR * pszMethodName = NULL;
   TCHAR * pszMessage    = NULL;
   HRESULT hr;
   hr = GetMethodName(guidRid, iMeth, &pszMethodName) ;
   if(hr == S_OK)
   {
      pszMessage = new _TCHAR[255];
      lstrcpy(pszMessage , L"Method Called------");
      lstrcat(pszMessage , pszMethodName);
      lstrcat(pszMessage , L"(...)");

      MessageBox(0,pszMessage,L"Subscriber Message",MB_OK);
      delete pszMethodName ;
      pszMethodName = NULL ;
      delete pszMessage ;
      pszMessage = NULL ;
   }

   return S_OK;
}

STDMETHODIMP CComMethodEventsImpl::OnMethodReturn(
             /* [in] */ COMSVCSEVENTINFO *pInfo,
             /* [in] */ ULONG64 oid,
             /* [in] */ REFCLSID guidCid,
             /* [in] */ REFIID guidRid,
             /* [in] */ ULONG iMeth,
             /* [in] */ HRESULT hresult)
{
   TCHAR * pszMethodName = NULL;
   TCHAR * pszMessage    = NULL;
   HRESULT hr;
   hr = GetMethodName(guidRid, iMeth, &pszMethodName) ;
   if(hr == S_OK)
   {
      WCHAR wcRet[16];
      wsprintfW(wcRet, L"0x%08X", hresult);
      pszMessage  = new _TCHAR[255];
      lstrcpy(pszMessage , L"Method Returned------");
      lstrcat(pszMessage , pszMethodName);
      lstrcat(pszMessage , L"(...) , ");
      lstrcat(pszMessage , L"Return Code -----");
      lstrcat(pszMessage , wcRet);

      MessageBox(0,pszMessage,L"Subscriber Message",MB_OK);
      delete pszMethodName ;
      pszMethodName = NULL ;
      delete pszMessage ;
      pszMessage = NULL ;
   }



   return S_OK;
}

STDMETHODIMP CComMethodEventsImpl::OnMethodException( 
             /* [in] */ COMSVCSEVENTINFO *pInfo,
             /* [in] */ ULONG64 oid,
             /* [in] */ REFCLSID guidCid,
             /* [in] */ REFIID guidRid,
             /* [in] */ ULONG iMeth)
{
   return S_OK;
}

That's it; that was the all of code for the Subscriber component. Now, move to the next step to get it working, by the Administrator code.

Trace RPC Calls and Notify the COM+ Events to Your Program

Administrator is just a simple dialog-based application that has two options to subscribe your Subscriber to monitor the Publisher, either by the AppId of Publisher.

[AppID_Subscribe.jpg]

Or by the ProcessId of Publisher.

[ProcessID_Subscribe.jpg]

Now, the code to the magic. You can see that you have two buttons: to Subscribe and Unsubscribe. So, two simple methods also are needed to do the configure part. First, make available the DLL which you have to use for using COM+ Admin interfaces, and also the Subscriber DLL because you need IUnKnown of your Subscriber component.

#import "c:\\winnt\\system32\\com\\Comadmin.dll"  no_namespace
#import "C:\\Pardeep_Data\\Subscriber\\DebugU\\Subscriber.dll"
        no_namespace

_variant_t VarOutSubscribtion;    //To Remove Subscribtion
bool bSubscribeStatus = false;
//To get Instance of Subscriber
IComPlusMethodEventPtr ptrSubscriber;
_variant_t varUnknown;            //To get IUnKnown of Subscriber

And here the code piece to use them all.

First, I'd like to explain some simple interfaces and steps to use COM+ Admin Catalog collections programmatically. For an available collection, Please go through http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cossdk/html/eed8ca97-39ad-4188-afc6-8670b5073fad.asp.

There are always three basic interfaces to use any of the Catalog Collections:

  • ICOMAdminCatalog: An interface to initialize the COM+ Admin Catalog
  • ICatalogCollection: An interface to get the any of available Collections from the Catalog
  • ICatalogObject: An interface to get/set the new object available in the Collection

To understand more practically, I'll give you a simple story, like your Component Console (Component service in MMC) is "COM+ Admin Catalog" (ICOMAdminCatalog). Now, you see "COM+ Applications" (ICatalogCollection) as a Collection and every application has "Components" (ICatalogObject), that you can see as an Object. Hopefully, it will help you understand the following code.

/*************************************************
OnSubscribe()
To Subscribe the Subscribtion.
**************************************************/
void CComPlusObjectSpyDlg::OnSubscribe()
{

   try{

      ICOMAdminCatalogPtr spCatalog("COMAdmin.COMAdminCatalog");
      ICatalogCollectionPtr spCatalogCollection_Subscriber; 

      //Simple usage for COM Admin Applications could be
      //spCatalogCollection_Subscriber = spCatalog->GetCollection
      //(L"Applications");

      //But to listen COM+ Event Service we have to get the
     //""TransientSubscriptions""
      spCatalogCollection_Subscriber =
         spCatalog->GetCollection (L"TransientSubscriptions");
      spCatalogCollection_Subscriber->Populate ();

      //Add a new transient object in the collection of
      //""TransientSubscriptions""
      ICatalogObjectPtr spCatalogObject_Subscriber;
      spCatalogObject_Subscriber =
         spCatalogCollection_Subscriber->Add();

      //Now that we have our new subscription CatalogObject we can
      //begin filling out the details. Set the name of the
      //subscription.
      _variant_t vntName(L"Method");
      spCatalogObject_Subscriber->put_Value(L"Name",vntName);

      //Set the event class ID. This is the GUID for
      //CLSID_ComServiceEvents, and is defined in comsvcs.h.
      vntName = L"{ECABB0C3-7F19-11D2-978E-0000F8757E2A}";
      spCatalogObject_Subscriber->put_Value(L"EventCLSID",vntName);

      //Set the interface ID for IComMethodEvents.
      vntName = L"{683130A9-2E50-11D2-98A5-00C04F8EE1C4}";
      spCatalogObject_Subscriber->put_Value(L"InterfaceID",vntName);

      //Now create the subscriber object, which is just a simple
      //ATL Object where we implement IComMethodEvents to get
      //events for OnMethodCall, OnMethodReturn, and OnMethodException
      if(NULL == ptrSubscriber)
         ptrSubscriber.CreateInstance(L"Subscriber.
                                      ComPlusMethodEvent.1");

      //Get the IUnKnown of subscriber object, as this would be
      //mapped to our new Catalogobject of ""TransientSubscriptions""
      if(varUnknown.vt == VT_EMPTY)
         varUnknown = ptrSubscriber->GetInterfaceUnknown();

      spCatalogObject_Subscriber->put_Value(L"SubscriberInterface",
                                            varUnknown);
      spCatalogObject_Subscriber->put_Value(L"Enabled",
                                            _variant_t (true)) ;

      //Save the changes to the ""TransientSubscriptions"" catalog
      //collection.
      long lret = spCatalogCollection_Subscriber->SaveChanges();

      //We need to get the subscription ID and save it so that we
      //will be able to remove the subscription.
      //Must be Global varaible
      spCatalogObject_Subscriber->get_Value(L"ID",&VarOutSubscribtion);



      /************************************************************
      THE SUBSCRIBER INFORMATION HAS BEEN SAVED. NOW WE HAVE TO
      SAVE ABOUT PUBLISHER OR WE CAN SAY THE OBJECT TO WHICH WE
      WANT TO MONITOR
      ************************************************************/


      //Now we need to get the ""TransientPublisherProperties""
      //collection.
      _variant_t VarOutKey;
      spCatalogObject_Subscriber- get_Key(&VarOutKey);

      ICatalogCollectionPtr spCatalogCollection_Publisher =
         (ICatalogCollectionPtr)spCatalogCollection_Subscriber->
         GetCollection(L"TransientPublisherProperties",VarOutKey);

      spCatalogCollection_Publisher->Populate();

      //Add a new TransientPublisherProperties object.
      ICatalogObjectPtr spCatalogObject_Publisher =
         (ICatalogObjectPtr)spCatalogCollection_Publisher->Add();

      //Here we are going to set the publisher we want to subscribe to.
      //This will be the AppID of the COM+ application we want to monitor.
      //OR Could be the PID also of any com object
      //Com+ Event Service will send info of given object to our
      //Subscriber object.

      UpdateData();

      if(((CButton *)GetDlgItem(IDC_RADIO1))->GetCheck() &&
         m_ID.IsEmpty() != TRUE)
      {
         vntName = L"AppID";        //If it is an AppID
         spCatalogObject_Publisher->put_Value(L"Name",vntName);
         vntName = m_ID.AllocSysString();
      }
      else if (((CButton *)GetDlgItem(IDC_RADIO2))->GetCheck()
               && m_ID.IsEmpty() != TRUE)
      {
         vntName = L"ProcessID";    //If it is an PID
         spCatalogObject_Publisher->put_Value(L"Name",vntName);
         vntName = m_ID.AllocSysString();
      }
      else
      ::MessageBox(0,"Can't Subscribe , Invalid Com+ Application to
                   monitor.", "ERROR",0);


      spCatalogObject_Publisher->put_Value(L"Value",vntName);


      //Finally, save the changes on the TransientPublisherProperties
      //collection.
      lret = spCatalogCollection_Publisher->SaveChanges();
   }
   catch(_com_error &e){

   ::MessageBox(0,(char*) e.Description(), "ERROR",0);

}
bSubscribeStatus = true;



}
/*************************************************
OnUnsubscribe()
To Unsubscribe the Subscribtion.
**************************************************/
void CComPlusObjectSpyDlg::OnUnsubscribe()
{
   try
   {
      //Get a COMAdminCatalog object. This object is defined in the
      //COMAdmin namespace.
      ICOMAdminCatalogPtr spCatalog("COMAdmin.COMAdminCatalog");

      //Get the TransientSubscriptions collection and populate it.
      ICatalogCollectionPtr spCatalogCollection_Subscriber =
         (ICatalogCollectionPtr)spCatalog->
         GetCollection(L"TransientSubscriptions");
      spCatalogCollection_Subscriber->Populate();

      long lCount = 0;
      int i = 0;
      ICatalogObjectPtr spCatalogObject_Subscriber;
      lCount = spCatalogCollection_Subscriber->Count;

      // If the count is 0, then there are no transient subscriptions.
      if (lCount == 0)
      {
         return;
      }

      //Loop through the collection until we find the subscription
      //we want to remove. Remember, we saved the subscription ID
      //when we created the subscription.
      for (i=0; i<lCount; i++)
      {
         spCatalogCollection_Subscriber->get_Item(i,(IDispatch **)
            &spCatalogObject_Subscriber);

         _variant_t VarOutSubscribtionTemp;
         spCatalogObject_Subscriber->get_Value(L"ID",
            &VarOutSubscribtionTemp);
         if (VarOutSubscribtion == VarOutSubscribtionTemp)
         {
            spCatalogCollection_Subscriber->Remove(i);
            spCatalogCollection_Subscriber->SaveChanges();
            break;
         }
      }



   }
   catch(_com_error &e){

      ::MessageBox(0,(char*) e.Description(), "ERROR",0);

   }
   bSubscribeStatus = false;



}

I think it was small to implement but big to explain. Anyway, that's it this was all to do. You can monitor any of your COM Components or Services by setting aside without any load or code in your application.

The Administrator has the Button like "OK" that simply hides the application and "Cancel" to Close all. Here is the simple output of Administrator if your Publisher gets the call to its method.

When Method calls happen in Publisher, you will be notified to your Subscriber, which I can show by a simple MessageBox.

[MethodCall.jpg]

Now, the same step will hapen when a method returns from the call. You can get the return value as I show in the MessageBox again.

[MethodReturn.jpg]

I hope this all may get you more knowledge to understand all these interfaces. Let's see how much success I've had to make it useful for all of us. Thanks to all those articles and sample codes from which I went through to make it at all in one place with C++. Thanks for all of your patience and interest to cope with me.



About the Author

Pardeep Kadian

Be young by heart, sharp by mind.
doesn't matter to have a whisky or wine.
Neither it belong to you nor mine.
Keep learning to always have a shine.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

Most Popular Programming Stories

More for Developers

RSS Feeds