Joe White’s Blog

Life, .NET, and Cats


Archive for October, 2004

IComparable change

Thursday, October 28th, 2004

Krzysztof Cwalina, one of the .NET library designers, asked a question on his blog about a proposed change to IComparable<T> in .NET 2.0.

Right now, Whidbey has a generic interface called IComparable<T>, which lets an object compare itself to other objects; and IComparer<T>, which lets a third party compare objects. They’re used for things like sorting, comparing, hashtable keys, etc. The problem is, the interfaces, as they’re designed today in the Whidbey betas, pretty much assume that the objects you’re comparing will have a natural order (i.e., they’ll have a meaningful way to say that X is “less than” or “greater than” Y). If you’re ever dealing with objects that don’t have a natural order (e.g., points, rectangles, colors, directories, state-machine states, etc.), you would either have to make up an arbitrary ordering, or only implement half the interface (implement the Equals method, which doesn’t ask about ordering, but throw an exception from Compare, which does).

The design team is thinking about splitting those interfaces, so there’s one interface for “are these equal?” and another for “which one is greater?”. But they won’t do it unless there’s overwhelming support from the community.

Let’s go overwhelm them. (grin)

What is the expected behavior of this code?

Thursday, October 28th, 2004

Here’s the code. This app writes data to a file on a network drive, sets the file to read-only, and then runs a sanity check on the file size. What would you expect the result of this program to be?

The code happens to be written in Delphi, but would apply to any Win32 language. .NET, interestingly enough, appears to contain a workaround for the problem.

That’s hint #1, by the way: the bug is not in the code itself; it’s in the Windows API. (There’s an easy workaround, but you’ll never find it by reading the Windows API docs.)

program Project1;

{$APPTYPE CONSOLE}
{$WARN SYMBOL_PLATFORM OFF}

uses
  Windows,
  SysUtils,
  Classes;

var
  FileName: string;
  Stream: TFileStream;
  I: Integer;
  B: Byte;
  DesiredSize: Integer;

begin
  FileName := 'H:\Foo.dat';
  DesiredSize := 123456;

  Stream := TFileStream.Create(FileName, fmCreate or fmShareDenyWrite);
  try
    for I := 0 to DesiredSize - 1 do
    begin
      B := Byte(I);
      Stream.Write(B, SizeOf(B));
    end;
    Windows.SetFileAttributes(PChar(FileName), faReadOnly);
  finally
    FreeAndNil(Stream);
  end;

  WriteLn('Desired size: ', DesiredSize);
  Stream := TFileStream.Create(FileName, fmOpenRead);
  try
    WriteLn('Actual size: ', GetFileSize(Stream.Handle, nil));
  finally
    FreeAndNil(Stream);
  end;

  WriteLn('Press ENTER to continue');
  ReadLn;
end.

Hint #2: The results depend not just on your operating system, but on which service packs you have installed.

Hint #3: The results are nondeterministic.

And here’s hint #4.

To quote my boss: “Those guys.”

WatchCat

Tuesday, October 26th, 2004

WatchCat is a little freeware utility I found quite a few years back (I was developing on Windows 95 at the time, if that gives you any idea).

It’s a sweet little tool. It can minimize windows to the system tray, so they’re not hogging so much space on your taskbar. Once WatchCat is running, you just right-click on a window’s Minimize button, and away it goes; left-click the tray icon, and the window pops right back.

It can also hide windows entirely (not even a tray icon; use WatchCat’s context menu to bring them back), and you can even set it to automatically minimize/tray/hide certain windows after a timeout period. You tell it how to identify windows (which ones get hidden, which ones get minimized to the tray, what each window’s timeout value is, whether you need a password to bring the window back, etc.) by writing regular expressions against the class name and/or title bar text. (I used to use this for Web-based admin interfaces, so they would vanish from my desktop after a while of disuse, and not be sitting there for anyone to read over my shoulder — remember, Windows 95, so no “lock workstation”.)

You can also assign hotkeys to various Windows functions. I used to set it so the Pause key would start the screensaver. (I never did figure out why Microsoft didn’t do that by default.) Then I would set a password on my screensaver… so with WatchCat, I guess I did have a “lock workstation” on Win95.

