Enhancing Rich Client Communications with the Microsoft Real-Time Communications API

 

Jim Huang
Sr. Technical Marketing Engineer
Intel Corporation

February 2003

Applies to:
   Microsoft® Windows® XP

Summary: Learn how to use the Real-Time Communication (RTC) Client API to incorporate functionalities such as presence information, profiles, and buddy list in order to create a community and track people's availability. This article builds on an earlier article, Integrating Rich Client Communications with the Microsoft Real-Time Communications API. (26 printed pages)

Contents

Introduction
Get Ready
Initializing for Presence and Buddy List Notifications
Enabling and Detecting Presence
SIP Server Registration
Creating the XML Profile
Creating the Buddy List and Watcher Object
Deregistering and Disabling a Profile
Optimizing for Performance
Conclusion
Resources

Introduction

In an earlier article, Integrating Rich Client Communications with the Microsoft Real-Time Communications API, we discussed how simple and straightforward it is to integrate functionalities such as audio/video conferencing, instant messaging, and application sharing to your application using the Real-Time Communications (RTC) Client API.

This article extends the discussion to how easy it is to use the RTC Client API to add functionalities such as presence information, profiles, and buddy list to create a community. You will learn the steps to enable this capability using the Windows XP application created in the first article.

Get Ready

The discussion assumes that you are familiar with XML Schema. Refer to the first article for information on initializing the RTC object.

The source code that accompanies this article is available in two versions:

  • The RTCSampleCode.exe sample code demonstrates the full functionality of enabling audio/video conferencing, instant messaging, presence, and a buddy list.
  • The AVDConf2.exe sample code, available from Intel's® Real-Time Communication Sample Application page, contains the same capabilities as the RTCSample.exe sample code, with additional features such as whiteboard sharing, application sharing, Intel® Pentium® 4 processor with Hyper-Threading Technology detection, and a CPU utilization meter. The AVDConf2.exe code uses many samples from RTCSample.exe and the RTC Code snippets from the Microsoft Software Developers Network Web site.

Note Hyper-Threading Technology requires a computer system with an Intel Pentium 4 processor at 3.06 GHz or higher, a chipset and BIOS that utilize this technology, and an operating system that includes optimizations for this technology. Performance will vary depending on the specific hardware and software you use. For more information, see Hyper-Threading Technology for more information.

What you need:

  • Visual Studio® 6.0 with SP5
  • Microsoft Platform SDK
  • SIP Server or Windows .NET Server Beta 3

Session Initiation Protocol Server

Currently, to integrate presence, profiling, and a buddy list into your application, you need the SIP server or Windows .NET Server Beta 3. The SIP registrar server is required when presence information is desired, such as enabling the Buddy List feature. Users can register their presence information with the server and retrieve the presence information of others with this service.

The server uses Session Initiation Protocol (SIP) and its related protocol SIMPLE as the underlining communication protocol. SIP provides excellent support for multi-modal communications. SIP and SIMPLE are more than just protocols for text messaging sharing; they can manage voice, video, application sharing, and more.

Session Initiation Protocol

The SIP protocol initiates sessions in an IP network and registers presence information. A session can be a simple two-way PC-to-PC communication, or it can be a collaborative multi-media conference session. SIP is an Internet Engineering Task Force (IETF) signaling protocol for establishing, manipulating, and tearing down sessions. SIP's main purpose is to help session originators deliver invitations to potential session participants wherever they may be. SIP has been described as a "simple, extensible" IP telephony signaling protocol.

Initializing for Presence and Buddy List Notifications

To receive new event notifications for presence, buddy list, and profiling, set the following event filter masks so your application can receive event notification from the RTC layer. These event masks are in addition to the event mask set in the first sample application.

#define RTCEF_REGISTRATION_STATE_CHANGE     0x00000002
#define RTCEF_BUDDY                         0x00000100
#define RTCEF_WATCHER                       0x00000200
#define RTCEF_PROFILE                       0x00000400

A simple way to register all events is to use the RTCEF_ALL macro to notify the RTC layer to send all events to the application.

   long lEventMask = RTCEF_ALL;

Handling RTC Events

