Click here to Skip to main content
15,891,607 members
Articles / Programming Languages / C#
Article

TorTray

,
Rate me:
Please Sign up or sign in to vote.
4.62/5 (6 votes)
5 Oct 20056 min read 36.8K   869   32   2
An article describing tray icon controls, inter-thread event handling, and processes using .NET 2.0 RC1.

Sample Image

Introduction

This program was developed to complement Tor on the Windows platform. Along the way, a few issues presented themselves. The solutions to those issues prompted writing this article for CodeProject.

Background

For those of you not familiar with Tor, visit its homepage. The concept behind Tor is "onion skin routing". The idea is that to protect anonymity, network traffic is encrypted then routed through a series of hosts. Each host only knows about the host it is receiving packets from and the host it is forwarding those packets on to. No records are kept and the path varies from connection to connection. The result is a network within the internet that offers almost perfectly anonymous access to any resource, and one that can traverse virtually all firewalls, routers, and web proxies, such as WebSense.

After using Tor for a while, we found the console window a bit annoying. There is no way to hide it. One of the installation options is to run Tor as a system service. Although this works, Tor can get itself into an odd state if the connection drops. The only way around this is to restart the service. Our solution was to write a program that sits in the system tray and launches Tor in a subprocess. The console window is completely hidden, yet all output from this subprocess is captured and displayed in the application's main form. The tray icon's context menu offers options for starting, stopping, and restarting Tor.

Please note that this is not intended as a finished product. We wanted to distribute the application and its source code in its present state to help others write applications that use these techniques.

Points of Interest

1. Initially Hidden Main Form

When the application starts, you will notice that the main form is never displayed. .NET 2.0 handles the application class a little differently than .NET 1.1. The default application class looks like this:

C#
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace TorTray
{
  static class Program
  {
    /// <SUMMARY>
    /// The main entry point for the application.
    /// </SUMMARY>
    [STAThread]
    static void Main( )
    {
      Application.EnableVisualStyles( );
      Application.SetCompatibleTextRenderingDefault( false );
      Application.Run( new TorTray( ) );
    }
  }
}

The problem is that there is no way to prevent the window from being displayed. At best there will be a flash as it appears and disappears. But it is always there. This can be solved with one small change to the class:

C#
using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace TorTray
{
  static class Program
  {
    /// <summary>
    /// The main entry point for the application.
    /// </SUMMARY>
    [STAThread]
    static void Main( )
    {
      Application.EnableVisualStyles( );
      Application.SetCompatibleTextRenderingDefault( false );
      TorTray tt = new TorTray( );
      Application.Run( );
    }
  }
}

The Application.Run function calls the form's Show function as part of its normal execution. Short of re-writing the Application class, there is no way to prevent it from doing so. However, that is the only function in the form ever called by Application.Run. Since the main form's class has to be created, removing it to its own line accomplishes exactly what we needed: a form that is initially invisible. It is still fully instantiated and can be used without modification. It is just never made visible.

2. Creating an invisible subprocess

This is actually quite straightforward: create an instance of the Process class and set the Process.StartInfo.CreateNoWindow member to true. However, we wanted to capture output from the console application to display it in the main form. The lines of interest from TorControl are:

C#
m_processTor.StartInfo.RedirectStandardOutput = true;
m_processTor.StartInfo.RedirectStandardError = true;
m_processTor.StartInfo.UseShellExecute = false;
m_processTor.OutputDataReceived += new 
  DataReceivedEventHandler( m_processTor_OutputDataReceived );
m_processTor.ErrorDataReceived += new 
  DataReceivedEventHandler( m_processTor_ErrorDataReceived );

According to the MSDN, output cannot be redirected unless UseShellExecute is false. The Process class has the facility to send each line of output, as it is received, to registered event listeners. We took advantage of this so that we didn't have to poll for output.

That brings us to the next problem: receiving those events!

No matter what we did, the event handlers just did not want to fire. There was nothing obvious in the MSDN describing this issue but after much reading and experimenting, we hit on the following solution:

Just after the process is started, the Process.BeginErrorReadLine and Process.BeginOutputReadLine functions are called. Apparently these start the asynchronous pump so that the events fire. The lines are:

C#
m_started = m_processTor.Start( );
m_processTor.BeginErrorReadLine( );
m_processTor.BeginOutputReadLine( );

Of course this means that prior to killing the process, the asynchronous pumps have to be shut down, otherwise the application will hang waiting for the output to end:

C#
m_processTor.CancelErrorRead( );
m_processTor.CancelOutputRead( );
m_processTor.Kill( );
m_processTor.WaitForExit( );

3. Displaying the text in the main form

