Catching SENS Events in .NET

Microsoft Windows provides a process called the System Event Notification Service. This service raises events relative to interactive logon, network, and power changes. Using this service an application can be notified when network connectivity changes, when available power decreases, or when a person logs on, locks his or screen, or his or her screensaver starts. This service notifies COM+ of these events, which appropriates them to any subscribing application.

In this article, we will focus on the logon events. However, the principles discussed in this article extend to catching and processing network and power events.

Uses of SENS Logon Events
When working in an environment without a Windows Domain to track users or a consistent IM service, it would be good to know when co-workers are at their desks. An application could track when someone has logged into his or her computer, when the screensaver turns on, or when the screen is purposefully locked.

This is ambient information about people's presence at work and the likelihood of their presence at their desks. Managers especially like this information so that they can drop in and chat with workers unannounced. This logon information could be presented in many interesting ways, such as a media player visualization where pictures of each person logged in could fly around, with those who have locked their screens being represented by small pictures. Knowing user logon status could also help reclaim lost computing cycles. Most desktop computers utilize very little of their possible computing power. Open the Task Manager and watch the CPU usage: most of the time, it will barely peak above 4% usage. This computing power could be used to solve many interesting problems.

For example, our media players could run in-depth statistics on the songs we listen to, such as finding patterns within the actual audio of our favorite songs, so that it can suggest other songs that have similar audio qualities. The computer would need to spend a lot of time analyzing each song, producing a local database of songs, and indexing between songs. Desktop search engines need to view and index all of the files you have on your computer. An interesting help would be to index all Web pages visited, particularly sites that get a lot of interactive time from the user, and are relevant to keywords in personal documents and emails.

Then, when performing an online search, results from relevant, frequently visited sites could be favored over the typical search results. In fact, the application may be able to suggest search terms that would help search for more accurate and relevant results when looking for new information. This kind of intensive processing could be done on a low-priority thread: eating up processor time when it is not needed by other applications. However when a lot of disk access is involved, it can degrade the performance of other higher-priority applications.

SENS Events
Let's start a new console application to illustrate catching SENS events through COM. Create a new Visual Studio solution called DotNetSENS with a C# Console Application in that solution. Then add a COM reference to "SENS Events Type Library." Visual Studio.Net will create a small DLL that interfaces between the .NET Framework and the SENS Events COM DLL. By using the code in Listing 1 we should be able to catch the screen lock and unlock events (if you cannot lock your screen, you can catch the screen saver start and stop events).

The first time we try and run this new application it crashes. Unfortunately, we cannot actually create an instance of the SENSClass class: it is a COM process that is running, and it provides its events through the COM+ Event System.

After a lot of searching on the topic, I stumbled on an article that helps to explain how to connect to the COM+ Event Sys-tem using managed code (www.codeproject.com/csharp/subscriptionviewer.asp). That article helped me learn how to register for SENS events. Most of the rest of this article describes how to register for SENS events directly; once you understand the code, it becomes a pattern for how to register for any events that are provided through the COM+ Event System.

Registering With the COM+ Event System
Ideally, we want a DLL that we can import into any project that we are working on and start getting SENS events. To do this, add a new "C# Class Library" project to the DotNetSENS project, and name it ManagedSENS. Remove Class1 and add a new class called EventSystemRegistrar. This class will contain all of the code necessary to connect to the Event System. Now add a reference to "EventSystem 1.0 Type Library" so that we can have the classes necessary to register with and use the COM + Event System.

Just importing the EventSystem classes is not enough to gain access to the Event System; we need a few classes we can instantiate and call methods on. These classes are provided in Listing 2. Even though they do not have any methods on them, through the magic of COM Interop these classes will expose the methods of the classes specified by the GUID attribute. (COM is dependent on Globally Unique Identifiers (GUID) to uniquely identify all of the classes, interfaces, methods, etc. that can be specified in COM. GUIDs in COM serve the purpose of the GAC in the .NET Framework: they make sure that two classes with the same name do not conflict with each other, and a library that wants to call methods on certain classes does not get the wrong classes.)

The COM+ Event System is a set of COM libraries that make it seamless for any class to register for events raised in any running process. Thus the system can send events to the places we want it to, and it requires several pieces of information to register for them:

To conform to the needs of the Event System, we will create a SubscribeToEvents routine, which is shown in Listing 3. Each of the parameters matches what the Event System requires. Also, if you look inside this method, you can see the magic of the COM Interop: the EventSubscription object has no methods, yet it can be cast to an IEventSubscription interface, which has methods that can be called.