Much time has passed. I’m no longer developing on Windows 95 (thank God).

But I just found myself needing WatchCat again.

I’ve been doing a lot of refactoring the past few days, so I always have a large number of applications open. (Since it’s not like I have an IDE with built-in refactoring or anything.) (Yet.) (Anxiously waiting for the Diamondback release in November sometime.)

Here’s a quick look at my taskbar right now:

  • Task Manager (launched from my Startup menu, so I can always keep an eye on CPU usage) (except when Windows randomly loses Task Manager’s tray icon)
  • Visual SourceSafe
  • Delphi 6 (where I’m actually doing the coding)
  • Visual Studio .NET 2003 (in case I need to make changes to SearchForm)
  • SearchForm (an app I wrote in Visual Studio .NET; it lets me do fancy searches through Delphi source, drag files to the Delphi IDE, and check files in/out of VSS. It’s sort of DGrok‘s little brother, but I wrote it on work time, so it’s proprietary.)
  • DelphiTools (a Delphi code parser and refactoring tool that we hired John Brant to write for us; we use this for Find References, Rename refactorings, and a few more-specialized tools)
  • Programmer’s File Editor (an MDI text editor where I keep a few cross-reference files open, like the “which unit is symbol X defined in?” report and the “who descends from class Y?” and “who implements interface Z?” reports that DelphiTools generates. I use an MDI editor so I don’t have to have three SDI editors all taking up space in the taskbar.)
  • Untitled – Notepad (various notes I make and refer back to throughout the day)

I’ve had all of these open all day. Then there are the browser windows I open when I need to look for something online (or blog about WatchCat), the chat windows from conversations on our internal Jabber server, Outlook whenever I need to read my e-mail, SharpReader and RssBandit whenever I’m reading RSS feeds, Windows Explorer windows, etc.

I run out of taskbar space reeeeeal quick these days. I think it’s been at least twice today that I’ve overflowed and the taskbar has acquired a scrollbar. So I went looking for WatchCat, so each app could be 16 pixels wide, instead of 40 or whatever a taskbar button is.

I had some trouble finding it; the author’s Web site is no more, and a couple of the shareware sites I found didn’t have WatchCat listed anymore. I eventually found it here.

There have been some new features since the last time I used it. Now you can assign hotkeys to various folders on your hard drive, so that just hitting a key will bring up a context menu showing the folder contents. I am definitely gonna have to spend some time fiddling with that. (Actually, maybe they did have it before. But I’ve spent a lot of time trying to do the same thing with the Quick Launch bar and its relatives, and I’m thinking WatchCat may be a way better solution.)

Only downside is that it doesn’t grok 32-bit icons, so when it paints the icons in the menu, anything with transparency looks kind of ugly. But I can live with that.

One odd thing: on WatchCat’s About page, it says it’s distributed under the GPL. Yet it isn’t distributed with source. Doesn’t that violate the license?

Grokking Delphi Source

Sunday, October 24th, 2004

Okay, it was getting too awkward to keep referring to “my tool that searches through Delphi source“, so I’ve given it a name. Announcing… DGrok.

(So called because it groks your Delphi source code. Well, okay, it doesn’t actually become one with your code, but it does way more grokking than your typical garden-variety grep.)

I just registered www.dgrok.com. It probably won’t be live for a day or two, but it will eventually point to the new DGrok category here on my blog.

Of course, DGrok is still in development, and I don’t have even a tentative release date for the first alpha. But at least the thing has a name now.

BorCon blog summary

Tuesday, October 19th, 2004

Erwien Saputra contacted me a while back. He wanted to put together a compilation of all the BorCon blogs, and asked permission to use my blog content. (Basically doing what I was trying to do with the BorCon wiki, except unlike me, Erwien actually got it finished.) He got pretty much all the blogs pulled into one place and grouped by category, and it’s now published on BDN. Pretty comprehensive. Check it out.

Control.Visible… ugh.

Monday, October 18th, 2004

Control.Visible lies.