The following code snippet shows the additional events to process when registering to the SIP server with the user profile, sending and receiving presence information, and buddy list events. As each event is received, the event filter method uses the appropriate RTC interface to process the received event.

HRESULT CAVDConfDlg::OnRTCEvent(UINT message, WPARAM wParam, LPARAM lParam)
{
    IDispatch * pDisp = (IDispatch *)lParam;
    RTC_EVENT enEvent = (RTC_EVENT)wParam;
    HRESULT hr;

    // Based on the RTC_EVENT type, query for the 
    // appropriate event interface and call a helper
    // method to handle the event

    switch ( wParam )
    {
   … .

   case RTCE_REGISTRATION_STATE_CHANGE:
            {
                IRTCRegistrationStateChangeEvent * pEvent = NULL;
                // Get the event handle associated with the current session.
   hr = pDisp->QueryInterface( IID_IRTCSessionStateChangeEvent,
                                            (void **)&pEvent );

                if (SUCCEEDED(hr))
                {
                    OnRTCSessionStateChangeEvent(pEvent);
                    SAFE_RELEASE(pEvent);
                }                                            
            }
   break;

   case RTCE_BUDDY:
            {
                IRTCBuddyEvent * pEvent = NULL;
    hr = pDisp->QueryInterface( IID_IRTCBuddyEvent,
                                            (void **)&pEvent );

                if (SUCCEEDED(hr))
                {
                    OnRTCBuddyEvent(pEvent);
                    SAFE_RELEASE(pEvent);
                }
   }
   break;

   case RTCE_WATCHER:
            {
                IRTCWatcherEvent * pEvent = NULL;
                hr = pDisp->QueryInterface( IID_IRTCWatcherEvent,
                                            (void **)&pEvent );

                if (SUCCEEDED(hr))
                {
                    OnRTCWatcherEvent(pEvent);
                    SAFE_RELEASE(pEvent);
                }            }
   break;
    }
   … .
}

Enabling and Detecting Presence

The presence information service enable users to track their contact's presence status, notify their contacts of their status, and call their buddies (or contacts) through a registrar server that maintains current location information of their contacts. The location can be a PC or a telephone, and in the future, a cell phone, pager, or a handheld device.

ms997616.rtc_enhancerichclient-real-timecomm-1(en-us,MSDN.10).jpg

Figure 1. Sample UI with Buddy List and Presence Status

The following diagram shows the high-level steps to register the client on the SIP server and enable Presence.

ms997616.rtc_enhancerichclient-real-timecomm-2(en-us,MSDN.10).gif

Figure 2. Steps to Enable Profile and Presence Service

SIP Server Registration

To enable presence service, you will need to create a Profile object. The Profile object is created by the IRTCClientProvisioning::CreateProfile method. To create the Profile object, the client application will need to create an XML string that conforms to the provisioning schema. Attributes of the XML schema includes:

  • Provision settings — Unique identifier for the Profile.
  • User settings — User's URI, realm, and login account information
  • Client settings — Information about the client application that is not related for communications link. This information is optional.
  • Provider settings — Information about the Internet telephony service provider (ITSP).
  • SIP server settings — Specifies the SIP servers available, the role of the SIP server and the types of session the server can support.

To register the user on the SIP server, the client needs to create a XML profile string to instruct the RTC client API how to communicate to the SIP server. Once the XML string is created, call the IRTCClientProvisioning::CreateProfile() method to create a profile object. Next step is to call the **IRTCClientProvisioning::EnableProfile()**method to register the user on the RTC server and specifies the type of sessions that the profile should be registered for on the server. The possible registration types are: allow incoming PC-to-PC sessions (RTCRF_REGISTER_INVITE_SESSIONS), allow incoming instant messaging session (RTCRF_REGISTER_MESSAGE_SESSIONS), allow incoming watchers (RTCRF_REGISTER_PRESENCE), or allow all of the registration types (RTCRF_REGISTER_ALL). Setting the RTCRF_REGISTER_PRESENCE or RTCRF_REGISTER_ALL will inform the registrar server the client accepts SIP SUBSCRIBE method. This will enable the user to notify other users of their presence status change, get notifications of their contact's presence status change and others who are adding your presence to their Watcher list.