Registering for SENS through the Event System
Now that we have a class to register with the Event System, we need a class to receive the events. The problem is that SENS will only call certain kinds of methods. To support this, we need to add the "SENS Events Type Library" to the class library project, as we did with the console application. The reason this library must be added is so that we have all the appropriate GUIDs, interfaces, and methods available for the Event System to call.

To register for the SensLogon events, we need to implement the ISensLogon interface, with its required methods. In .NET we usually use delegates and events for notification. To translate between the COM method calls and the .NET events we would rather have, we need a structure to mask the old COM code.

When another programmer imports this library, we only want my .NET-friendly classes and events to be exposed. The main class we will expose is called SensLogon, and will have static events for logon, screen lock, etc. To directly receive the messages and then raise the events, this class would need to implement the ISensLogon interface, but that would expose the methods of the interface to anyone, allowing other code to fake the events. To prevent this, we will create a private inner class called SensLogonInterop to implement and hide the interface, register for the events, and forward the events to the containing class. Some of the code for this implementation is in Listing 4 (though much of the code is removed for brevity, it is very similar to what is shown).

The SensLogonInterop class has a few constant and static values at the top for registering with the COM+ Event System. These members are used in the class constructor to register it with the Event System. Since it is a private inner class, it can only be instantiated by the SensLogon class. The SensLogon class keeps a static reference to the only instance, thus preventing it from being garbage collected.

Once we have a class that will get the events raised by the SENS Events system, we need to add events to the SensLogon class so that it can raise them. This is the trickiest part. COM interoperability is slow, so if no classes within an application are registered to events, we do not want COM to send them. For efficiency, we need a system that registers with COM when a class first registers with SensLogon and removes the COM registration when no classes are left.

The typical way of declaring an event in a C# class is inadequate because we cannot perform any special processing when a delegate is attached to an event. Thankfully, C# provides a way of creating custom event registration code: write an event as though you are writing a property, but use the add and remove keywords instead of get and set. The add keyword is for when a delegate is attached to the event, and the remove keyword is for when a delegate is detached from the event. This is illustrated at the bottom of Listing 5. It takes more work to attach and detach events, but because of the RegisterEvent and UnregisterEvent routines, it is a lot simpler than it could have been.

Also, to actually raise the events, we have protected static OnEventName methods, which are used to raise the events defined (having a protected method that raises an event is standard procedure for all events in the .NET Framework). The appropriate method is called by the SensLogonInterop class when an event comes in, thereby causing the associated event to be raised, if anyone is registered for that event.

Bringing It All Together
Now we get back to the initial problem: getting an event whenever someone locks or unlocks their screen. In the DotNetSENS Console project, add a reference to the ManagedSENS project. Then, the code should change to the code in Listing 6. This is very similar to the code in Listing 1, but it uses our new .NET class for catching the SENS event. From start to finish, these are the steps to catch the screen lock and screen unlock events.

  1. The application starts up, and the DisplayLock event has a delegate attached to it
  2. This attachment causes the SensLogon class to create an instance of the SensLogonInterop class
  3. The SensLogonInterop class registers for the ISensLogon events with the COM+ Event System
  4. The application then attaches to the DisplayUnlock event, which does nothing other than remember the delegate to call
  5. The application then waits for the user to hit the enter key before quitting
  6. Meanwhile, when the screen is locked, SENS sends the event out to the COM+ Event System
  7. The COM+ Event system passes the event to the instance of SensLogonInterop by calling DisplayLock
  8. When DisplayLock is called, it calls SensLogon.OnDisplayLock
  9. OnDisplayLock then raises the DisplayLock event
  10. The application catches the event, and writes out to the console my login name
  11. When the screen is unlocked, steps similar to steps 6 through 10 are run
Although it takes a lot of work to register for COM+ Events, there is a lot of information that can be gained from them. Once set up in an appropriate way, all of this code can be masked from future users so that attaching to and catching these events is seamless to a .NET application.

SENS Events in .NET 2.0
In .NET 2.0, Microsoft provides some events that look similar to SENS events (http://winfx.msdn.microsoft.com/library/ en-us/cpref/winfx/ref/microsoft.win32.asp?frame=true). These events are actually provided through messages sent to a Window's main message handler, the WndProc method. Through P/Invoke, you can register for these messages, and receive them in .NET 1.1 or you can attach to the events provided in .NET 2.0 (check out the documentation in MSDN about WTSRegisterSessionNotification for information on these messages). Unfortunately, no messages are sent when the screen saver starts and stops. Also, these messages do not provide any information about the account that caused them.

© 2008 SYS-CON Media