Wednesday, May 30, 2007

MFC Sucks. Badly.

I've done a fair amount of GUI-programming over the last 10 years, and there are a lot of toolkits out there of varying quality. None of them stand out as "excellent" in my eyes, but there is one which stands out as particularly lousy. MFC.

MFC is a patchwork of old (and in many cases non-existing) design, backward-compatibility fixes, API limitations, bad documentation, and bugfixes. Just to take a few examples:

  • The OnVScroll() method which is called when a window's vertical scrollbar changes only gets the position as a 16-bit integer. 32-bit integers are supported, but you need to call GetScrollInfo() to get it.
  • OnVScroll() gets a pointer to a CScrollBar control, but this pointer is unusable (and in most cases NULL). Instead, you need to call GetScrollInfo() to get a reference to the scrollbar.
  • CListCtrl can be used as a "virtual listview", i.e. you specify the number of lines and a callback which is invoked each time the view needs the data for a given row. Useful when you need a large number of rows and fetch data on demand (such as displaying memory contents). However, it refuses to use more than 100 million elements. And, yes, this is then power-of-ten kind of million, not the power-of-two you might have expected in case the widget uses a part of the 32 bit index integer for tagging or such. And all documentation on this, including web articles and code samples consider 100 million to be "huge" and "enormous". If you have a 32-bit integer as index, why can't I have 2^32 entries in the view.
  • You can't set CListCtrl's vertical position by setting the position on the scrollbar.
  • If you want to change colors in individual cells in a CListCtrl(), you must implement your own drawing function from scratch. There is no way to enhance the list control's own implementation. You might as well reimplement the entire list control from scratch (which many people seem to have done.)
  • There is no way (short of black magic and sacrificing green goats) to get CListView to use another implementation of CListCtrl. There are lots of variants of CListCtrl on the web, but exchanging the one ClistView uses is easier said than done.
  • I needed to add a text-display window below my CListView which was to display details about the selected row. This required 4 (!) extra classes with ~500 LOC. Compare this to XMonad, a tiling X11 window manager implemented in about the same amount of code. But they used Haskell, not MFC. ;-)

Wednesday, May 16, 2007

Yet Another Annoying Thing About Visual Studio

I'm getting more and more annoyed by Visual Studio for every day I use it. Today's annoying thing is broken .sbr files.

Visual Studio has the annoying habit of leaving broken .sbr files around. I'm not sure when or why they appear, possibly when a build is interrupted. When bscmake at a later point tries to read these broken files, it croaks and causes the entire build to fail.

Every single other compiler in the world has the good sense of not leaving incomplete files on disk when exiting. But not Microsoft's. And it's not like this is a new bug; a quick check on google showed that this bug has been there at least since 2000.

Wednesday, May 9, 2007

Redirecting output from compiler/linker in Visual Studio 2005

I ran into an "interesting" (i.e. very annoying) problem today. I was in the process of figuring out a way to know if two Windows executables or DLLs are different. Just comparing them byte-for-byte does not work; the output cl.exe is not identical even if the incoming source code is.

So I tried to use dumpbin /disasm and compare the two disassembly listing, which worked nicely. I then wrote a little Ruby script to add as a post-build step in Visual Studio:
require 'digest/sha1'
require 'tempfile'

inputfile = ARGV.shift
IO.popen("dumpbin /disasm #{inputfile}") do |io|
Tempfile.open(File.basename(inputfile)) do |tempio|
while io.gets
if $_ =~ /.*Dump of file.*/
next
else
tempio.write($_)
end
end
tempio.rewind
File.open(inputfile + ".sha1", "w") do |outio|
outio.puts Digest::SHA1.hexdigest(tempio.read)
puts "Wrote SHA1 signature to #{outio.path}"
end
end
end
but to my surprise, dumpbin insisted on passing its output to the Visual Studio output window instead of through stdout and into my Ruby program. WTF?!

I started to dig. What could possibly cause dumpbin.exe to bypass stdout/stderr when run from inside Visual Studio? I fired up Process Explorer during build and studied the environment variables of cl.exe. Is there any magic going on here? The following environment variables caught my eye:
VCBuildHelper_CancelEvent
VCBuildHelper_Command
VS_UNICODE_OUTPUT
Knowing that Google Is Your Friend, I got a few hits, which gave me a hint about Unicode output being handled differently depending cl.exe is executed outside or inside Visual Studio. It turns out that people has compiler wrappers such as STLFILT which went mad when the output from the compiler disappeared.

Was it really so difficult for Microsoft to anticipate this problem? Piping output from a compiler or linker isn't a very odd thing to do. And how necessary is it to be able to have Unicode error messages? And why didn't they fix stdout/stderr to be able to handle Unicode? -- which would have been The Right Solution to the problem in the first place.

Monday, May 7, 2007

What happens during ExitProcess()

"The Old New Thing" is an excellent blog if you want to know things about Windows which is not in the documentation. Such as how ExitProcess works:

"Exiting is one of the scariest moments in the lifetime of a process. (Sort of how landing is one of the scariest moments of air travel.)"

Thursday, May 3, 2007

The quest for a real build system, part 1

I've been looking for a "real" build system for quite some time, something which allows me to correctly and completely specify the relationsships between all elements of my program in a compact and easy-to-maintain sort of way. I didn't think it would be so hard, but apparently it is.

Take Visual Studio, for an example. If I build an executable in directory A and also want to copy it to B, the only way to do that (except for possibly creating a special project for the task) is to add a post-build step which copies the executable from A to B. However, there is no way to make Visual Studio guarantee that the executables actually is copied. If the copy-step fails, it will never be executed again unless the executable itself is rebuilt. Since Visual Studio does not know (or care) about what happens in a post-build step, it is impossible to know if all post-build steps in a project actually succeeded. In other words, post-build steps are useless for anything related to the actual build process.

SCons does this right. Its specification files are Python programs and you can manipulate nodes in the dependency graph exactly as you wish. Given that you've specified your dependency graph correctly, you can actually run scons and it will rerun exactly those commands necessary to bring your targets up-to-date. As a result, you really don't need a "clean" command (even if there is one).

Unfortunately, SCons has serious performance issues and does not scale well for very large systems. But sometimes I'm willing to pay a lot of performance for actually doing correct builds.

I've been using CMake at work for some time, and it has some nice features which makes it the best there is at the moment. The fact that it can generate Visual Studio project files, while still giving command-line people like me a Makefile is a big plus.