Code Listing: Basic Event Registration and Other Helper Methods in C#

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

The following C# class is an example class used as the basis for many of the code samples in the Microsoft Unified Communications Client SDK documentation. It provides a set of utilities ranging from COM eventing functionality to tracing functionality that can be used to debug an application.

Event-related functions:

  • AdviseForEvents<T>(source, sink). This method calls into methods on the System.Runtime.InteropServices.ComTypes namespace to Advise for events. In addition, it caches the cookie for a given event source, an event sink, and an event type.
  • UCC_Advise<T>(object source, T sink). This is an overload of AdviseForEvent<T>.
  • UnadviseForEvents<T>(object source, T sink). This method calls into methods on the System.Runtime.InteropServices.ComTypes namespace to Unadvise for a specific event. It uses the cookie cache to determine if the specified event source is advised of the given event sink of the specified event type. This ensures that it will not attempt to implement an unadvised event for an event sink that has not been already advised. Memory resources are allocated for each event advise action and are removed when the advise is canceled. Therefore, to prevent memory leaks, an application should cancel every advised event when it is no longer needed.
  • UCC_Unadvise<T>(object source, T sink). This is an overload of UnadviseForEvent<T>.
  • RaiseEvent(object exeContext, MulticastDelegate mDelegate, EventArgs eventArgs). This method can be used by a client application to raise application-defined events. It is mostly used for revealing the API-defined events to, for example, the user-interface elements of a .NET application.
using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using Microsoft.Office.Interop.UccApi;
using System.IO;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using System.Threading;
using System.ComponentModel;
using System.Reflection;

namespace UccApiComponents
{

    public partial class UccApiUtilities
    {

        // represents the success code, corresponds to S_OK code in COM
        public const int S_OK = 0;

        /// <summary>
        /// structure holds int OUT param of IConnectionPoint.Advise
        /// key is string built of HASH of event source interface, HASH of 
        /// event sink, and int pointer for IUnknown interface of event 
        /// source instance
        /// </summary>
        static Dictionary<string, int> cookieJar;


        /// <summary>
        /// constructor instantiates new empty dictionary
        /// </summary>
        static UccApiUtilities()
        {
            cookieJar = new Dictionary<string, int>();
        }


        /// <summary>
        /// creates unique key value for cookie jar
        /// </summary>
        /// <param name="i">pointer to IUnknown interface of event source</param>
        /// <param name="j">HASH of event sink class</param>
        /// <param name="k">HASH of event source class</param>
        /// <returns></returns>
        static string StringKey(IntPtr i, int j, int k)
        {
            return i.ToString() + j.ToString() + k.ToString();
        }

        /// <summary>
        /// establishes an advisory relationship between event 
        /// source connection point and provided event sink.
        /// casts event source to IConnectionPointContainer to
        /// obtain a connection point. Advises new connection
        /// point of event sink (T)
        /// </summary>
        /// <typeparam name="T">Type of event sink</typeparam>
        /// <param name="source">source of event</param>
        /// <param name="sink">object implementing T</param>
        public static void AdviseForEvents<T>(
            source, 
            sink);
        {
            if (source == null || sink == null)
            {
                throw new ArgumentNullException("source", "AdviseForEvents<T>: Source and sink cannot be null");
            }

            IntPtr i = Marshal.GetIUnknownForObject(source);
            int j = sink.GetHashCode();  // 
            int k = typeof(T).GetHashCode();
            string key = StringKey(i, j, k);

            if (cookieJar.ContainsKey(key))
            {
                return;   // already advise the source of the sink.
            }

            IConnectionPoint cp;
            int cookie;
            IConnectionPointContainer container = (IConnectionPointContainer)source;
            Guid guid = typeof(T).GUID;
            container.FindConnectionPoint(ref guid, out cp);
            cp.Advise(sink, out cookie);

            cookieJar.Add(key, cookie);

        }

        /// <summary>
        /// terminates an advisor connection previously established
        /// </summary>
        /// <typeparam name="T">type of event sink</typeparam>
        /// <param name="source">event source</param>
        /// <param name="sink">class instance implementing T</param>
        public static void UnadviseForEvents<T>(
            object source, 
            T sink)
        {
            if (source == null || sink == null)
            {
                throw new ArgumentNullException("source", "UnadviseForEvents<T>: Source and sink cannot be null");
            }

            IntPtr i = Marshal.GetIUnknownForObject(source);
            int j = sink.GetHashCode();
            int k = typeof(T).GetHashCode();
            string key = StringKey(i, j, k);

            //avoid exception, check to see if cookieJar
            //has key value before referencing item using
            //index syntax
            if (cookieJar.ContainsKey(key))
            {
                int cookie = cookieJar[key];

                IConnectionPointContainer container = (IConnectionPointContainer)source;
                IConnectionPoint cp;
                Guid guid = typeof(T).GUID;

                container.FindConnectionPoint(ref guid, out cp);
                cp.Unadvise(cookie);

                cookieJar.Remove(key);
            }
        }