Now that we had the text being sent to our event handlers, we needed to send it along to the main form. We could have made the ListView control publicly accessible and just added each line to it directly, but it is just plain ugly. That couples the classes too closely. The solution is to create an event handler of our own!

.NET 2.0 introduces the idea of a delegate. For all intents and purposes, this is a function pointer prototype. The delegate has the exact signature of the function that will ultimately be used to handle the events. The code to create an event handler is:

C#
class TorControl
{
  ...
  public event TorOutputReceivedEventHandler TorOutputReceived;
  ...
}

TorOutputReceivedEventHandler is the delegate. It takes the following form:

C#
public delegate void TorOutputReceivedEventHandler( object sender, 
                    TorOutputReceivedEventArgs e );

You will notice that this is actually defined outside of the class to be consistent with the way the .NET Framework defines delegates. You can just as easily define it within your class, but the client event handler would have to reference it as TorControl.TorOutputReceivedEventHandler.

The reason for creating a custom delegate is that we can create a subclass of EventArgs that allows data to be sent from the event trigger to the event handler. In this case, the class is TorOutputReceivedEventArgs:

C#
public class TorOutputReceivedEventArgs : EventArgs
{
  public TorOutputReceivedEventArgs( ) { }

  public TorOutputReceivedEventArgs( string output )
  {
    m_output = output;
  }

  public string Output
  {
    get
    {
      return ( m_output );
    }
  }

  private string m_output = string.Empty;
}

Now the main form can subscribe to events in order to display them:

C#
public partial class TorTray : Form
{
  ...
  public TorTray( )
  {
    InitializeComponent( );
    listBox.Items.Clear( );
    m_torControl = new TorControl( );
    m_torControl.TorOutputReceived += new 
      TorOutputReceivedEventHandler( m_torControl_TorOutputReceived );
  }
  
  void m_torControl_TorOutputReceived( object sender, TorOutputReceivedEventArgs e )
  {
    listBox.Items.Add( e.Output );
  }
}

"Great!", we thought, "We're done!". As we ran it in the debugger, an exception was thrown stating that a worker thread could not modify GUI elements in the main form thread. What? We've written code like that before. Plus, the event handler is in the main form class, so why would there be a problem?

Well, just because a function is in a particular class doesn't mean that it cannot be called by another thread. That is the situation here. The event handler is actually called in the context of the Process thread, not the GUI thread. According to the MSDN, GUI elements have never been thread safe, but there has never been a way of preventing it. Now in .NET 2.0 it is quite easy. This is a job for another delegate, this time being used in its pure form, as a function pointer:

C#
delegate void SetItemCallback( string text );

This creates the prototype for a callback function that will accept a string of text. Now, we need a callback with the same signature that can differentiate between the threads:

C#
private void SetItemText( string text )
{
  if( listBox.InvokeRequired == true )
  {
    SetItemCallback callback = new SetItemCallback( SetItemText );
    Invoke( callback, new object[] { text } );
  }

  else
  {
    listBox.Items.Add( text );
  }
}

The idea here is that the execution of the worker thread can be detected through checking the InvokeRequired property on the control that needs to be updated. The delegate is used to create a pointer to the real function, which is then invoked within the thread that the overall class belongs to. Essentially this allowed us to copy data from one thread's local storage to the other, and then manipulate the GUI. For this to work, the event handler is modified to use the callback:

C#
void m_torControl_TorOutputReceived( object sender, 
                  TorOutputReceivedEventArgs e )
{
  SetItemText( e.Output );
}

Conclusion

That's it for our .NET 2.0 tips! We hope that you find them useful in your applications. We also hope that you find the TorTray application useful as well. We intend to expand its functionality in the future, so stay tuned!

History

  • 2005-Sep-29 - Initial release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United States United States
Mr. Howes has been programming since he was first introduced to the Apple II+ at the tender age of ten. After briefly dabbling with circuit design as a combined CS/EE major, he came to his senses and completed a pure CS degree at RIT

All these years later, the programming bug still bites him each day and causes him to go to work, where he writes software for a living. Now he is even learning Cocoa for the Mac!

In his copious amounts of "spare" time, Mr. Howes is learning the art of cabinet-making, flies radio-controlled helicopters and airplanes, and mountain cycles. Next summer he would like to learn how to kayak.

Linked In Page: http://www.linkedin.com/pub/3/54a/578

Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralCancelErrorRead and CancelOutputRead Pin
Jorge Branco10-Sep-08 21:27
Jorge Branco10-Sep-08 21:27 
GeneralDelegates Pin
Gary Thom6-Oct-05 2:42
Gary Thom6-Oct-05 2:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.