This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


MIND

Make the Internet Your Office with NetMeeting
Suresh G. Nair

Microsoft NetMeeting lets users collaborate with audio, video, whiteboard, and application sharing. And now you can add its features to your Weblication!

Download the code (6KB)

I've burned a lot of time playing around with the Internet Client SDK. I was particularly astounded when I got to the NetMeeting® section. The power NetMeeting can add to applications and the simplicity of its object model are amazing. The NetMeeting 2.1 SDK has made phenomenal improvements from the previous version with the introduction of a component object model.
      I'll show you how to use NetMeeting COM-based objects to add NetMeeting functionality to your own applications. I'll analyze the NetMeeting object model and establish a generic approach for supporting NetMeeting functionality using its objects. Then I'll take a closer look at some of the key NetMeeting objects and how to use the generic approach to develop an ActiveX® Template Library (ATL)-based NetMeeting wrapper. This ATL wrapper will help add NetMeeting functionality to your applications with minimal effort. Finally, I will illustrate how to use the generic wrapper to add application sharing, audio, video, and remote-launching conferencing capabilities to other applications.

Overview
      The Microsoft® NetMeeting 2.1 SDK enables developers to integrate conferencing capabilities into other applications in two ways. You can write applications that work within NetMeeting itself, or you can add NetMeeting functionality to your own code, replacing the NetMeeting user interface. The SDK is organized into four categories based on the functionality it provides: the component object model, the Internet Locator Server (ILS) object model, the NetMeeting ActiveX control, and the Installable Codecs services.
      The NetMeeting component object model exposes interfaces and methods that let you add Internet video conferencing, Internet phone/audio conferencing, or data conferencing to other applications through both application sharing and file transfer. The ILS object model provides a complete Lightweight Directory Access Protocol (LDAP) client API for accessing all the capabilities of the ILS directory used by NetMeeting. The NetMeeting ActiveX control is provided so that conferencing functionality can be integrated into Web pages. Finally, the Installable Codecs services expose interfaces and structures so that vendors of audio and video codecs can install their products for use in NetMeeting applications.

Setting Up the Sample Application
      Let's investigate how to set up a standard MFC application to use NetMeeting objects. First, install the NetMeeting SDK (it can be found at any of the locations listed at the end of this article). Next, open a Visual C++®-based project, then choose Project | Add to Project | Files and insert the interface definition file imsconf2.idl (from the NetMeeting SDK's include directory) into the project. Under project settings for imsconf2.idl, type in the following custom build command:


 midl /client none /server none /ms_ext /W1 /c_ext /env win32 /Oicf  /dlldata
 $(TargetDir)\dlldata.rpc /proxy $(TargetDir)\$(InputName).rpc /header   
 $(InputName).h /iid $(InputName).c $(InputPath)
Add .\$(InputName).h as the output file name.
      During the build process initiated by this custom build command, the MIDL compiler will generate the files imsconf2.h and imsconf2.c. Edit stdafx.h so it includes the file imsconf2.h. Choose Project | Add to Project | Files and add imsconf2.c into the project. A warning dialog will appear, saying that the file does not exist. Select Yes to ignore this, because the file will be created during the first build. Once you have completed these steps, the project will be ready to use all of the NetMeeting objects.

NetMeeting Objects
      NetMeeting exposes a simple component object model to let you integrate conferencing capabilities into your applications. Figure 1 shows the objects and interfaces that are exposed by the object model.
      As you examine each of these objects, you'll see that, except for Local System and Member, each object supports COM connection points. This makes your design choices a lot easier. The ActiveX Template Library provides a simple mechanism to add support for connection point interfaces. (For details on how to implement connection points and sink interfaces using ATL, or how to use ATL with MFC, please refer to the links at the end of this article.) Combining ATL and the NetMeeting object model makes adding conferencing capabilities to an application easy, since it hides the mechanics of managing interfaces and connection points, allowing you to concentrate on adding functionality to the application.
      To support any of these connectable objects in an application, you will need to manage an instance of the object's interface and implement the object's connection point sink. The important thing to note here is that for all of these connectable objects, you must establish the object-to-connection-point-sink connection before you can call any methods on the object's interface.