Watcher objects offer the status of users in the Buddy List. The Watcher can query for the status of the Presentity, and is notified of changes in the status of users in the Buddy List. The Watcher also enables users to block or allow others from the status of their Presentity.

The sample code illustrates the steps to register the user and enable presence and the buddy list.

HRESULT CAVDConfDlg::DoSIPLogin(BSTR bXMLObj)
{
    HRESULT hr;

   … .

    // Get the RTC client provisioning interface
    IRTCClientProvisioning * pProv = NULL;

    hr = m_pClient->QueryInterface(
            IID_IRTCClientProvisioning,
            (void **)&pProv);

    if (FAILED(hr))
    {
      // Query Interface failed
           return hr;
    }

    // Create a RTC profile object from the XML
    // provisioning document
    hr = pProv->CreateProfile(bXMLObj, &m_pProfile);

    SAFE_FREE_STRING(bXMLObj);

    if (FAILED(hr))
    {
        // CreateProfile failed
        SAFE_RELEASE(pProv);
        return hr;
    }

    // Enable the RTC profile object
    hr = pProv->EnableProfile(m_pProfile, RTCRF_REGISTER_ALL);

    SAFE_RELEASE(pProv);

    if (FAILED(hr))
    {
        // EnableProfile failed
        return hr;    
    }

    // Enable presence
    // It is best to enable presence immediately after enabling the
    // profile so that incoming watchers will not be lost.
   
   // See below for EnablePresence()  method below for function definition
    hr = EnablePresence(TRUE);

    if (FAILED(hr))
    {
        // DoEnablePresence failed
        DoSIPLogoff();   // See code sample AVDConf2.exe for function sample
        return hr;
    }


   return S_OK;
}

Now that the profile is enabled, you should also enable presence to prevent loss of incoming watcher events. The EnablePresence() method also illustrates how to create the buddy list.

HRESULT CAVDConfDlg::EnablePresence(BOOL bEnable)
{
    IRTCClientPresence * pPresence = NULL;
    HRESULT hr;

    // Cleanup the buddy list
    ClearBuddyList();

    // Get the RTC client presence interface
    hr = m_pClient->QueryInterface(
            IID_IRTCClientPresence,
            (void **)&pPresence);

    if (FAILED(hr))
    {
        // QueryInterface failed
        return hr;
    }

    // Get the location of the presence storage
    VARIANT varStorage;
    VariantInit(&varStorage);
    varStorage.vt = VT_BSTR;
    varStorage.bstrVal = SysAllocString(L"presence.xml");

    // If we are disabling presence, save the latest
    // copy of the presence data to presence.xml file.
    if (!bEnable && m_bPresenceEnabled)
    {
        hr = pPresence->Export(varStorage);

        if (FAILED(hr))
        {
            // Export failed
            SAFE_RELEASE(pPresence);
            VariantClear(&varStorage);
            return hr;
        }
    }

    // Enable presence
    hr = pPresence->EnablePresence(
        bEnable ? VARIANT_TRUE : VARIANT_FALSE, varStorage);
    
    VariantClear(&varStorage);

    if (FAILED(hr))
    {
        // EnablePresence failed
        SAFE_RELEASE(pPresence);
        return hr;
    }

    // Set the enabled flag
    m_bPresenceEnabled = bEnable;

    // If we are disabling presence, cleanup the
    // presence data
    if (!bEnable)
    {
        // Cleanup buddies
        IRTCEnumBuddies * pEnumBuddy = NULL;
        IRTCBuddy * pBuddy = NULL;

      if (FAILED(hr))
      {
            // Enumerate buddies failed
            SAFE_RELEASE(pPresence);
            return hr;
      }
        
      // Enumerate user's buddy list.      
      hr = pPresence->EnumerateBuddies(&pEnumBuddy);
   
        if (FAILED(hr))
        {
            // Enumerate buddies failed
            SAFE_RELEASE(pPresence);
            return hr;
        }

        while (pEnumBuddy->Next(1, &pBuddy, NULL) == S_OK)
        {
            pPresence->RemoveBuddy(pBuddy);

            SAFE_RELEASE(pBuddy);
        }

        SAFE_RELEASE(pEnumBuddy);

        // Cleanup watchers
        IRTCEnumWatchers * pEnumWatcher = NULL;
        IRTCWatcher * pWatcher = NULL;

        hr = pPresence->EnumerateWatchers(&pEnumWatcher);
   
        if (FAILED(hr))
        {
            // Enumerate watchers failed
            SAFE_RELEASE(pPresence);
            return hr;
        }

        while (pEnumWatcher->Next(1, &pWatcher, NULL) == S_OK)
        {
            pPresence->RemoveWatcher(pWatcher);

            SAFE_RELEASE(pWatcher);
        }

        SAFE_RELEASE(pEnumWatcher);
    }

    SAFE_RELEASE(pPresence);

    return S_OK;
}