I vaguely recalled this, actually, from some sessions in Reflector a few months ago. But it just bit me today. Not in a major way, but enough to be irritating.

I put my search tool’s search options into two expandable/collapsible panels, one for selecting the directories, the other for the search options. I’m not offhand aware of any good collapsible panels for WinForms, so I just rolled my own, with one control for the header and then a panel for the contents. And I showed a little “+” or “-” glyph on the header, so it would look familiar to anyone who’s used a treeview. I updated that glyph based on the Visible property of the panel, and had hooks in various places to make sure the glyph was up-to-date.

Well, this morning I noticed that, when the program came up, those glyphs were wrong. The first time I do anything to update them, they correct themselves and stay correct for the rest of the program run.

Set breakpoints. The method that updates the glyphs gets called several times during startup, and each time, the panels’ Visible property is true… up until the last time, when it’s false.

Now, I know I’m not changing the panels’ Visible property during startup. And I know that the panels are visible once the window is displayed. So… what the heck is the deal?

At least WinForms gives me a VisibleChanged event that I can hook. So I hook it, and put in a breakpoint. Lo and behold, it does indeed fire when the panels’ Visible is mysteriously changing to false. (Oddly enough, it does not fire again when their Visible is mysteriously changing back to true.) I check the stack trace, and among the code on the stack is Control.WmShowWindow.

That’s when it clicks in my head: Aha, that’s right. The value you set into Visible is not necessarily the value you get back out.

The MSDN docs have this to say:

Gets or sets a value indicating whether the control is displayed.

Wow, that’s helpful. I can tell you from looking in Reflector, however, that what happens when you read Visible is this: it returns this.Visible && Parent.Visible && Parent.Parent.Visible && Parent.Parent.Parent.Visible && …

That’s not quite how they implement it, but it makes Visible nearly useless for nearly everything. It certainly makes it useless for what I was using it for, because there are times when it returns random values (i.e., when the window is in the process of being shown, and events are getting fired, but Form.Visible is not yet true so all of its children lie).

This is not how properties are supposed to work. I want a property to return the value I set into it. Bad WinForms! No biscuit!

Once again, this is an area where Delphi got it right. You can set TControl.Visible to True, and it will return True, even if it’s on a form that’s not visible yet. They have a separate property, called Showing, that has the other “is it actually visible all the way up the chain?” behavior, for those rare occasions when that’s what you want. This seems much more in keeping with common sense.

Sigh. Not a big deal, once I identified the problem; I just added a couple more properties to my model object (ExpandDirectoryOptions and ExpandSearchOptions), and used them instead of reading the Visible property. Voila; problem solved.

Lesson learned: Consider the WinForms Control.Visible property to be write-only.

Option interaction

Sunday, October 17th, 2004

I’m making slow but (somewhat) steady progress on my Delphi-source-code searching program.

(I was serious, by the way: if anyone has any ideas for a good name for this tool, let me know. Otherwise, you’ll be stuck with whatever name I come up with.)

Directory recursion, complete with exclusions, is coded and ad-hoc tested. I don’t have that logic put into a thread yet, though, partly because I need to figure out how the pieces will fit together for passing the search options into the thread. In an effort to get that part straight in my head, I’ve started coding on the GUI.

The part I just finished is the search options. There are five checkboxes:

  • Whole words
    • Strict (only enabled when “Whole words” is checked)
  • Regular expressions
  • Loose whitespace
  • Ignore case

I’ve got an object that takes the search string you type in, plus these search options, and munges them together to create a Regex object. All the actual searching will be done via this regex. That makes life easier for things like “whole words”.

The transformation is interesting, though not terribly difficult, for the most part. “Ignore case” gets passed to the Regex constructor via the RegexOptions enum. “Loose whitespace” involves transforming any whitespace in the search string into \s+. “Regular expressions” passes the string through Regex.Escape(), and “Whole words” sticks a \b at the beginning of the string, and another \b at the end.