Figure 2: Object Interaction
      Figure 2: Object Interaction

      To make life a little easier, you can implement an object instance manager that will manage the object's interface pointer and connection with the sink and wrap any of the calls to the object's interface. The overall object interaction is illustrated in Figure 2. Thus for every connectable object that has to be supported in the application, you need an object instance manager that will have a pointer to the NetMeeting object's interface, the connection point cookie to manage the sink connection, and function support for the connection point notifications. The corresponding sink interface will have a pointer to the object instance manager to pass on the information the sink gets through notifications from the server. The beauty of this object model is that once you understand the basic structure, most of the NetMeeting support in the application can be done in almost no time.
      Two NetMeeting connectable objects are key. The Conference Manager object manages the entire conferencing system, while the Conference object provides the context within which all the channels exist. To provide any of the NetMeeting functionality in an application, you must do at least two things: create and manage an instance of the Conference Manager object and implement its connection point, and manage instances of the Conference object and implement its connection point. Let's take a detailed look at these objects.

The Conference Manager Object
      The Conference Manager is where all the action begins. The Conference Manager uses its INmManager interface to expose methods that activate and initialize the conferencing system, get local system information, and create calls and conferences (see Figure 3). You can create any number of calls and conferences using this interface.

Figure 3: The Conference Manager
      Figure 3: The Conference Manager

      The Conference Manager object will notify the application using the connection point sink interface, INmManagerNotify, whenever a conference or call is initiated or when a conference change occurs. Following the general approach, the object interface manager class, CNMManager, encapsulates the INmManager interface, as shown in Figure 4. This code uses the smart pointer CComPtr to manage the NetMeeting interface. The connection point sink will be managed through the smart pointer's cookie. Implemented this way, the only data that you need to store in the application to use the CNMManager object is a pointer to its instance. The object instance manager and the connection point sink implementation go hand in hand.
      Before looking at the methods of CNMManager, let's take a quick look at the sink interface. The Conference Manager sink, INmManagerNotify, requires that you implement three notifications: NmUI, ConferenceCreated, and CallCreated. All of these event handlers in the sink objects must return S_OK to the sink source. The connection point sink implementation CNMManagerEventSink (see Figure 5) is a typical ATL sink interface, with the exception of the Initialize method to set the pointer to the interface manager, CNMManager.
      Let's examine the actual implementation of some of the important CNMManager methods. First and foremost is CNMManager::Initialize, shown in Figure 6. This method creates the INmManager instance, sets up the connection point, decides which channels from the conferencing system will be supported, and establishes the level of control that the application needs on NetMeeting. The first parameter, uchCaps, has a default value of NMCH_ALL, meaning the program should allow all the applications to access all the channels that NetMeeting supports. Depending on an application's needs, you can choose any combination of the channels that you want to provide to the user. For example, you can have application-sharing channels only (NMCH_SHARE), or video and audio channels only (NMCH_VIDEO | NMCH_AUDIO).
      The value of the second parameter, uOptions, determines the level of control that the program will have over the NetMeeting UI and execution. It can take one of the three values shown in Figure 7. CNMManager sets the uOptions parameter to NM_INIT_CONTROL by default, indicating that the application will provide a custom user interface for NetMeeting.
      Note that the only information about the sink that you need to store is the cookie that uniquely identifies the connection. In the CNMManager destructor, the stored cookie value is used to remove the connection and destroy the sink instance:

 CNMManager::~CNMManager ()
 {
     ATLTRACE( "CNMManager::~CNMManager\n" );
 
     AtlUnadvise(m_pManager, IID_INmManagerNotify, m_dwManagerCookie);
 }
In the destructor, you don't have to do anything with the INmManager interface pointer, m_pManager. The CComPtr smart pointer will take care of the reference count management and release the memory for you.
      As I mentioned earlier, NetMeeting objects will notify the sink whenever specific events occur. One such notification is common to all the NetMeeting objects: NmUI. The server sends a NmUI notification to the sink whenever a change occurs that might require notification to be displayed to the user. Two other events can be fired from the Conference Manager. ConferenceCreated is sent when a Conference object is created. It can be either auto-created by the system because of an incoming call or created by the application by calling INmManager::CreateConference.CallCreated is sent when a Call object is created. It can be sent as a result of an incoming call or an outgoing call placed by calling INmManager::CreateCall.
      To provide a common access point to the NetMeeting objects that are created, the server will send pointers to the objects as parameters when firing these notifications. When the application gets the notifications, it should initialize the object interface managers and set up the connection points for these connectable objects. Generally, the sink should pass the notification information to the object interface manager. The Conference Manager notification support functions CNMManager::OnConferenceCreated and CNMManager:: OnCallCreated will create the corresponding object interface manager instances and initialize them (see Figure 8).
      After setting up the notification routines like this, the only task the two create routines, CNMManager::CreateConference and CNMManager::CreateCall, will have to perform is to turn around and call the corresponding functions in the INmManager interface through the pointer m_pManager.