Notice the second parameter of the pPresence->EnablePresence() function, which requires a variant storage to retain the buddy list and the allowed and blocked watcher lists. The variant storage can be in the form of a file name (BSTR), a DOMDocument object, or any object that supports IStream, ISequentialStream, or IPersistStream

After registering with the RTC server, the application receives the RTCE_REGISTRATION_STATE_CHANGE event, which indicates whether the registration completed or failed.

The Presence service can be enabled prior to registering your Profile on the server. The diagram below shows how the Presence service can be enabled prior to registration.

This enables the user to set their status prior to registering on the server. The user can now register on the SIP server, but can be appeared "Off-line" to their Watchers and see his contacts' status. This feature is helpful when a user needs to monitor the group's presence status without being intrusive.

ms997616.rtc_enhancerichclient-real-timecomm-3(en-us,MSDN.10).gif

Figure 3. Steps to Enable Presence Before Registration.

Note the CreateProfile() method can be call after setting the Presence status.

The following is the XML Schema.

<?xml version="1.0" ?>
  
<Schema name="ProvisioningSchema.xml" 
        xmlns="urn:schemas-microsoft-com:xml-data" 
        xmlns:dt="urn:schemas-microsoft-com:datatypes">
     <comments>Schema Version MajorRevisionNumber = 1 
                              MinorRevisionNumber = 0
</comments> 
  
<ElementType name="provision" model="closed">
     <AttributeType name="key" required="yes" /> 
     <attribute type="key" /> 
     <AttributeType name="name" required="yes" /> 
     <attribute type="name" /> 
     <AttributeType name="expires" dt:type="dateTime.tz" /> 
     <attribute type="expires" /> 
     <element type="user" minOccurs="1" maxOccurs="1" /> 
     <element type="sipsrv" minOccurs="1" maxOccurs="*" /> 
     <element type="client" minOccurs="0" maxOccurs="1" /> 
     <element type="provider" minOccurs="0" maxOccurs="1" /> 
</ElementType>
  
<ElementType name="user" model="closed">
     <AttributeType name="uri" required="yes" /> 
     <attribute type="uri" /> 
     <AttributeType name="account" required="no" /> 
     <attribute type="account" /> 
     <AttributeType name="name" required="no" /> 
     <attribute type="name" /> 
     <AttributeType name="password" required="no" /> 
     <attribute type="password"   /> 
     <AttributeType name="realm" required="no" /> 
     <attribute type="realm" /> 
</ElementType>
  
<ElementType name="client" model="closed">
     <AttributeType name="name" required="yes" /> 
     <attribute type="name" /> 
     <AttributeType name="banner" 
                    dt:type="bool" 
                    required="no" /> 
     <attribute type="banner" /> 
     <AttributeType name="updates" 
                    dt:type="bool" 
                    required="no" /> 
     <attribute type="updates" /> 
     <AttributeType name="minver" 
                    dt:type="fixed.14.4" 
                    required="no"/>
     <attribute type="minver" /> 
     <AttributeType name="curver" 
                    dt:type="fixed.14.4" 
                    required="no"/>
     <attribute type="curver" /> 
     <AttributeType name="updateuri" 
                    dt:type="uri" 
                    required="no" />
     <attribute type="updateuri" /> 
     <element type="data" minOccurs="0" maxOccurs="1" />
</ElementType>
  
<ElementType name="data" model="open" /> 
  