        /// <summary>
        /// public wrapper for advise
        /// </summary>
        /// <typeparam name="T">type of event sink</typeparam>
        /// <param name="source">class instance event source</param>
        /// <param name="sink">class instance implementing T</param>
        public static void UCC_Advise<T>(object source, T sink)
        {
            AdviseForEvents<T>(source, sink);
        }

        /// <summary>
        /// public wrapper for unadvise
        /// </summary>
        /// <typeparam name="T">type of event sink</typeparam>
        /// <param name="source">class instance event source</param>
        /// <param name="sink">class instance implementing T</param>
        public static void UCC_Unadvise<T>(object source, T sink)
        {
            UnadviseForEvents<T>(source, sink);
        }

        /// <summary>
        /// converts state availability Int to readable string
        /// </summary>
        /// <param name="avail">state availability</param>
        /// <returns>availability string</returns>
        public static string AvailabilityIntToString(int avail)
        {

            if (avail < 3000)
                return "Unknown";

            else if (avail < 6000)
                return "Available";

            else if (avail < 9000)
                return "Busy";

            else if (avail < 12000)
                return "Do Not Disturb";

            else if (avail < 18000)
                return "Away";

            else
                return "Offline";
        }

        /// <summary>
        /// Converts availability from a string to an integer.
        /// </summary>
        /// <param name="avail">String representation of availability</param>
        /// <returns>Integer representation of availability</returns>
        public static int AvailabilityStringToInt(string avail)
        {
            //TODO: Use string.compare

            StringComparer strComparer =
                StringComparer.Create(System.Globalization.CultureInfo.CurrentCulture, true);

            if (strComparer.Compare(avail, "Available") == 0)
                return 3500;

            else if (strComparer.Compare(avail, "Busy") == 0)
                return 6500;

            else if (strComparer.Compare(avail, "Do Not Disturb") == 0)
                return 9500;

            else if (strComparer.Compare(avail, "Away") == 0)
                return 15500;

            else if (strComparer.Compare(avail, "Appear Offline") == 0)
                return 18500;

            else
                return 0;
        }

        /// <summary>
        /// Shows error by popping up a message box. This method maybe modified to log
        /// errors.
        /// </summary>
        /// <param name="errorString">Error string to be shown or logged</param>
        public static void ReportError(string errorString)
        {
            Debug(errorString);

        }

        public static UccOperationContext CreateOperationContext(int operationId, UccContext context)
        {
            UccOperationContext opCtx = new UccOperationContext();
            opCtx.Initialize(operationId, context);
            return opCtx;
        }


        // Firing events: calling an event handler with given event args in the specified execution context 
        public static void RaiseEvent(object exeContext,
                                      MulticastDelegate mDelegate,
                                      EventArgs eventArgs)
        {
            if (mDelegate == null)
                return;

            Delegate[] delegates = null;
            delegates = mDelegate.GetInvocationList();
            if (delegates == null)
                return;

            // Invoke delegates within their threads
            foreach (Delegate _delegate in delegates)
            {
                object[] contextAndArgs = { exeContext, eventArgs };
                (_delegate.Target as ISynchronizeInvoke).Invoke(_delegate, contextAndArgs);
            }
        }

        public static void Trace(object exeContext, EventHandler<TraceEventArgs> eventHandler, string msg)
        {
            if (exeContext != null)
            {
                MulticastDelegate mDelegate = eventHandler as MulticastDelegate;
                if (mDelegate != null)
                {
                    RaiseEvent(exeContext, mDelegate, new TraceEventArgs(msg));
                }
            }
        }

        public static void Trace(string msg)
        {
            msg = DateTime.Now + ": " + msg.Replace("\n", Environment.NewLine);

            StreamWriter sw = new StreamWriter(@"trace.txt", true);
            
            sw.WriteLine(msg);
            sw.Flush();
            sw.Close();
        }

        public static string COMErrorMessage(System.Runtime.InteropServices.COMException ex)
        {
            if (ex == null)
                return string.Empty;

            return string.Format("COMException: {0}\n\tErrorCode: {1}\n\tStackTrace: {2}",
                ex.Message, ex.ErrorCode, ex.StackTrace);
        }

        public static void Debug(string msg)
        {
            System.Diagnostics.Debug.WriteLine(msg);
        }

    }

    
}

See Also

Concepts

Creating and Initializing a Platform Object
Create a Principal Endpoint
Endpoint Events
Code Listing: Programming Sign-in Function