The Conference Object
      The Conference object manages a specific conference and all the channels in it. It also keeps track of the individual conference participants. A Conference object is the context within which channels exist. Creating a call through INmManager::CreateCall, hosting a local conference through INmManager::Host, or accepting an external call through INmCall::Accept will all make a conference active. The conferencing system can only have one active conference at any given time. An active Conference object auto-creates all of the channel types that you requested in the INmManager:: Initialize call except the data channel; the application can create as many data channels as it needs. Through its connection point sink interface INmConferenceNotify, the Conference object will notify the application whenever the conference state, channel state, or member state changes. An idle conference cannot have any members or channels.
      The conference interface INmConference exposes a number of methods to manage conference members and channels and to launch remote applications. Following the general approach, I will now create CNMConference, the object instance manager for the Conference object. CNMConference will have a pointer to the INmConference interface, the conference sink interface cookie, and data members to hold the specific channels that I want to handle (see Figure 9).
      ATL does not support collection classes. Therefore, wherever the application needs to handle multiple channels of the same type, as in the case of the video channel, I'll use the Standard Template Libraries (STL) vector template. ATL and STL together provide an ideal combination for developing lightweight applications. In this sample I will be handling the application-sharing and video channels only. Depending on the application's need, any or all of the remaining channel data members can be added. CNMConference exposes the LaunchRemote method for remote application launching support. I'll discuss how to set up an application for remote launching and how to remote launch an application later in this article.
      Apart from NmUI, the Conference object sends three more notifications through its connection point sink INmConferenceNotify. StateChanged sends notification when the conference state changes to active, idle, initializing, or waiting; MemberChanged sends notification when someone joins or leaves the conference or a member's capabilities have been updated; and ChannelChanged sends a notification when a channel for the conference has been added, updated, or removed.
      When the StateChanged notification is fired with the uState value set to NM_CONFERENCE_IDLE, you need to free all the channels and release the conference sink. Remember, an idle conference cannot have any channels. The MemberChanged notification can be used to keep track of the members in the current conference. Figure 10 shows the conference connection point sink CNMConferenceEventSink, which handles these notifications.
      The CNMConference::Initialize method will set up the connection point with the Conference sink interface CNMConferenceEventSink as shown here:


 void CNMConference::Initialize ()
 {
      ATLTRACE( "CNMConference::Initialize\n" );
     
     //Establish the connection point
     CComObject<CNMConferenceEventSink>* pSink;
     CComObject<CNMConferenceEventSink>::CreateInstance(&pSink);
     pSink->Initialize (this);
     AtlAdvise(m_pConference, pSink->GetUnknown(), 
               IID_INmConferenceNotify, 
               &m_dwConferenceCookie);
 }
      As in the case of the Conference Manager, the stored cookie value is used to remove the sink connection and destroy the sink instance in the CNMConference destructor, as shown in Figure 11. The destructor will release all the channels before removing the sink connection, and finally the INmConference interface will be released by the smart pointer destructor.

Channel Management and Channels
      All of the channel management is based on the ChannelChanged notification. CNMConferenceEventSink will get the ChannelChanged notification whenever a channel is added, removed, or updated. A pointer to the channel being addressed is also sent as a parameter to the notification. The sink will respond to the notification based on the notification code and the type of the channel sent. Figure 12 shows a typical ChannelChanged function.
      INmChannel is the base interface for all communication channel types, including audio, video, data, file transfer, and application sharing, and is used for general channel manipulation. CNMConference::OnAddChannel (see Figure 13) will create the corresponding object instance manager for the channel, based on the channel type returned by pIChannel->GetNmch, and will initialize its connection point sink. CNMAppShare and CNMChannelVideo are the object instance managers for the application-sharing channel and the video channel. CNMConference:: OnRemoveChannel, also shown in Figure 13, will release the object instance manager—and hence the channel and the connection point sink—after making sure that you are getting the notification for the correct channel.
      INmChannel::IsSameAs indicates whether two Channel objects actually represent the same channel. The CNMConference::OnUpdateChannel method can be used to display specific channel information, like whether the channel is active or inactive, using INmChannel::IsActive.