<ElementType name="provider" model="closed" content="mixed">
     <AttributeType name="name" /> 
     <attribute type="name" /> 
     <AttributeType name="homepage" dt:type="uri" required="no" />
     <attribute type="homepage" /> 
     <AttributeType name="helpdesk" dt:type="uri" required="no" />
     <attribute type="helpdesk" /> 
     <AttributeType name="personal" dt:type="uri" required="no" /> 
     <attribute type="personal" /> 
     <AttributeType name="calldisplay" dt:type="uri" required="no" /> 
     <attribute type="calldisplay" /> 
     <AttributeType name="idledisplay" dt:type="uri" required="no" /> 
     <attribute type="idledisplay" /> 
     <element type="data" /> 
</ElementType>
  
<ElementType name="sipsrv" model="closed" content="mixed">
     <AttributeType name="addr" required="yes" /> 
     <attribute type="addr" /> 
     <AttributeType name="protocol" 
                    dt:type="enumeration" 
                    dt:values="TCP UDP TLS" 
                    required="yes" /> 
     <attribute type="protocol" /> 
     <AttributeType name="auth" 
                    dt:type="enumeration" 
                    dt:values="basic digest" 
                    required="no" /> 
     <attribute type="auth" /> 
     <AttributeType name="role" 
                    dt:type="enumeration" 
                    dt:values="proxy registrar" 
                    required="yes" /> 
     <attribute type="role" /> 
     <element type="session" minOccurs="0" maxOccurs="*" /> 
</ElementType>
  
<ElementType name="session" model="closed">
     <AttributeType name="party" 
                    dt:type="enumeration" 
                    dt:values="first third" /> 
     <attribute type="party" /> 
     <AttributeType name="type" 
                    dt:type="enumeration" 
                    dt:values="pc2pc pc2ph ph2ph im" /> 
     <attribute type="type" /> 
</ElementType>
  
</Schema>

Creating the XML Profile

This code illustrates how to create XML profile string.

HRESULT CSIPLogin::CreateXMLProvision(LPSTR szURI, LPSTR szSIPIP, 
                  LPSTR szTransport, BSTR *bstrBuf)
{
   … .
    
   // Generate the XML provisioning document
      wsprintf(szBuf, "<provision key=\"AVDConf_2\" name=\"AVDConf_2\">"
           "<user uri=\"%s\" account=\"\" password=\"\" realm=\"%s\" />"
           "<sipsrv addr=\"%s\" protocol=\"%s\" %s role=\"proxy\">"
           "<session party=\"first\" type=\"pc2pc\" />"
           "<session party=\"first\" type=\"pc2ph\" />"
           "<session party=\"first\" type=\"im\" />"
           "</sipsrv>"
           "<sipsrv addr=\"%s\" protocol=\"%s\" %s role=\"registrar\"/>"
           "</provision>",
           szURIBuf, szRealm,
           szSIPIP, szTransport, bBasicAuth ? "auth=\"basic\"" : "",
          szSIPIP, szTransport, bBasicAuth ? "auth=\"basic\"" : ""
        );

   … .

   return S_OK;
}

In the sample application and code snippets in this article shown above, it was not necessary to include the account userid and password as the SIP server did not require it. However, if you know the SIP server requires a login account, the userid and password can be included in the Profile string to login to the SIP server. The only information needed to register was user Uniform Resource Identifier (URI), realm or domain, RTC server IP, authentication method, and the transport to use to communicate to the server. The supported transports are TCP, UDP, and TLS. The SIP server that was used supported both Basic and Digest authentication. If the authentication is Basic, the transport must be TLS for security reasons.

Creating the Buddy List and Watcher Object

