<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
   <title>new TechBlog();</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/" />
   <link rel="self" type="application/atom+xml" href="http://www.randomtree.org/eric/techblog/atom.xml" />
   <id>tag:www.randomtree.org,2007:/eric/techblog//4</id>
   <updated>2006-07-20T06:31:15Z</updated>
   <subtitle>A web log devoted to .Net, general software engineering, and technology, especially technology specific to computers and programming.</subtitle>
   <generator uri="http://www.sixapart.com/movabletype/">Movable Type 3.33</generator>

<entry>
   <title>Teaching Kids to Program, Redux</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2006/07/teaching_kids_to_program_redux.html" />
   <id>tag:www.randomtree.org,2006:/eric/techblog//4.892</id>
   
   <published>2006-07-20T05:52:40Z</published>
   <updated>2006-07-20T06:31:15Z</updated>
   
   <summary>Last October I mentioned a board game called c-jump, with the following commentary: I think this concept of “teaching kids to program” meaning teaching them C-like syntax is symptomatic of a deeper problem in the industry; the idea that knowing...</summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<p>Last October I <a href="http://www.randomtree.org/eric/techblog/archives/2005/10/Teaching%20kids%20to%20program.html">mentioned a board game called c-jump</a>, with the following commentary:</p>
<blockquote><p>I think this concept of “teaching kids to program” meaning teaching them C-like syntax is symptomatic of a deeper problem in the industry; the idea that knowing how to program means only knowing the syntax for a language, being able to put together a file about which the compiler doesn’t complain.</p></blockquote>
<p>More recently (okay, January) I ran across a very different concept for teaching kids to program, a development environment from Carnegie Mellon called <a href="http://coolthingoftheday.blogspot.com/2006/01/from-carnegie-mellon.html">Alice</a> that answers my objections neatly. In Alice, there's no emphasis on the syntax itself; the environment prevents you from needing to know the syntax by enforcing correctness rules (at any given time you can only make changes that result in a legal program). The point is that then you can concentrate on <em>what you want the program to do</em>, rather than <em>how you get the program to do what you want</em>.</p>
<p>I think this approach would be much more successful at teaching kids programming; what's really impressive is that it includes some concepts that are rarely if ever actually taught in classes (such as concurrency and event-based programming) but that can be very important in the real world.</p>
<p>While I'm on the subject, I read another very interesting article recently. <a href="http://www.codinghorror.com/blog/archives/000635.html">Coding Horror</a> linked to an academic paper about predicting which students can become successful programmers, and which can't. Apparently between 30 and 60% of incoming C/S students fail their first programming course, not because they're not smart or hardworking (although there are those, too ;)), but because they either cannot form a consistent enough mental model to understand the system, or they reject the whole exercise as nonsense. It was actually kind of a shock to me to learn that some students not only aren't intuitively able to form a consistent model of assignment (one of the most basic requirements for understanding programming)--not even an incorrect but consistent one--but that they cannot do so even after a formal programming class. Some people, it appears, really can't learn to program. I guess what that says about me is that I have a high tolerance for nonsense. ;) The test (and answer key) is available at <a href="http://www.cs.mdx.ac.uk/research/PhDArea/saeed/">the paper's site</a>, if you want to test yourself.</p>]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term="Programming" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>
<entry>
   <title>FolderSyndication 1.0.3</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2006/01/foldersyndication_103.html" />
   <id>tag:www.randomtree.org,2006:/eric/techblog//4.867</id>
   
   <published>2006-01-19T03:09:30Z</published>
   <updated>2006-06-22T18:52:42Z</updated>
   
   <summary>Due to a minor logic error which prevents FolderSyndication from publishing the XML in some cases, I&amp;#8217;ve created a maintenance release. The package will automatically upgrade any previous versions, however, you should manually back up the .config file and any...</summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<p>Due to a minor logic error which prevents FolderSyndication from publishing the XML in some cases, I've created a maintenance release. The package will automatically upgrade any previous versions, however, you should manually back up the .config file and any custom XSLT files you have added, as the installation process will overwrite or delete them.</p>