The Application-sharing Channel
      The application-sharing channel lets a user share local applications with other conference members. Members can all review the same data or information and see the actions as the person sharing the application works on the program (for example, editing content or scrolling through information). They can share Windows®-based applications transparently without any special knowledge of the application capabilities. Only the person sharing the program needs to have the given application installed on their computer. Let's first look at the object interface manager, CNMAppShare, which encapsulates the NetMeeting application-sharing channel interface, INmChannelAppShare, as shown in Figure 14.
      CNMConference::OnAddChannel creates a CNMAppShare object with a pointer to the base interface INmChannel so the constructor will get the correct derived interface pointer and initialize the m_pAppShare member variable as shown here:


 CNMAppShare::CNMAppShare (INmChannel* pIChannel)
 {
     ATLTRACE( "CNMAppShare::CNMAppShare \n");

     if (FAILED(pIChannel->QueryInterface(IID_INmChannelAppShare,
                                          (void **)&m_pAppShare)))
     {
         m_pAppShare = NULL;
     }
 }
      INmChannelAppShare contains two important methods: SetState and SetAppShareState. SetState lets the user set the level of control each conference member has on an application the user has shared on their local machine. The three supported levels are listed in Figure 15. The main window handle of the CNMAppShare::SetAppShareState method (see Figure 16) allows any local application to be shared among the conference members. m_pAppShare->EnumSharableApp returns a list of applications in the local system that can be shared, each represented by an instance of the Application object. The Application object exposes the INmSharableApp interface, which can be used to identify the application and manage its sharing state.
      The application Sharing Channel object sends two notification messages apart from NmUI through its connection point sink interface, INmChannelAppShareNotify. The StateChanged notification is sent when a shareable application on the local computer becomes shared or is no longer being shared, while the MemberChanged notification indicates that a member of the application-sharing channel was added, removed, or updated. The connection point sink CNMAppShareEventSink, shown in Figure 17, implements the INmChannelAppShareNotify interface.
      Just like the Conference object's Initialize method, the CNMAppShare::Initialize method will set up the connection with connection point sink interface, and the destructor will use the cookie value to release the connection. The StateChanged and NmUI notifications can be used to update the application-sharing status on the UI.

The Video Channel
      A video channel lets the user send and receive real-time visual images to another conference participant. NetMeeting 2.1 allows audio and video conferencing with only one other meeting participant at a time, unlike with other channels. Therefore, there can be only one active incoming video channel and one active outgoing video channel at any given time. These channels are managed using the Video Channel object. The two channels can be distinguished by using the INmChannel::IsIncoming method that is shown in the CNMConference::GetVideoChannel method in Figure 18.
      The basic structure of the video and all other channel support classes is the same as that of the application-sharing channel. Therefore, in this section I'll concentrate on the important video channel functions. The Video Channel object interface, INmChannelVideo, exposes a SetProperty method with which you can control just about everything related to the video channel. The SetProperty method takes three parameters. The first parameter, uID, can take any of the values shown in Figure 19. The values of the other two parameters, wLow and wHigh, depend on the uID value.
      The Video Channel object, through its connection point notification sink INmChannelVideoNotify, sends three notifications apart from NmUI. StateChanged is sent whenever the video state changes to idle, previewing, transferring, or local and/or remote video pauses. The MemberChanged notification will indicate that a member of the video channel was added, removed, or updated. PropertyChanged will be fired when one of the video properties like window, position, image size, or quality has changed. The notifications can be used to update the video channel status on the user interface.

Launching a Remote Application
      With a NetMeeting-enabled application, users can work together over a local or remote network. But how does the user ensure that the application is running on all the conference members' systems? The Conference object has the ability to launch data channel applications on all or selected conference members' systems. For the Conference object to do this, you need to ensure that all participants have the application installed on their systems and that the application is registered for conferencing.
      In NetMeeting 2.1 all the programs that can be remotely launched in a system must be indicated in the user's system registry. To safeguard the applications from any future registry key moves, Microsoft has come up with a convenient way to register applications for conferencing using the local system information object INmSysInfo's SetNmApp method:


 HRESULT hr = E_FAIL;
 CComPtr<INmSysInfo> pSysInfo;
 CComBSTR bstrDir  ("C:\NMDemo");
 CComBSTR bstrPath ("C:\NMDemo\NMDemo.exe");
 
 //Get the Local system information from the conference manager
 hr = GetNmManager()->GetSysInfo(&pSysInfo);
 hr = pSysInfo->SetNmApp( IID_NMDEMOGUID, bstrPath, NULL, bstrDir ); 