Once the profile is registered and presence is enabled, adding a new user to a buddy list is simple. Use the IRTCClientPresence interface, which provides methods to enable presence, add a buddy, remove a buddy, enumerate watchers, set local presence status, determine how the application handles subscriptions from new Watchers, and set the privacy mode. This sample code does not demonstrate how to implement the privacy mode, but it is worth noting that this feature lets the user create a discrete list of people permitted to call.

    // Get the RTC client presence interface
    IRTCClientPresence * pPresence = NULL;

    hr = m_pClient->QueryInterface(
            IID_IRTCClientPresence,
            (void **)&pPresence);

    if (FAILED(hr))
    {
        // QueryInterface failed
      char szBuf[256];

      wsprintf (szBuf, "Failed to Query Presence Interface\nErr = 0x%x", hr );
      MessageBox ( szBuf );
        return hr;
    }

    // Add the buddy
    IRTCBuddy * pBuddy = NULL;

    hr = pPresence->AddBuddy(
            bstrURI,
            bstrName,
            NULL,
            VARIANT_TRUE,
            NULL,
            0,
            &pBuddy);

    SAFE_RELEASE(pPresence);

    if (FAILED(hr))
    {
        // Addbuddy failed          
        SAFE_RELEASE(pBuddy);
      char szBuf[256];

      wsprintf (szBuf, "Failed to Add Buddy to List.\nErr = 0x%x", hr );
      MessageBox ( szBuf );
        return hr;
    }

    // Update the buddy list entry
    UpdateBuddyList(pBuddy);
    SAFE_RELEASE(pBuddy);

If the new buddy is successfully created, the AddBuddy() method will return a pointer to the IRTCBuddy interface on the newly created Buddy object. Using the IRTCBuddy interface, the client application will be able to get the buddy's presentity URI, name of the buddy, the buddy's status, persistent type, and private data associated with the buddy's presence.

Deregistering and Disabling a Profile

Call the IRTCClientProvisioning::DisableProfile() method to deregister the user from the SIP server. Be sure to release the profile object after calling DisableProfile() method.

Optimizing for Performance

In the earlier article, Integrating Rich Client Communications with the Microsoft Real-Time Communications API, we showed the CPU utilization when running the sample RTC application. On a Pentium 4 processor-based system, there is still a large amount of headroom for background tasks. However, the responsiveness of the application can be hindered by its architecture. To address this problem, create threads to run in parallel and service other activities that require immediate attention. Running the multi-threaded application on a Pentium 4 processor with Hyper-Threading Technology and Windows XP with Service Pack 1 (SP1) will show a significant improvement to the application's responsiveness and enable the application to multitask more efficiently.

Windows XP with SP1 detects a Pentium 4 processor with Hyper-Threading Technology as two logical processors, so Windows XP can schedule twice the amount of work to the CPU as compared to a single logical CPU.

ms997616.rtc_enhancerichclient-real-timecomm-4(en-us,MSDN.10).jpg

Figure 4. Sample UI Running with Hyper-Threading Technology (left) and Running without Hyper-Threading Technology (right)

Figure 3 illustrates the CPU utilization difference on a PC with a Pentium 4 processor with Hyper-Threading Technology and without while the Disk Cleanup utility is running in the background.

Conclusion

The Real-Time Communications (RTC) API lets you build fully functioning conferencing and collaboration tools, whether the communication is PC-to-PC, PC-to-Phone, or Phone-to-Phone. We demonstrated in the earlier article how quickly you can develop an audio and video conferencing application with instant messaging and application sharing. In this article, we extended the capabilities to include presence and buddy list functions to create a community and track people's availability. Couple the extensive RTC API with the Microsoft Real-Time Communications server, and you can build sophisticated, effective corporate communication tools that will increase cross-site team productivity.

Communication applications developed using the RTC API and running on the Pentium 4 Processor with Hyper-Threading Technology and Windows XP with SP1 deliver a higher level of peer communications power with improved responsiveness while performing multiple tasks concurrently.

Resources

Intel® Developer Services

Intel Pentium 4 Processor with Hyper-Threading Technology

Tips on Threading

Integrating Rich Client Communications with the Microsoft Real-Time Communications API

Integrating Windows Real-Time Communications into Applications

Q&A: Instant Messaging Milestone for Real-Time Communications Strategy Press Pass, Microsoft Corporation, December 2002

Next Stop, "Greenwich": Enterprise IM Takes Shape with Platform Roadmap, Press Pass, Microsoft Corporation, October 2002

Microsoft Platform SDK: Real-time Communications (RTC) Client

Microsoft Platform SDK: Real-time Communications: Sample XML Profiles