<p><a href="/tracker/download.php?Link=FolderSyndication">Download Link</a> (Note: This package will not install on Windows 2000 or previous due to its use of the LocalService account. To install on Windows 2000 or previous, use <a href="/tracker/download.php?Link=FolderSyndication_w2k">this package</a> instead.)</p>
<p>If you have any questions, comments, or bug reports, please don't hesitate to <a href="mailto:code@randomtree.org?Subject=FolderSyndication">contact me</a>.</p>]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term=".Net" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="FolderSyndication" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>
<entry>
   <title>FolderSyndication 1.0</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2006/01/foldersyndication_10.html" />
   <id>tag:www.randomtree.org,2006:/eric/techblog//4.858</id>
   
   <published>2006-01-05T00:07:37Z</published>
   <updated>2006-06-22T18:52:25Z</updated>
   
   <summary>Announcing FolderSyndication 1.0. FolderSyndication is a tool that will watch folders and files for changes (new files, modified/renamed files, deletions, etc) and will publish those changes, for instance to an Atom feed (the default, although an RSS 2.0 feed is...</summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<p>Announcing FolderSyndication 1.0. FolderSyndication is a tool that will watch folders and files for changes (new files, modified/renamed files, deletions, etc) and will publish those changes, for instance to an Atom feed (the default, although an RSS 2.0 feed is another provided option). If you don't like Atom or RSS 2.0, you can provide your own XSL transform to output whatever you want--Word document, raw XML, database update, whatever.</p>
<p>Features:</p>
<ul>
<li>Publish format is completely customizable through the use of standard XSLT files.</li>
<li>Folders can be watched recursively, and notifications can be filtered by wildcard patterns.</li>
<li>Publishing is performed at configurable intervals in order to not cause performance degradations during large file modifications (such as copying or deleting large numbers of files).</li>
<li>Windows native FileSystemWatchers are used, meaning even a very large file tree can be watched efficiently. (Not yet tested against very high volumes of file changes.)</li>
</ul>
<p>As with the other tools available on my website, FolderSyndication is licensed under a Creative Commons <a href="http://creativecommons.org/licenses/by/2.0/">By Attribution</a> license, meaning you are free to use and redistribute it as long as you give me credit as the original program author.</p>
<p><a href="/tracker/download.php?Link=FolderSyndication">Download Link</a> (Note: This package will not install on Windows 2000 or previous due to its use of the LocalService account. To install on Windows 2000 or previous, use <a href="/tracker/download.php?Link=FolderSyndication_w2k">this package</a> instead.) Either package is about 400k in size.</p>
<p>If you have any questions, comments, or bug reports, please don't hesitate to <a href="mailto:code@randomtree.org?Subject=FolderSyndication">contact me</a>.</p>]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term=".Net" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="FolderSyndication" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>
<entry>
   <title>Teaching kids to program</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2005/10/Teaching kids to program.html" />
   <id>tag:www.randomtree.org,2005:/eric/techblog//4.846</id>
   
   <published>2005-10-05T04:16:39Z</published>
   <updated>2005-10-05T04:18:31Z</updated>
   
   <summary>The other day I read in Wired (which probably means it&amp;#8217;s old news ;) about a &amp;#8220;programming board game&amp;#8221; invented by Igor Kholodov to teach kids the &amp;#8220;basics of programming&amp;#8221;. It&amp;#8217;s called c-jump, and as Wired says, The board game...</summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<p>The other day I read in Wired (which probably means it's old news ;) about a "programming board game" invented by Igor Kholodov to teach kids the "basics of programming".  It's called <a href="http://www.wired.com/news/technology/0,1282,68872,00.html?tw=wn_tophead_2">c-jump</a>, and as Wired says,
<blockquote>
The board game turns players into skiers who must race down a mountain in the quickest way possible. With each roll of the die, players must follow instructions that are similar to computer program codes. Using basic math, players have to figure out which paths are open to them and then decide the fastest way to the finish line. The trick, however, is learning which paths are open to you using only programmer jargon like "if (X==1)" then you can take the green path or "while (X&lt;4) you can take the orange path," where X is the roll of the die.
</blockquote>
No offense to Mr. Kholodov, but I always have the same reaction whenever people talk about "teaching kids to program".  That reaction is, more or less, confusion.  I don't know that "teaching kids to program" is a particularly valuable thing to do, at least as most people seem to envision it.  Teaching them the basics of programming, to me, involves teaching them to think logically and algorithmically, teaching them to construct mental models and extrapolate consequences, and to balance competing objectives.  I don't see much use in teaching them what "x==1" means, or teaching them how to follow an if branch.  The important thing is not to learn the syntax, but to learn the <em>concepts</em>.  Not to learn <em>how</em> to follow an if branch (any idiot computer can do that), but <em>when and why</em> you might want to choose between two courses of action.</p>
<p>In fact, I think this concept of "teaching kids to program" meaning teaching them C-like syntax is symptomatic of a deeper problem in the industry; the idea that knowing how to program means only knowing the syntax for a language, being able to put together a file about which the compiler doesn't complain.  Too many programs are constructed by trial-and-error, changing things semi-randomly until they work rather than understanding the system and considering the best method to use to solve the problem at hand.</p>
<p>Rote programming is not an advantageous skill; if you understand the concepts, you can pick up any language quite rapidly.  Just as importantly, rote programming is something that can be effectively outsourced; there's no point in teaching your child a skill that will put them in the position of needing to be the lowest bidder to get a job.  The advantageous skills are ones not unique to programming, which makes teaching them even more useful; the kid may choose to never write a single real program, after all, while mental modeling is a widely helpful skill.  These skills are also the ones that tend to result in higher-paying or at least more satisfying jobs, something I think all of us want for our children. I applaud Mr. Kholodov's interest and his creativity, I just don't think this particular effort is as successful as it could be.</p>]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term="Programming" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>
<entry>
   <title>Defensive Event Publishing for Remoting</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2005/04/defensive_event_publishing_for_remoting.html" />
   <id>tag:www.randomtree.org,2005:/eric/techblog//4.835</id>
   
   <published>2005-04-08T02:56:28Z</published>
   <updated>2006-06-22T18:52:15Z</updated>
   
   <summary>Some possible additions to Roy Osherove&apos;s comments on Defensive Event Publishing in .Net, with special attention to some performance scenarios introduced by Remoting.</summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<p>Several months ago, Roy Osherove posted a discussion of <a href="http://weblogs.asp.net/rosherove/articles/DefensiveEventPublishing.aspx">Defensive Event Publishing in .Net</a> that discussed various problems with the "normal" methods of event publishing and raising in .Net.  The naive programmer merely calls MyEvent(sender, eventArgs), never suspecting the minefield into which he or she is blithely strolling.  Roy's post suggests several progressively more cautious methods of raising events to protect oneself against "bad" clients. At the time I commented that further improvements could be made, specifically to both avoid using Threadpool threads and to detect which callers are bad. I thought I'd finally get around to explaining what I meant and actually providing a solution I've used in the past.</p>
<p>First off, not using Threadpool threads. I'm really not a fan of using the Threadpool for any operation that I don't have absolute control over, because there's a limited number of them. The default number can be increased, but you can't make it infinite (and if you could, it would defeat the purpose of thread pooling anyway). IMO threadpool threads are useful for short, relatively deterministic operations which won't ever call any client code and which either will never fail, or will fail in such a way that you don't care or can't do anything about anyway. Raising events just doesn't fit those qualifications for me. So the solution is to not use threadpool threads; this is a fairly simple thing to do if you're at all familiar with .Net threading. Depending on your implementation, however, and definitely if you use the code I've posted at the end of this article, then there are a few caveats to watch for; I'll note them along the way.</p>
<p>The second way in which we can add to Roy's article is in detecting failed calls. His solution calls a OneWay async Invoke on the delegate; it's a fire-and-forget situation. Unfortunately, especially for an application that needs to stay up 24/7 for long periods of time, it may not be acceptable to just ignore failed calls; the app may want to clean up, or at least rid itself of the bad reference and let the GC pick it up. In order to do that, I use WaitHandles; each thread that I spawn for an individual delegate call will set a WaitHandle when it finishes. (Note that .Net events raised over Remoting automatically time out after a period of time. Using this method with non-remoted events would require additional code to detect timeouts, but would not require any additional code to detect clients that just don't exist anymore.) Here's one of our caveats: <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemthreadingwaithandleclasswaitalltopic1.asp">WaitHandle.WaitAll</a> can only handle a certain number of handles; on the current .Net implementation (namely .Net 1.0 and 1.1 on Win32) that limit is 64 handles. Calling WaitHandle.WaitAll on > 64 handles will throw an exception. So, should you have more than 64 clients listening to the event, the code will automatically break them up into batches of 64 and wait on each batch sequentially. Another wrinkle is that WaitHandle.WaitAll isn't usable from <acronym title="Single-Threaded-Apartment">STA</acronym> threads--such as those used by Windows Forms--if you're waiting on more than one handle. This can be particularly tricky, as this means you probably can't raise an event using this code on your main Windows Forms UI thread. The code below doesn't handle this case (because our app wasn't a WinForm app and had no STA threads); if your code will be called from STA threads you will need to handle that situation (possibly by raising all events on a new thread).</p>
<p>The final caveat is that <strong>only the class that declares an event can modify that event</strong> (other than a simple += or -= to add/remove a listener). Thus you can't modify the delegate list to remove a specific listener except from the original class. In order to get around this, my utility function returns a new delegate list that has all of the "bad" clients removed. If your code needs better information about exactly which delegates were removed, you could add either an out param for the "bad" list, or a delegate called for bad clients, etc.</p>
<p>Using the code is fairly simple.  The general case looks like this:</p>
<style type="text/css">
.cf { font-family: Lucida Console; font-size: 8pt; color: black; background: white; padding-top: 0pt; padding-left: 0pt; padding-right: 0pt; padding-bottom: 0pt; }
.cln { color: teal; }
.cb1 { color: blue; }
</style>
<pre class="cf">
<span class="cln">    1</span> <span class="cb1">using</span> System;
<span class="cln">    2</span>  
<span class="cln">    3</span> <span class="cb1">namespace</span> EventTest
<span class="cln">    4</span> {
<span class="cln">    5</span>   <span class="cb1">public</span> <span class="cb1">delegate</span> <span class="cb1">void</span> MyEventHandler(<span class="cb1">object</span> sender, EventArgs e);
<span class="cln">    6</span>  
<span class="cln">    7</span>   <span class="cb1">public</span> <span class="cb1">class</span> EventRaiser
<span class="cln">    8</span>   {
<span class="cln">    9</span>     <span class="cb1">public</span> <span class="cb1">event</span> MyEventHandler MyEvent;
<span class="cln">   10</span>  
<span class="cln">   11</span>     <span class="cb1">public</span> <span class="cb1">void</span> RaiseEvent()
<span class="cln">   12</span>     {
<span class="cln">   13</span>       MyEvent = (MyEventHandler)EventRemoter.RaiseRemotedEvent(MyEvent, <span class="cb1">this</span>, EventArgs.Empty);
<span class="cln">   14</span>     }
<span class="cln">   15</span>   }
<span class="cln">   16</span> }
</pre>
<p>Relatively simple, aside from the need to cast the return value and the WaitHandle issues mentioned above.</p>
<p>The code for EventRemoter is available <a href="/tracker/download.php?Link=EventRemoter">here</a>. If you find it useful, or find a problem or just have a comment, please, <a href="mailto:code@randomtree.org?Subject=EventRemoter">let me know</a>!</p>
<p>This code is covered by the same license as other items available from this blog, namely the Creative Commons' <a href="http://creativecommons.org/licenses/by/2.0/">"By Attribution 2.0" license</a>.</p>
<p><strong>Addendum:</strong> After a brief conversation with someone who had recently asked me about this code, I added a static parameter to control the number of simultaneous threads that will be used by any one event raise, rather than using a magic number sprinkled through the code. The parameter defaults to 64 in order to be correct on Win32, but can be changed in either of two situations. If you want the code to use fewer threads (as the default version will spawn a lot of (very short-lived) threads when raising events to a lot of subscribers), then set the parameter lower. If you are using the code on a platform where WaitAll works with more than 64 handles, then you can set the parameter higher. The new version is at the same location linked above; enjoy!</p>]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term=".Net" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="Programming" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>
<entry>
   <title>CopySourceAsHtml</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2004/10/copysourceashtml.html" />
   <id>tag:www.randomtree.org,2004:/eric/techblog//4.807</id>
   
   <published>2004-10-18T14:43:34Z</published>
   <updated>2005-09-29T17:14:02Z</updated>
   
   <summary><![CDATA[Colin Coller has created a very nice plugin for VS.Net called CopySourceAsHtml that lets you create colorized text by copying source from VS.Net. It produces pure HTML code (not the stuff spat out by Word) using embedded stylesheets: &lt;style type="text/css">...]]></summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<p>Colin Coller has created a very nice plugin for VS.Net called <a href="http://www.jtleigh.com/people/colin/blog/archives/2004/10/copysourceashtm_1.html">CopySourceAsHtml</a> that lets you create colorized text by copying source from VS.Net.  It produces pure HTML code (not the stuff spat out by Word) using embedded stylesheets:</p>
<code><pre>
&lt;style type="text/css">
.csharpcode
{
	font-size: 10pt;
	color: black;
	font-family: Courier New , Courier, Monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0px; }
.rem { color: #008000; }
.kwrd { color: #0000ff; }
.str { color: #006080; }
.op { color: #0000c0; }
.preproc { color: #cc6633; }
.asp { background-color: #ffff00; }
.html { color: #800000; }
.attr { color: #ff0000; }
.alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0px;
}
.lnum { color: #606060; }
&lt;/style>
&lt;div class="csharpcode">
&lt;pre>&lt;span class="lnum">   167: &lt;/span>    &lt;span class="rem">/// &lt;summary&gt;&lt;/span>&lt;/pre>
&lt;pre>&lt;span class="lnum">   168: &lt;/span>    &lt;span class="rem">/// Creates a new socket server object and optionally starts it listening.&lt;/span>&lt;/pre>
&lt;pre>&lt;span class="lnum">   169: &lt;/span>    &lt;span class="rem">/// &lt;/summary&gt;&lt;/span>&lt;/pre>
&lt;pre>&lt;span class="lnum">   170: &lt;/span>    &lt;span class="rem">/// &lt;param name="name"&gt;The friendly name for this socket server.&lt;/param&gt;&lt;/span>&lt;/pre>
&lt;pre>&lt;span class="lnum">   171: &lt;/span>    &lt;span class="rem">/// &lt;param name="port"&gt;The port to listen on.&lt;/param&gt;&lt;/span>&lt;/pre>
&lt;pre>&lt;span class="lnum">   172: &lt;/span>    &lt;span class="rem">/// &lt;param name="startListening"&gt;Whether to immediately start listening, or wait for a &lt;see cref="StartListening"/&gt; call.&lt;/param&gt;&lt;/span>&lt;/pre>
&lt;pre>&lt;span class="lnum">   173: &lt;/span>    &lt;span class="kwrd">public&lt;/span> SocketServer(&lt;span class="kwrd">string&lt;/span> name, &lt;span class="kwrd">int&lt;/span> port, &lt;span class="kwrd">bool&lt;/span> startListening)&lt;/pre>
&lt;pre>&lt;span class="lnum">   174: &lt;/span>    {&lt;/pre>
&lt;/div></pre></code>
<p>Which turns out looking like this:</p>
<style type="text/css">
.csharpcode
{
	font-size: 10pt;
	color: black;
	font-family: Courier New , Courier, Monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0px; }
.rem { color: #008000; }
.kwrd { color: #0000ff; }
.str { color: #006080; }
.op { color: #0000c0; }
.preproc { color: #cc6633; }
.asp { background-color: #ffff00; }
.html { color: #800000; }
.attr { color: #ff0000; }
.alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0px;
}
.lnum { color: #606060; }
</style>
<div class="csharpcode">
<pre><span class="lnum">   167: </span>    <span class="rem">/// &lt;summary&gt;</span></pre>
<pre><span class="lnum">   168: </span>    <span class="rem">/// Creates a new socket server object and optionally starts it listening.</span></pre>
<pre><span class="lnum">   169: </span>    <span class="rem">/// &lt;/summary&gt;</span></pre>
<pre><span class="lnum">   170: </span>    <span class="rem">/// &lt;param name="name"&gt;The friendly name for this socket server.&lt;/param&gt;</span></pre>
<pre><span class="lnum">   171: </span>    <span class="rem">/// &lt;param name="port"&gt;The port to listen on.&lt;/param&gt;</span></pre>
<pre><span class="lnum">   172: </span>    <span class="rem">/// &lt;param name="startListening"&gt;Whether to immediately start listening, or wait for a &lt;see cref="StartListening"/&gt; call.&lt;/param&gt;</span></pre>
<pre><span class="lnum">   173: </span>    <span class="kwrd">public</span> SocketServer(<span class="kwrd">string</span> name, <span class="kwrd">int</span> port, <span class="kwrd">bool</span> startListening)</pre>
<pre><span class="lnum">   174: </span>    {</pre>
</div>
<p>It's highly configurable and very cool, so if you intend to post code on the web, check it out!</p>]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term=".Net" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="Programming" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="Tools" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>
<entry>
   <title>Multithreading is hard.</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2004/10/multithreading_is_hard.html" />
   <id>tag:www.randomtree.org,2004:/eric/techblog//4.805</id>
   
   <published>2004-10-13T20:13:57Z</published>
   <updated>2005-09-29T17:14:02Z</updated>
   
   <summary>Discovery of a race condition in the TimedLock class (from Ian Griffiths and Phil Haack), and other ruminations on multithreading.</summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<p>Lately at work I've been dealing with a problematic socket server.  The currently deployed version has something of a memory leak (to the tune of 140+MB/day), probably due to complications of incorrectly multithreading System.Net.Socket instances (note: they're not thread-safe).</p>
<p>Unfortunately, when I redid the socket server to lock all the sockets and other non-thread-safe resources, I ran into a deadlock.  In chasing it down, I used <a href="http://haacked.com/archive/2004/05/12/431.aspx">Phil Haack's modification</a> of <a href="http://www.interact-sw.co.uk/iangblog/2004/04/26/yetmoretimedlocking">Ian Griffith's</a> TimedLock class.  That enabled me to find where the deadlocks were, and eliminate them.  This class is really a very clever tool, with one small problem: it was throwing exceptions on the production server.  The test server ran fine for days at a time, loaded down as heavily as I could manage, but the production server locked inside of two hours every time.  The first error in the log was always an ArgumentException thrown by the stack trace hashtable, saying that the object being inserted as the key was already in the hashtable.</p>
<p>After several days of debugging, and a few e-mails exchanged with Phil, he said the following to me:</p>
<blockquote><p>If the object wasn't removed from the hashtable via the dispose method before the second lock is acquired, that could cause the error.</p></blockquote>
<p>I started to write back, saying "But isn't the whole point of the locking that there is no way any other thread could acquire that lock until Dispose is called, thus calling Monitor.Exit and removing the object from the hashtable?", and then I was, as they say, enlightened.  The sequence of events in the TimedLock runs like this:</p>
<code><pre>
TimedLock tl = TimedLock.Lock(o);
  Monitor.TryEnter(o);
  StackTraces.Add(o);
...
tl.Dispose();
  Monitor.Exit(o);
  StackTraces.Remove(o);
</pre></code>
<p>On a single-CPU machine (such as our test server), this code runs fine, I would guess, 99.99999% of the time.  On a dual-cpu machine (such as the production server in question), however, it runs fine only 99% of the time.  That 100th time, here's what happens...(assuming o is the same object in both threads)</p>
<code><pre>
Thread A                              Thread B
TimedLock tl = TimedLock.Lock(o);
  Monitor.TryEnter(o);
  StackTraces.Add(o);                 TimedLock tl = TimedLock.Lock(o);
...                                     Monitor.TryEnter(o); // blocked
...                                   ...waiting
...                                   ...waiting
tl.Dispose();                         ...waiting
  Monitor.Exit(o);                    ...waiting
                                        StackTraces.Add(o); //******
  StackTraces.Remove(o);
</pre></code>
<p>The starred line is where the exception gets thrown.  Textbook race condition -- if Thread B doesn't hit that Add() call between Thread A's calls to Monitor.Exit and StackTraces.Remove, then everything looks fine.  But every once in a while (such as when processing a send and a receive simultaneously on a socket), it'll hit that tiny little target and blow the whole thing up.</p>
<p>What's worse is that as written, once that target has been hit, that object can't be successfully TimedLocked (<em>even though the original lock has been released</em>) until the TimedLock that hit the exception has been finalized.  This is true even if you wrap the TimedLock in a using statement (because the exception will leave using() with a null reference, which it can't Dispose).</p>
<p>The fix?  Simple -- swap the order of the Monitor.Exit() and StackTraces.Remove() calls.  That ensures that the object will be removed from the hash table before any other thread can try to re-add it.</p>
<p>This all looks very cut and dry now that I've laid it out, but before anyone goes accusing Phil of not knowing his stuff, reread the subject of this post.  Multithreading <em>is</em> hard.  .Net (and other modern languages) do a good job of hiding some of the complexity; for most WinForms apps, for instance, threading is very easy as long as you remember to use InvokeRequired and Invoke.  For something more complex, for instance a server app with multiple long-running threads that must access common resources, you need some help, and writing that help can be very difficult.  It took me about 3 full days to find this bug, and all I have to say at the end is that if I <em>weren't</em> using a good helper class like TimedLock, it would have taken me much, much longer.</p>
<p>One other lesson I've (re)learned... always always always test multithreaded code on a multiprocessor machine, because it's so much easier to hit race conditions and other problems on that platform.</p>]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term=".Net" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="Programming" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>
<entry>
   <title>Innovative--or at least creative--recruiting</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2004/09/innovativeor_at_least_creativerecruiting.html" />
   <id>tag:www.randomtree.org,2004:/eric/techblog//4.792</id>
   
   <published>2004-09-16T16:42:55Z</published>
   <updated>2005-09-29T17:14:02Z</updated>
   
   <summary>Slashdot posted a story about a sign in Cambridge, MA that poses a mathematical riddle which, when solved, leads to a website which poses yet another riddle which, when solved, ends up being a recruiting pitch for Google. Google actually...</summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<p>Slashdot posted a story about a <a href="http://www.npr.org/features/feature.php?wfId=3916173">sign in Cambridge, MA</a> that poses a mathematical riddle which, when solved, leads to a website which poses yet another riddle which, when solved, ends up being a recruiting pitch for Google.</p>
<p>Google actually seems to be doing a lot of this--the current issue of Dr. Dobb's Journal has a leaflet in the middle which is several pages of strange or difficult (or both) questions and a postage-paid envelope.  The envelope is addressed to Google, and encourages you to include your resume with your answers.  This <a href="http://news.com.com/Google+recruits+eggheads+with+mystery+billboard/2100-1023_3-5263941.html?part=rss&tag=5263941&subj=news.1023.20">isn't the first time Google has used this particular billboard, either</a>.  And of course other companies have used <a href="http://www.pbase.com/images/24017078.original.jpg">similar, if not quite so difficult to solve, tricks</a> as well.</p>
<p>I find this really interesting.  Most companies act as if it's the potential employees' job to find and interest them, not the other way around.  I don't live in any of the <a href="http://www.google.com/jobs/eng/sw.html">places they're hiring people like me for</a>, but if I did I know this would pique my interest.</p>]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term="Work" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>
<entry>
   <title>The Windows Task Scheduler Is Not a Second-Class Citizen</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2004/09/the_windows_task_scheduler_is_not_a_secondclass_citizen.html" />
   <id>tag:www.randomtree.org,2004:/eric/techblog//4.791</id>
   
   <published>2004-09-15T18:05:33Z</published>
   <updated>2005-09-29T17:14:02Z</updated>
   
   <summary>R. Frank Lutz has posted a tutorial on scheduled posts using MT3.1 which makes one of those statements that drives me slightly nuts: Task Scheduler, which comes bundled with Windows attempts to make automation of tasks effortless. Unfortunately, it is...</summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<a title="Lutz-R. Frank MT 3.1: MT 3.1 Tutorial - Scheduled Posting on Windows/IIS 5.1" href="http://localmail.dynip.com/_MT3/archives/2004/09/mt_31_tutorial_1.html">R. Frank Lutz has posted a tutorial on scheduled posts using MT3.1</a> which makes one of those statements that drives me slightly nuts:</p>
<blockquote cite="http://localmail.dynip.com/_MT3/archives/2004/09/mt_31_tutorial_1.html"><p>Task Scheduler, which comes bundled with Windows attempts to make automation of tasks effortless. Unfortunately, it is not very configurable and basic in what it is capable of. On UNIX and Linux systems, Cron is what is used for task scheduling. This scheduler is very configurable, and is capable of well more then its Windows counterpart.</p></blockquote>
<p>This isn't actually true.  I believe it used to be, though I'm not sure, but for years now the Windows Task Scheduler has been far more capable than most people realize out of the box--it just hides it well.</p>
<p>To set up a task that runs every 15 minutes, here are the steps:</p>
<ol>
<li>Start->Control Panel->Scheduled Tasks->Add Scheduled Task.</li>
<li>Click Next.</li>
<li>Select any program (we'll be replacing this so it doesn't matter) and hit Next.</li>
<li>Name the task "MT Periodic Tasks".</li>
<li>Select Perform This Task Daily and hit Next.
Hit Next (accept the default start time/date).</li>
<li>If prompted, enter your username and password (twice for the password) and hit Next.</li>
<li>Check "Open advanced properties for this task" and hit Finish.  The advanced properties will open.</li>
<li>Change the Run: box to read "perl run-periodic-tasks".</li>
<li>Change the Start In: box to the name of the directory where your run-periodic-tasks script is (for example C:\Inetpub\wwwroot\mt\tools).</li>
<li>Select the Schedule tab and click Advanced.  Pick today's date as the start date, and check Repeat Task.  Set the Every boxes to whatever your repeat rate should be (for instance 15 and minutes).  Check the "Duration" button, and set it to 23 hours and 59 minutes and click OK.  This will run the task every day and repeat it every 15 minutes for 24 hours.  At the end of the 24 hours it will be a new day, and the task will start over -- repeating every 15 minutes for another 24 hours.  This part of the interface is especially unintuitive; somebody should make a new interface for Task Scheduler that actually makes sense to normal people.  :-P</li>
<li>Change the start time to 15 minutes from now and click OK.  If you are prompted again for your username and password, put them in and hit OK.</li>
</ol>
<p>Your task will now run every 15 minutes until you disable or delete it.</p>]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term="Windows" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>
<entry>
   <title>SharpTerminal Update: Large DPI Gotchas</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2004/07/sharpterminal_update_large_dpi_gotchas.html" />
   <id>tag:www.randomtree.org,2004:/eric/techblog//4.765</id>
   
   <published>2004-07-30T15:48:08Z</published>
   <updated>2005-09-29T17:14:02Z</updated>
   
   <summary>Another update to SharpTerminal: this one fixes the large blank spaces on the bottom and right sides of the GUI, as well as a minor startup bug where if you hadn&amp;#8217;t saved any default settings, and hit Connect without going...</summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<p>Another update to SharpTerminal: this one fixes the large blank spaces on the bottom and right sides of the GUI, as well as a minor startup bug where if you hadn't saved any default settings, and hit Connect without going to the Config tab, you'd get an error.  Going to the Config tab and back fixed the problem, but now it shouldn't appear at all.</p>
<p>The GUI bug was an interesting one for me.  The computer I write SharpTerminal on had the DPI setting (Display Properties, Settings, Advanced) set to Large (90DPI).  So the GUI looked fine on that computer, but it turns out that .Net is smart enough to perform <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnforms/html/winforms12102002.asp">Automatic Control Scaling</a> according to the difference between the developer's settings and the runtime settings.  This works great when the developer's settings are Normal and the runtime settings are whatever; Windows Forms scales the GUI appropriately.  Things get a little weird when the developer's settings are Large (or possible any non-Normal setting)--as you can see in the screenshot below, on a system set to Normal, the scaling doesn't quite work:</p>
<img src="/eric/img/st_nondev.png" alt="User interface with large blank gaps on the right and bottom edges." />
<p>The solution turns out to be fairly convoluted.  First, set the developer's computer to use Normal DPI settings and restart the PC.  Next, open the solution in VS.Net and go to the code for the form with the issue.  Look for a line that says <code>this.AutoScaleBaseSize = new System.Drawing.Size(6, 15);</code> in the Windows Form Designer generated code region, and change the values to 5, 13 (the default values for a Normal system).  Open the form in designer mode.  Things will likely be very screwed up (controls will run off the bottom and right sides).  Fix them.  Note that some controls--for instance, the Microsoft ActiveX Web Browser Control--will probably have to be removed and readded in order to work properly.  Recompile and the app should look right.</p>
<p>Of course, probably the best idea is for developers to not use strange DPI sizes to develop UIs in the first place.  :-P</p>
]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term=".Net" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="SharpTerminal" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="Windows" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>
<entry>
   <title>SharpTerminal Update - COM port fix</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2004/07/sharpterminal_update_com_port_fix.html" />
   <id>tag:www.randomtree.org,2004:/eric/techblog//4.762</id>
   
   <published>2004-07-27T20:26:00Z</published>
   <updated>2006-06-22T18:51:46Z</updated>
   
   <summary><![CDATA[New version of SharpTerminal available. Here&#8217;s the changelog: Fixed issues with COM ports not being consistently named between systems. Seems like the name of the COM port varies from system to system -&gt; Mobo related? Added ability to disable local...]]></summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<p>New version of <a href="/tracker/download.php?Link=SharpTerminal">SharpTerminal</a> available.  Here's the changelog:
<ul>
<li>Fixed issues with COM ports not being consistently named between systems. Seems like the name of the COM port varies from system to system -&gt; Mobo related?</li>
<li>Added ability to disable local echoing.</li>
<li>Added ability to automatically append a string (incl. hex chars) to every message.</li>
</ol>
</ul>
</p>]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term="SharpTerminal" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>
<entry>
   <title>How not to convince people to convert</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2004/07/how_not_to_convince_people_to_convert.html" />
   <id>tag:www.randomtree.org,2004:/eric/techblog//4.759</id>
   
   <published>2004-07-13T04:09:49Z</published>
   <updated>2005-09-29T17:14:02Z</updated>
   
   <summary>So today Erik Porter linked to a very silly list of reasons to switch from VB.Net to C#. I say very silly because most&amp;#8212;nearly all&amp;#8212;of the &amp;#8220;reasons&amp;#8221; are either nonsense, irrelevant, or outright wrong. As someone who recently switched from...</summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<p>So today Erik Porter <a href="http://weblogs.asp.net/eporter/archive/2004/07/12/180951.aspx">linked to</a> a <a href="http://www.elegancetech.com/CSVB_WhyConvert.aspx">very silly list of reasons to switch from VB.Net to C#</a>.  I say very silly because most--nearly all--of the "reasons" are either nonsense, irrelevant, or outright wrong.  As someone who recently switched from working primarily in VB.Net to primarily in C# (not on my own initiative, and having no axe to grind), I feel I can shed a little light on this foolishness.  In order, my comments in italics:</p>
<p>For the Developer:
<ol>
  <li> Developers who program primarily in C# earn 26 percent more than those who develop primarily in Visual Basic .NET.<br /><em>This is about the only one I have no issue with, as long as the survey data is good.  I'm not 100% sure I buy it, but at least it's not completely obviously off-base.</em></li>
  <li> C# just looks more elegant because it was consistently designed. VB.Net was evolved over many years and has inconsistencies.<br /><em>You can write C# code that looks like a mishmash of styles (and even languages) because--surprise!--there's more than one way to write programs!  Similarly, it is entirely possible to write a completely .Net, FxCop-compliant application in pure VB.Net.</em></li>
  <li> C# is closer to Java which means it is easier for you to move to or from Java. This is good for your career.<br /><em>Poppycock.  The real differences are <em>not</em> in the syntax--they're in the class library.  Going from C# to Java will entail just as much of a learning curve as going from VB.Net to Java (since the BCL is <em>identical</em> for both VB and C#).  Any developer worth the crud in their keyboard can learn a new syntax very easily; it's the BCLs that are difficult.</em></li>
  <li> C# is perceived as a ?real? language where VB.Net is still perceived as a ?toy? language.<br /><em>Maybe.  And I say <strong>maybe</strong>.  But perceptions change.</em></li>
  <li> Microsoft does all of its internal .Net development in C#. Even MS thinks C# is the better language.<br /><em>That's a lie.  Microsoft internal teams do work in VB.Net.  It's true that the majority of them work in C#, but they're coming from a C++ background--C# is more natural.  VB.Net is in no way inferior to C# because of this.</em></li>
  <li>C# has the following features which VB.Net doesn?t have:
<ul><li>Operator overloading<br /><em>Will be added to VB.Net in 2005--and I've never missed it, anyway.  How much real development involves overloading the + operator?</em></li>
<li>XML code documentation<br /><em>Has been available via a free plugin forever, and will be in 2005.  Yee-haw.</em></li>
<li>Ability to write ?unsafe? code for better interoperability.<br /><em>In the (extremely rare IME) situation where you need unsafe code, it's trivial to drop down into C# or even C++ for it.</em></li></ul></li>
  <li>Microsoft is actively adding new useful features to C# including generics, interators, anonymous methods, and partial types.<br /><em>Whereas VB.Net is stagnant?   Hardly.  Generics and partial types are coming to VB.Net at the same time, interators (sic) already exist, and some VB.Net features--Edit and Continue and the My classes, for instance--won't be in C# at all.</em></li>
</ol></p>
<p>For the Manager:
<ol>
  <li> Your code quality will improve because C# catches potential errors (example: variable use allowed before initialization and dead code) that are permitted in VB.Net.<br /><em>Am I the only one who's ever heard of Option Strict?  As for dead code, yeah, it's nice that the C# compiler catches it, but I bet the VB.Net 2005 compiler will too.</em></li>
  <li>Your developers will be more productive because they will work in a language that they like.<br /><em>I really need a "confused" emoticon here.  I don't know anyone (at least anyone who's ever actually used it) that doesn't like VB.Net.</em></li>
  <li>If your project is a mix of VB.Net and C# code, your developers will be more productive because they won?t have to switch between languages.<br /><em>That doesn't make any fucking sense at all.  If the project is a mix, then choosing either language is about as effective.</em></li>
  <li>C# is more portable than VB.Net. It is closer to Java which makes it easier to port code to Java later. It also can run on other operating systems (including Linux) by using the  Mono and  DotGNU  open source projects.<br /><em>Again with the portable-to-Java canard.  Say it with me: <strong>Base Class Library</strong>.  And Mono should be adding support for VB.Net towards the end of this year.</em></li>
  <li> C# has been submitted as a standard language (ECMA) which makes its syntax more stable. Microsoft could make drastic changes to VB.Net at any time.<br /><em>They could make drastic changes to the Office object model, too, but they're not retarded.  If they were going to make drastic changes, they'd have done it between VB6 and VB.Net (*cough*AndAlso/OrElse*cough*), not between VB.Net 2003 and 2005.</em></li>
  <li>Microsoft does all of its internal development in C#.<br /><em>Repeating it doesn't make it any less wrong.</em></li>
  <li> C# is better because the following features make it easier to write better code faster.<br /><em>Yeah, see #6 on the Developer side.  Repeating it doesn't make it any less wrong.</em></li>
</ol>
</p>
<p>Don't get me wrong... I like C# a lot (some days better than VB.Net, some days not).  But choosing between VB.Net and C# has nothing to do with the above list (excepting perhaps #1 on the developer side) and everything to do with your background and comfort level.  If you know VB6, go with VB.Net.  If you know C++ or Java, go with C#.  If you know two or more, go hog wild.  :)</p>]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term=".Net" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="Professionalism" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="Rants" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>
<entry>
   <title>SharpTerminal 1.0</title>
   <link rel="alternate" type="text/html" href="http://www.randomtree.org/eric/techblog/archives/2004/06/sharpterminal_10.html" />
   <id>tag:www.randomtree.org,2004:/eric/techblog//4.745</id>
   
   <published>2004-06-25T02:37:14Z</published>
   <updated>2006-06-22T18:51:35Z</updated>
   
   <summary>So yeah, this is my new blog showcasing technology and programming, so that Jenny doesn&amp;#8217;t have to read all that boring stuff unless she wants to. ;) For my first post, I&amp;#8217;m going to present a little app I&amp;#8217;ve put...</summary>
   <content type="html" xml:lang="en" xml:base="http://www.randomtree.org/eric/techblog/">
      <![CDATA[<p>So yeah, this is my new blog showcasing technology and programming, so that Jenny doesn't have to read all that boring stuff unless she wants to.  ;)</p>
<p>For my first post, I'm going to present a little app I've put together called SharpTerminal.  As the name suggests, it's a terminal app (essentially a replacement for Hyperterminal) written in C#.  It does a number of things Hyperterminal doesn't do, including a command buffer, easy entry of binary (non-printable) data, display of carriage return/line feed, etc.  Oh heck, here's the ReadMe:</p>
<blockquote>
<p>Features:
<ol>
<li>View communication as either ASCII text or hexadecimal values</li>
<li>Save full session transcripts in multiple formats for easy analysis</li>
<li>Open previous session transcripts</li>
<li>Easy entry of binary data (prepend with 0x for hex entry)</li>
<li>Unlimited command history (up and down arrow in Send box)</li>
<li>Show or hide connection, control line, etc events.</li>
<li>Colored text for ease of distinguishing between sent and received data</li>
<li>Multithreaded for responsiveness</li>
<li>Prettier than Hyperterminal</li>
</ol></p>
<p>Future Enhancements:
<ol>
<li>Error Handling is not completely up to snuff.  It won't crash, but it's not as pretty as it could be.</li>
<li>Allow user to select encoding for "Text" mode.</li>
<li>Enable DTR handshaking</li>
<li>Consider allowing the intermixing of ASCII and binary?</li>
<li>Add automatic crash reporting.</li>
</ol></p>
<p>System Requirements:
<ol>
<li>Microsoft .Net Framework, version 1.1.  It might run against 1.0, I haven't tried.</li>
<li>Internet Explorer (any version 4 or later should work AFAIK).</li>
<li>One or more serial ports.</li>
</ol></p>
<p>Licensing:<br/>
SharpTerminal use is not limited; you may copy it, redistribute it freely, use it in a business, install it on a rocket and shoot it to the moon, or anything else I haven't mentioned here, with the following restrictions:
<ol>
<li>No claiming it is your own work.  You must include this ReadMe.txt file, UNMODIFIED, any time you redistribute it.</li>
<li>Actually, that's pretty much it.  If you really need specifics, see <a href="http://creativecommons.org/licenses/by/1.0/">http://creativecommons.org/licenses/by/1.0/</a></li>
</ol></p>
<p>Questions? Comments? Bugs? Feature Requests?<br/>
Visit <a href="http://www.randomtree.org/sharpterminal/">http://www.randomtree.org/sharpterminal/</a> or e-mail <a href="mailto:code@randomtree.org?Subject=SharpTerminal">code@randomtree.org</a></p>

<p>All code, text, and images copyright 2004 Eric Means.</p>
</blockquote>
<p>If it sounds like something you could use, download <a href="/tracker/download.php?Link=SharpTerminal">SharpTerminal</a> and give it a try (the zip file is about 1.5MB)!</p>]]>
      
   </content>
   <author>
      <name>Eric</name>
      <uri>http://www.randomtree.org/eric/</uri>
   </author>
         <category term=".Net" scheme="http://www.sixapart.com/ns/types#category" />
         <category term="SharpTerminal" scheme="http://www.sixapart.com/ns/types#category" />
   
   
</entry>

</feed>