In the SetNmApp call, IID_NMDEMOGUID is the application's GUID, bstrPath is the file specification for the application, and bstrDir is the current drive and directory for the application.
      The third parameter to the SetNmApp call is the actual command line to execute, allowing you to pass arguments to the application if necessary. If this parameter is NULL, the value of the second parameter bstrPath will be used to execute the application. With the application installed and registered for conferencing on all the participating systems, all you need to do to launch it remotely is call CNMConference::LaunchRemote. This function, shown below, can launch the application on all of the conference members' systems or a selected member's system.

 HRESULT CNMConference::LaunchRemote(GUID* pguidApp, 
                                     INmMember *pMember)
 {
     ATLTRACE( "CNMConference::LaunchRemote\n");
     
     HRESULT hr = E_FAIL;
     if (NULL != pguidApp)
         hr = m_pConference->LaunchRemote( *pguidApp, pMember);
     return hr;
 }
In this call, the second parameter, pMember, can be used to specify a single conference member if you want to launch the application only on that member's system. If it is set to NULL, the application will be launched on all conference members' systems.

Using the ATL Wrapper in an Application
      Now for the fun part. You're ready to reap the rewards of all the hard work you have done so far in creating the ATL wrapper for the Conference Manager, Conference, and Channel objects. The first step is to add the m_pNmManager member variable to the document class. (This assumes, of course, that you're continuing with the MFC application that I set up at the beginning of this article. You can use this wrapper to add NetMeeting functionality to any C++ application—with or without MFC support.) Adding the following statements will initialize the conferencing system with full control of the NetMeeting UI:


 m_pManager = new CNMManager();
 m_pManager->Initialize();
      Once the system is initialized, you can create a conference with all of the NetMeeting channel support with two lines of code:

 CComBSTR bstrName ("TestConference");
 m_pManager->CreateConference(bstrName);
Now the application can call the members and participants it wants in this conference using CNMManager::CreateCall. Once the conference is active, it will auto-create all the channels in the application, and in the process instantiate all the necessary support classes. With this infrastructure in place, adding a ShareMe button to auto-share the application among the conference participants can be as simple as adding the message handler shown in Figure 20.
      Within the active conference, the application can be remotely launched on all the participants' systems with the command

 m_pManager->GetCurrentConference()->LaunchRemote(GetAppGuid());
where GetAppGuid will return the application's GUID. The following code segment will position the incoming video channel window on the left-hand side of the application, halfway from the top, and will make it visible:

 CRect rectWindow;
 CNMChannelVideo* pIncomingVideo = 
    m_pManager->GetCurrentConference()->GetVideoChannel();
 
 AfxGetMainWnd()->GetWindowRect(&rectWindow);
 
 
 pIncomingVideo->SetProperty(NM_VIDPROP_WINDOW_POSITION,
                             rectWindow.left, rectWindow.right/2);
 pIncomingVideo->SetProperty(NM_VIDPROP_WINDOW_VISIBLE, 1);

Conclusion
      The Microsoft NetMeeting SDK provides a simple yet powerful component object model that lets you integrate conferencing capabilities with other applications. As long as you play by the rules of COM and follow the design of the object model, NetMeeting development can be a lot of fun. The generic approach and the ATL library developed here will double your pleasure, as you will be able to concentrate on adding functionality, not on the mechanics involved.
      The Microsoft NetMeeting SDK can be found at http://www.microsoft.com/netmeeting/sdk or on your MSDN CDs. To use this SDK, you will also need the final version of NetMeeting 2.1, which is available at the Microsoft NetMeeting home page (http://www.microsoft.com/netmeeting).

MSDN
http://msdn.microsoft.com/workshop/messaging/netmtg/netmtgcom.asp
http://www.microsoft.com/netmeeting/sdk

From the January 1999 issue of Microsoft Internet Developer.