Actually, my “whole words” is a little smarter than that. \b matches the boundary between a word character (traditionally meaning “alphanumeric or underscore”, but extended in Unicode; see Char.IsLetterOrDigit) and a non-word character. That means that if I’m searching for “TFoo =“, and the search changes that to “\bTFoo =\b“, the regex will only find cases where there’s a non-word character (or the beginning of a line), followed by TFoo, a space, an equal sign, and then a word character (to establish the second word boundary). But our coding standards require another space after that equal sign, not a word character, so that regex will never match. (This is a constant source of frustration when I’m using GExperts grep; I periodically get zero search results because I forgot that “whole words” won’t work for a particular search.)

So I don’t do the naive “prepend and append \bs”, unless you check Strict. In normal “whole words” mode, I only prepend a \b if the search string starts with a word character, and I only append a \b if the search string ends with a word character. Which is generally what I mean when I leave “whole words” checked and then type something like “TFoo =“. Obviously I want a word break before the TFoo, but not after the equals. And this will figure that out. It’ll be nice to reduce the number of times I cuss out the tool for not returning any results.

Thing is, this logic doesn’t work so well when what you’re actually typing is a regex (i.e., if you check “Regular expressions”). If you type in the regex [A-Z]+, and have “whole words” checked, you’d expect to find whole words that are in all caps. (Well, assuming you also uncheck “ignore case”.) But my logic sees that the first character is [, which is not a word character, so it doesn’t prepend the \b. Same thing with the last character, +; not a word character, so it doesn’t append the \b. Net result being, it doesn’t do what you expect.

If anyone has any suggestions on how to fix this, let me know. (I’m thinking about disabling “whole words” if you check “regular expressions”. I’m not sure I like that, but I’m not sure there’s any good way around it without actually parsing the entire regex myself. Character classes, parentheses, alternation… whew. Hey, if you’re typing in a regex, you can probably type the \b too.)

The other unusual interaction was between “Regular expressions” and “Loose whitespace”. When you uncheck “Regular expressions”, the app runs your search string through Regex.Escape, which prepends all the special characters with a backslash so they’re not special anymore. But… space characters also get prepended with backslashes. Which makes my job, of finding whitespace and turning it into \s+ (a regex expression meaning “any number of whitespace characters, of any kind, be they spaces or tabs or newlines”), a good bit more difficult. If I leave that extra backslash there, and then change the space after it to \b+, I’ll be left with \\b+, meaning “a backslash followed by one or more ‘b‘ characters”. Not good.

They escape whitespace because whitespace means something special if you specify a particular flag in RegexOptions (IgnorePatternWhitespace). But you normally don’t specify that, and so you normally don’t need to escape whitespace. I looked for another version of Regex.Escape that covers this “normal” case, but I couldn’t find one.

I got around this one by iterating through all the characters in the string, letting whitespace (Char.IsWhiteSpace) pass through untouched, and calling Regex.Escape on each individual non-whitespace character. (Yes, of course I put the results into a StringBuilder.) Once I puzzled out that this was the right way to do it, it works well.

Next step: Write a thread to build the directory list, build the file list, read and parse the files, and check the regexes. Then getting the listview working properly (grumble grumble Microsoft grumble grumble not providing usable drag-and-drop out of the box grumble grumble Delphi has had fully-functional drag-and-drop since v1 grumble grumble). And then I’ll probably post an EXE if anyone wants to look at it. (The tool will still have a long way to go, but the very very basics should be at least somewhat usable.)

This year’s novel

Saturday, October 16th, 2004

I’m doing NaNoWriMo again this year. (For the uninitiated, that means I’m going to spend the month of November writing a 50,000-word novel. My past two years’ novels are posted on my writing site.)

Jennie is thinking about doing NaNoWriMo, too (this will be her first year). We went out to eat yesterday night, to celebrate the fact that Jennie is no longer employed by 24 Hour Fitness, and we were talking about our story ideas.

My problem was basically that I didn’t have a story idea yet. I had this idea I’ve been toying with for a while, about somebody who can control the flow of time, but it was an idea — not a story. For a story, you need a Bad Guy, and you need the Good Guy to be trying to fix something. High stakes and all that. I didn’t have any of that.

Well, while we were sitting in the IHOP, I happened to notice the hot-chocolate-making machine they had back behind their counter. The sign on it said “Cecilware”.

And gears started turning in my head.

Final Fantasy II (well, that’s what they called the U.S. version on the Super Nintendo; it’s Final Fantasy IV in Japan, and in the U.S. Playstation version) is one of my all-time favorite video games. I don’t like those newfangled 3-D games. I want my tile-based RPGs.

Well, the hero of Final Fantasy IV is a Dark Knight named Cecil. (Cecil’s quest to become a Paladin gave me the inspiration for my first novel.) And I sat there thinking… Cecilware. Cecil, and software. Hmm.

Jennie always knows to be very afraid when I get that sort of contemplative look on my face.

My first novel was very lighthearted. Parody fare, vaguely along the lines of Terry Pratchett or Douglas Adams (though not as good — hey, give me a break, it’s a first draft). I tried writing something more serious for my second novel, and it just didn’t work as well. This year, I think I’ll go back to humor.

And I think this is going to be a fun book to write. It’s going to be an oddball blend of heroic fantasy (knights, dragons, the awakening of an ancient evil) and modern day (Internet, computer programmers, cell phones, Google). Swords with scroll wheels. Casting spells using a Pocket PC. Internet wyrms.

And boy, the Department of Natural Resources is going to have its hands full after the U.S. annexes the land of Faerie…

Google Desktop Search

Friday, October 15th, 2004

Believe it. Use Google to search your own computer.

Bonus points for letting you search through Web pages you’ve visited recently, and better yet, for including thumbnails of the pages. They’ll get even more bonus points if “recently” turns out to mean “within the past six or eight months”, because that’s typically when I want to find something again and can’t remember how to get there.

And how the heck do they show hits from your local computer right there on a results page from www.google.com? (There’s a screenshot at the very bottom of this page.) That’s absolutely wild.

How the heck do they even know that you have Google Desktop installed? When you install it and then go to www.google.com, they automatically add “Desktop” to their list of services across the top of the page. And it’s a link directly to the service running on 127.0.0.1. How do they do that? They don’t alter your user-agent string (I checked that already). I have to assume that they tweak your browser cookie somehow, but I looked at my cookie for google.com and I didn’t see anything obvious. (Doesn’t mean it isn’t there; their cookie is a bit cryptic.)

But… the downside of this otherwise-cool tool. Points off for not letting me specify which file types to index. I want to Google through all the source code on my hard drive, dammit! It’s just text files with a different extension. Why can’t they let me specify file masks? (Heck, why can’t they run a heuristic to determine that something looks like a text file, and parse it regardless of the extension?)

Odd. Their Help site claims that, even if they don’t know how to read a file type, they’ll usually at least put the filename into their index. But that doesn’t appear to happen for .pas files.

I already submitted feedback suggesting that they let me specify file masks, or, alternatively, that they build in support for *.pas, *.dfm, *.inc, *.dpr, *.dpk, and *.cs. Googling through source would be a cool thing.

You know what would be even cooler, though? If they provided an API so that you could write a DLL that they call when they want to read a file. And if this DLL could give them all the searchable words from a file, and note which ones are most important. Then find somebody smart enough to write Delphi and C# language parsers that can talk to this API. (And decompilers that can talk to the API, for that matter. Imagine Googling through the .NET Library source!)

Just imagine: you type in the name of a class, and Google instantly pops up all your source files that use that class, with the unit containing the class declaration appearing at the top as “most relevant”. Now, that would be sweet.

All that’d be left then would be Google Brain Search.

Freeloading scumbucket

Friday, October 15th, 2004

All right, I admit it. I’m entering Rory’s stupid contest.

And I’m lowering my chances of winning by telling you about it, too.

neopoleon.com. Because the world obviously needed another short opinionated big-nosed man of French descent.

(Yeah, I know, that last part isn’t in the graphic. But it just sounds more poetic this way, you know?)


Joe White's Blog copyright © 2004-2011. Portions of the site layout use Yahoo! YUI Reset, Fonts, and Grids.
Proudly powered by WordPress. Entries (RSS) and Comments (RSS). Privacy policy