Hardcore Visual Basic (cont'd)

How I was seduced into a third edition despite reservations
I got mixed up in this third edition by accident. I was working on another project related to using Windows interfaces in Visual Basic. It occurred to me that I might be wasting time on VB5 hacks and workarounds if VB6 offered new interface features. So I sent mail to one of my long lost contacts and asked a few simple questions about interface access in VB6.

Now, if my source had simply said, "There's nothing new related to interfaces in VB6" that would have been the end of it. I would have muttered a few curses about why these thick-headed fools couldn't see the value of making all the new Windows system features just another part of the Visual Basic run-time library. But I wouldn't have been surprised or especially disappointed, and I would have gone on to other things.

Unfortunately, at this point the bureaucracy kicked in. My source said I'd have to get onto the Visual Studio beta program to get that information, and passed me off to someone whose job was to get prospective authors onto the beta. One thing led to another and eventually the beta discs arrived at my doorstep.

How and why I selected a co-author
Somewhere about this time I ran into an old acquaintance, Keith Pleas, at a social event. This was shortly after Keith had written his infamous "VB6 Answers All Your Dreams" column in Visual Basic Programmer's Journal. He claimed, among other things, that VB6 would borrow an inheritance implementation from Visual FoxPro and have a new Foe keyword as an alternative to Friend.

Unlike some readers, I knew Keith was joking because I had dealt with him before (I quoted his Escher alter-ego on page 311). In any case, we got to talking about VB6 and how surprised I was that it didn't really have the new features I expected. For example, there was no inheritance, even though I had seen preliminary specs for inheritance back when I was writing the second edition about VB5. In fact, I realized that there was hardly anything in VB6 that would affect my book. I realized that I could do a minor rewrite in a few months and keep my book selling for another year or so until VB7.

But I didn't want to do even a minor rewrite. That's when Keith and I came up with the idea that he would do the minor rewrite. I would be a kind of editor and consultant. Both names would appear on the book as co-authors, mine first. But if things worked out, he would do a major update for VB7. My name would move to second. By VB8 the name of the book would be changed to "Keith Pleas' Hardcore Visual Basic" and my name would be in tiny type at the bottom. I'd continue to get some royalties and credit for my past work, but I wouldn't have to do anything. Keith would take over the work and eventually the credit. He'd be a hardcore author without having to start from scratch. My book would live forever without me rewriting it. Everybody wins.

I wouldn't have considered this kind of deal with just anybody, but Keith has some unique abilities. He's a funny guy. I wouldn't have even talked with an author who had no sense of humor. He's a hardcore programmer--although one with a different experience and viewpoint than mine. And he's not afraid to criticize Visual Basic, although he's mellower than I am. That's good. I got a little carried away with anger and frustration in the second edition. A little more diplomacy and a little less confrontation would be a good thing.

My foolish plan for minor enhancements
At this point my plan was to rebuild all the VB5 versions of components and programs with VB6. Those of you familiar with the book know that compiling all the code isn't exactly trivial. There's a lot of code, and it can all be built in Debug and Release modes. I have a batch file to do the build, but as a trial I just wanted to run every program once in the IDE to prove everything still worked properly.

After that, I would reread the book, marking problems and changes. Keith would go through my notes and write up the changes. I'd have my say on a few issues I had forgotten the first time around. Keith would write a couple of new chapters in his areas of expertise. I might find a few VB6 features that I couldn't resist commenting on. Keith would go through my bug list, fixing everything he could and leaving a few things for me. I'd review the final text.

Then we'd turn it over to Microsoft Press and wait for the big bucks to start rolling in.

How the Third Edition died a horrible death amid flames of incompatibility
Unfortunately, all these wonderful plans came to naught. I'm not going to blame it all on VB6. I horribly underestimated the amount of writing involved. My bug list didn't seem that long when I thought about it, but it kind of expanded when I starting looking at specific bugs and trying to figure out what to do about them. I encountered more VB6 features that I wanted to talk about than I expected.

In short, I got more involved than I intended. In fact, I got more than involved. I got angry. But let's not get ahead of ourselves…

Why Visual Basic rejected my type library with extreme prejudice
I loaded up my new VB6 beta and tried to compile the first large project sample (alphabetically) in the Hardcore directory. That turns out to be AllAbout.vbg. VB6 refused. It displayed the message, "Variable uses an Automation type not supported in Visual Basic" and put the cursor on the following line in the CSystem class module (System.cls):

Private sys As SYSTEM_INFO

I looked up SYSTEM_INFO in the Object Browser and saw that it comes from my Windows API Type Library. Nothing in Visual Basic tells me what's wrong with this type, but it's pretty easy to guess. The fields lpMaximumApplicationAddress and lpMinimumApplicationAddress show up in the browser with the type As Any.

Most hardcore programmers know what As Any means as a parameter in a Declare statement, but the concept doesn't really make sense as the type of a field in user-defined type. A UDT variable has to have a size so that the compiler will know what to store in memory, but As Any implies a variable type, which would change the size of the structure. It won't work.

The As Any comes because the fields were defined like this in the C-like IDL language used for creating type libraries:

typedef struct SYSTEM_INFO {
    WORD  wProcessorArchitecture;
    WORD  wReserved;
    DWORD dwPageSize;
    LPVOID lpMinimumApplicationAddress;    // void * 
    LPVOID lpMaximumApplicationAddress;    // void *
    . . . 

It turns out that void * is the syntax recognized by Visual Basic as being equivalent (or at least similar) to As Any in Declare parameters, and so the Object Browser always shows anything defined as void * (or the C alias LPVOID) with the type As Any. But in fact Visual Basic doesn't have a clue what to do with this type when it appears in structures.

I already knew about this bug because two of my readers (Kirk Roybal and Gerry Thomas) had reported it in a different circumstance. Through a stupid mistake on my part, I had made the definition of the C typedef HFONT mean void * rather than long. In C, all handle types are void * for reasons that don't concern us, but they need to be changed to C long type so that VB programmers will see them with Basic Long type. I dutifully changed the definitions for all the handle types--HWND, HDC, HBITMAP, HCURSOR, and so on--to the correct type in my IDL source, but somehow HFONT slipped through the cracks. So if you looked at the CreateFont or CreateFontIndirect API from my type library in the Object Browser, you would see them returning As Any rather than Long. Worse, if you tried to use those functions (as Kirk and Gerry did), you'd crash--because Visual Basic doesn't have any more idea what to do with As Any in return values than it does in UDT fields.

In version 5, Visual Basic simply ignored As Any in contexts where it didn't make sense. If a user of my type library actually tried to use the lpMinimumApplicationAddress field of a SYSTEM_INFO variable, he or she would probably crash. In this case, hardly anybody would actually try to use these obscure and useless fields (or at least not try it twice), so this wasn't a real problem. But it was a bug waiting to happen, and in version 6 Microsoft decided to "fix" it by putting up a message saying that VB didn't understand the type. This behavior would have been reasonable in Visual Basic 4, but breaking a type library that worked fine in the last two versions didn't seem like a good idea to me, so I entered a bug. (The fix for this problem is described later.)

I pointed out in my bug report that more than 25,000 people worldwide had bought my book and all of them would have their code broken by this change. Now one might argue that it would be better to risk having five or ten programmers crash when they tried to use obscure fields that VB couldn't handle than to break the code of 25,000 programmers. But in fact neither the old version nor the new version was right, so I made a radical proposal: Why not fix the bug rather than choosing which version of the wrong behavior was less destructive? I suggested that Visual Basic should translate void * in these two contexts into Long--which is the only behavior that makes sense. Nobody would crash from a Long. No old code would be broken (because As Any didn't work in these contexts before). And a few hardcore programmers might pass the Long to CopyMemory and actually use the fields.

When you report a bug like this, you don't know the cost of fixing it. I know enough about compilers to estimate that this fix might take a couple of hours of development time. But of course that's a wild guess for someone who hasn't seen the source code. In reality it might take five minutes or 20 hours. And even if I was right, it might take another four hours to test and it would probably take program management eight hours of arguing to decide whether or not to do it. To be fair, this was in May, 1998, which was very late in the beta schedule. I didn't expect the bug to be fixed when I entered it, and it wasn't. In fact, I didn't hear yeah or nay about the bug until July--after the final build of VB6 was complete.

How I fixed the type library to no avail
I didn't expect a bug fix, and I didn't wait for one. I had a few bugs reported against my Windows API Type Library, and plenty of requests for enhancements. Some readers had even sent me IDL source for improvements they had in their private versions. I needed to update the library anyway, and if I was doing a VB6 update to the book, I could include the new type library there. It's harsh to tell readers of your book that they must update to the next version of your book if they want the code from the last version to work; but it was VB, not me, that was imposing this restriction on them.

The type library fix to get around this problem is simple:

typedef struct SYSTEM_INFO {
    WORD  wProcessorArchitecture;
    WORD  wReserved;
    DWORD dwPageSize;
    PTR   lpMinimumApplicationAddress; // LPVOID 
    PTR   lpMaximumApplicationAddress; // LPVOID 
    . . . 

In this case PTR is an alias for the C long type. I'm simply doing explicitly what Visual Basic ought to be doing behind the scenes. I found several other structures with the same problem (the most common was the BITMAP type) and fixed them too. I also fixed the problem with HFONT so that it mapped to a VB Long like all the other handle types. Then I made a bunch of other type library changes that I'll describe later.

I built this new type library, registered it, and once again tried to build the AllAbout project group. I got the following message: "Unexpected error (32810)". I didn't expect much from the help on this error, and sure enough it simply paraphrased the text of the message and told me to report the number to Microsoft Support. I reported the bug through the proper beta channels with the highest possible priority and then I waited.

Waiting was all I could do. I was able to determine by experimenting with other projects that the error occurred in the VBCore component, not in the AllAbout program. That's the only thing I could tell about it, but that was enough. As anyone who owns the second edition of my book can attest, almost all my samples use the VBCore component. It's a kind of enhanced run-time library that provides core functions used everywhere else. If I can't build VBCore, I can't build anything. The inadequate error reporting stopped me cold, and I pointed this out in the bug report.

Now it happened that about this time the Visual Studio beta team (you couldn't be a VB beta site, you have to get all the Visual Studio languages) sent out a survey asking questions about the state of what they called a "release candidate." The first question asked, "On a scale of 1-5, where 1 is 'ready to ship' and 5 is 'don't even think about it' how would you rank the ship ability of Visual Studio 6.0?" Visual Basic wouldn't compile my key component and wouldn't tell me why not, so I said "don't even think about it" and gave similar ratings to the other questions, plus a few choice written comments at the end. My feedback seemed to get somebody's attention because they called me to ask if I'd really meant to be so harsh. I did.

A week after I sent the bug, someone from Visual Basic support contacted me to ask for the source of my revised type library. I couldn't figure out why they wanted the type library source, but I sent it along. I heard no more from them for another 10 days when I finally sent them mail asking for an update. Finally, after 17 days stopped dead on all my development, I got an explanation.

How compatibility died a mysterious death, but was resurrected
It turned out the culprit was that same damn SYSTEM_INFO type that had caused the original type library incompatibility. VB6 looked at the SYSTEM_INFO type in my new type library and discovered that it was different than the SYSTEM_INFO type accessed in the VB5 version that I was using for compatibility (the .CMP file). This error was apparently so unusual that they didn't know how to report it correctly and just coughed up "unexpected error."

They told me that the bug was postponed, but that I could work around it by throwing away COM compatibility. I could create a new component with exactly the same name and the same public interface that would not be compatible with the original component. Just set "No compatibility" in the Component page of the Project Properties dialog. New GUIDS would be generated and everything would work fine. I tried it and it worked. But it also made a mockery of one of the most important COM concepts. If you can't maintain compatibility by keeping the public interface the same, you might as well just stick with old-style system DLLs and struggle to deal with version incompatibilities through the inadequate versioning APIs.

This bug obviously makes no sense at all. The SYSTEM_INFO type and the code that uses it are not part of the public interface. No UDT could be part of the public interface in version 5 when this interface was written. In fact, the whole point of the CSystem class was to wrap and conceal the crude GetSystemInfo API and the SYSTEM_INFO UDT. And of course there was also the separate bug of putting out a nonsensical error message instead of at least claiming that SYSTEM_INFO was incompatible. Both of these bugs remain in the released version of VB6, as owners of my book can confirm by trying to compile the VB5 version of my code with the VB6 type library supplied here.

By this time we were very late in the beta cycle and I knew there was no point in arguing. They weren't going to fix this bug no matter what and I had to decide what to do about it. My first idea was to just let VBCore be incompatible. Anyone foolish enough to have used VBCore in commercial projects would be hosed, but it wasn't my fault and I would be sure to point out whose fault it was.

Fortunately, once I knew the cause of the problem, the solution occurred to me. I could replace those few incompatible type library elements with equivalent Declares and UDTs. When you have a Declare in a source file and an equivalent entry in a type library, the Declare always wins. So I added the following private types and declares to the modules in which they were used.

GetSystemInfo Declare

Used by System.cls


Used by System.cls

GetObjectBitmap Declare (alias for GetObject)

Used by PicTool.cls and PalTool.Cls


Used by PicTool.cls and PalTool.Cls

That fixed the whole problem and I was able to compile a compatible version of VBCore. But I was on hold for three weeks before I finally got the background information required to figure this problem out.

By this time I was beginning to seriously question whether I really wanted to spend all summer working on a third edition of my book. Testing all my programs was supposed to have taken an afternoon, but here I was a month into the project without having accomplished anything other than fix bugs in the type library. The other problems I encountered weren't encouraging either.

How a component almost fell victim to the Dialog Box From Hell
Although relatively small, the SubTimer component is a critical element in my book and sample programs. For one thing, it's used by the much larger VBCore component. For another, I package it separately as a sample component and donate it to the world on my Web site. So I was concerned when VB6 refused to compile it without compatibility warnings.

SubTimer supports window subclassing and timing without controls. Both these operations can generate large numbers of events in a short time. It can be extremely difficult to debug these events if the code that supports them exists in the same component as the code that uses them. That's why I package the CTimer class, the ISubclass interface, and the GSubclass global module in SubTimer.DLL rather than in VBCore.DLL. This means that you should compile and register SubTimer before you compile VBCore.

Unfortunately, neither the SubTimer provided with the second edition of my book, nor the one originally provided on my Web site compiled with VB6. Instead I ended up in the compatibility dialog box with the following message:

'Interval' in the 'CTimer' class module has a procedure ID that differs from a similar declaration in the version-compatible component.

Below the error message, it showed the incompatibility:

Original definition:
 Property Get Interval() As Long
Current definition:
 Property Get Interval() As Long

Below radio buttons gave me the choice to either break compatibility or…nothing. The second choice to preserve compatibility was disabled. If I chose the only choice, the message said the result would be:

The 'CTimer' class module will no longer support client applications compiled against the version-compatible component. To avoid this incompatibility, edit the procedure ID to be compatible, or clear the version compatibility setting in Project Properties.

It turns out this was actually a diabolical word puzzle test. I failed it completely. The first key to find an eventual solution to the puzzle is hidden in the error text above. If you can see the solution, you're more clever or observant than I am. My accomplice, Keith Pleas, couldn't figure it out either.

Eventually I gave up and cheated. I bypassed the normal beta reporting procedure and contacted one of my old Visual Basic sources. This person passed the problem on to someone in the product support group, who examined a simplified version of my component and gave me the solution.

The problem originates in what VB calls the Procedure Properties dialog, but which my book designates the Dialog Box From Hell (DBFH). I always considered this dialog to be stupid and unprofessional, but harmless. I listed many of its interface crimes in a long diatribe in Chapter 3, "Default members," page 143-147. One of those crimes is requiring you to set a default property using an obscure element called a Procedure ID. Among other things, the dialog allows you to set the procedure ID of a class method to BackColor or hWnd. What would it mean to give such a setting? My book claims, "If you guessed it would have no effect whatsoever, you win a trip to Disneyland and honorary title of Visual Basic Dialog Box Designer for a Day."

Alas, I was wrong. It does have an effect.

For example, the effect of setting the Procedure ID of the Interval property of the CTimer class to Appearance is that the SubTimer component is incompatible and won't compile under VB6. I remember testing the Procedure ID combo box when I wrote the section about it. I wanted to verify that setting nonsensical procedure ID values would really have no effect, and at the time it didn't. Apparently during this test I set the ID of Interval to Appearance. I compiled the .CMP version of the component used in compatibility tests. Then sometime later during development I noticed that it didn't make sense for Interval to have the Appearance ID, so I changed the setting to None (which doesn't really mean none). VB5 didn't care about this difference. VB6 does. According to VB product support, this was a bug in VB5 that was fixed in VB6.

Well, maybe. But the real bug is that the DBFH even lets you set this value. Giving programmers a dialog like this is like giving a kid a handful of dried beans and saying, "Now whatever you do, don't put these in your ears."

One other point: assume I had noticed and understood the message that the procedure ID was creating the incompatibility. How would I have figured out how to fix it? I would have had to examine the SubTimer.cmp file with OleView or some similar tool. I could have determined that its Interval property had ID &HFFFFFDF8 (-520). I still would have had no idea that this ID corresponds to Appearance. To determine that, I would have had to experiment, setting various named values in the DBFH and checking the result in the class source file until I found that Appearance corresponds with -520 and then set the ID in the new version to Appearance. So why didn't I figure this out on my own?

You'll have to speak up, I have beans in my ears.

Why GDI API calls meet their match in AutoRedraw forms

I found that some of the graphic samples provided with my book failed due to a change in painting behavior in VB6. Everything worked when I drew with Visual Basic methods such as Line or PaintPicture, but failed when using API calls such as LineTo, BitBlt, and StretchBlt. I had to call the Refresh method to make the results of the API calls appear on the screen.

For example, here's a bit of code that draws Bezier curves (see Chapter 7, "Bezier curves", page 382):

Sub DrawBezier()
    DrawStyle = vbSolid
    PolyBezier hDC, apt(0), 4
    DrawStyle = vbDot
    MoveTo hDC, apt(0).x, apt(0).y
    LineTo hDC, apt(1).x, apt(1).y
    MoveTo hDC, apt(2).x, apt(2).y
    LineTo hDC, apt(3).x, apt(3).y
    ' This line required in VB6
      because of change in painting model
End Sub

This code calls PolyBezier once and LineTo twice (MoveTo doesn't count because it doesn't draw anything). The Refresh call wasn't needed in VB5, but is required in VB6. When I reported this to Microsoft, they noted that this problem only occurs with AutoRedraw set. Since that's the default and the appropriate setting for many small programs, the change could affect a lot of existing code.

Microsoft agreed that this is a problem and even has a Knowledge Base article based on my bug report. Unfortunately, the article incorrectly implies that the problem only applies to BitBlt and StretchBlt, which were the API calls I reported in my original bug report. They admit that it's a bug--something I'm not so sure of. Perhaps it was a bug fix. A programmer's first response is that any change that breaks existing code is a bug by definition. But if something was implemented wrong in the first place, does that mean it has to be implemented wrong forever?

Think about the way VB implements AutoRedraw. The surface of the form is stored as a bitmap, and thus doesn't have to be constantly repainted (as it does when AutoRedraw is False). When you draw onto the HDC of the form with API calls, whatever you draw has to be refreshed from the HDC to the bitmap. I don't know exactly how this is done, but my guess is that in VB5 the bitmap was automatically updated from the HDC every time an API function was called. In VB6, this is no longer the case. You have to call the Refresh method specifically to update the bitmap. This requirement could create a mess if you had a bunch of existing code that assumed VB was going to do it automatically. But if you were writing new code, it could potentially be a lot faster because you'd refresh just once after you finished making all your API calls rather than have all those refreshes done secretly behind your back. Imagine you were drawing ten thousand dots with SetPixel. It would be a lot faster to refresh after every 500 dots than to refresh separately for each dot.

I'm not saying that's what's going on here. The Visual Basic folks never gave an explanation in response to my bug report, and didn't respond to attempts to update the report. But I'd like to think this was a carefully planned tradeoff rather than just another stupid, careless bug.

How build bugs bedeviled but could not block sample compilation

Despite unexpected problems, I struggled on to build and test all my samples. But as I worked my way through the list, I was bedeviled by strange errors, nonreproducible bugs, and voodoo spells from unknown enemies. It's hard to put my finger on the problem, but here are some of the things that happened.

·  References were removed from projects in my project groups without my having removed them. Suddenly classes and functions would disappear for no discernable reason and I would have to study the reference dialog to figure out what had been changed behind my back.

·  The order of references in my projects changed without my having done anything to change them. In some cases the order made an unexpected difference, and I had to shut down VB and edit the .VBP file with a text editor to change the order of referenced components.

·  Sometimes references in my project stopped working, and I had to remove them in the References dialog and put them back in exactly the way they were before to get them to work again.

·  The system of switching between VBGs and VBPs described in Chapter 5, "An Imaginary Real Project," page 239-242, stopped working reliably. Sometimes when I switched to the project group, the compiled component would still be referenced instead of the .VBP, or vice versa.

·  Controls would suddenly disappear from a project with no action on my part. Or they might be turned into PictureBoxes.

·  Converting to the new common control files turned out to be a nightmare. I downloaded the ActiveX Control Upgrade Utility from the Visual Basic Web site, and at first glance it seemed to work fine. But after running successfully once, my projects starting failing with a message saying that my common controls weren't loaded. After hours of frustration, I discovered that VB had mysteriously added a reference to the Common Controls file in the VBP. If you study the VBP files, you'll see the controls have an Object statement and other components have a Reference statement. Now I had both statements for one file, and nothing worked until I removed the reference with a text editor.

·  At one point Edwina stopped recognizing constants defined by the RichTextBox control, which is used by my XEditor control. These constants are supposed to be passed through from RichTextBox to XEditor. I eventually solved this problem by putting a RichTextBox control on the Edwina form. After running successfully once, I removed the RichTextBox and everything worked as it did before.

·  A Make Project Group command for a project containing VBCore would fail with an error indicating the file couldn't be written. I assume this meant that some other program no longer running had left VBCore in memory. This problem went away if I closed and restarted Visual Basic. I sometimes had similar problems when renaming files with Save As. Saving with the new name would fail the first time, but work on the second try--even thought the name was the same.

These problems are simpler to describe than to experience. Some of the problems took hours, and even days to figure out. It took a lot of trial and error to get projects working again when they broke. It's hard to put your finger on, much less write reproducible bug reports, but the build system seems to be more fragile in VB6 than in VB5.

How one last bug stopped the final build
I finally managed to build all of my components and all of my sample programs except for one. Edwina the Editor failed to build no matter what I did. It turns out that Edwina was unique among my projects in that she used VBCore twice. Edwina herself used VBCore, and she also used the XEditor control, which used VBCore. This double usage was no problem in VB5, but seemed to confuse VB6 no end.

If both XEditor and Edwina have references to VBCore.vbp, Edwina fails to recognize global objects. The first method of a global object that the compiler encounters is the IsRTF function, which is where it fails with a "Sub or Function not defined" error. If you comment that line out, it just fails on the next line with a global method, of which there are many. I suspect that there's nothing special about global objects--it probably doesn't recognize any classes, but the compiler encounters global objects first because they are created automatically.

I struggled many hours with this problem, trying to find the cause. I reported it to Microsoft early in July and have been communicating with them up until publication time (December). They confirmed the bug, but did not figure out the cause or a fix. The tech support person I communicated with said that from his experiments, it appeared that the following conditions had to be true for the bug to appear:

  1. The project group must include an ActiveX DLL that has GlobalMultiUse classes.
  2. The group must have an .EXE with a reference to the GlobalMultiUse ActiveX DLL.
  3. The group must have an ActiveX control with a reference to the GlobalMultiUse ActiveX DLL.
  4. The ActiveX DLL Project Compatible server must be set to a version of the DLL built with VB5 and converted to VB6.

He suggested that if I rebuilt the Compatible server (VBCore.cmp) in VB6, my problems would go away. But I couldn't rebuild the original VBCore.cmp in VB6 because of the type library bugs and incompatibilities described earlier. I did try building a new Compatible server based on the new VBCore with the bug fixes and changes described in this article. But that didn't work either.

Finally, through trial and error I figured out a workaround. The problem would go away if I removed VBCore.vbp from Edwina.vbg. As long as both Edwina and XEditor referenced the compiled VBCore.dll instead of VBCore.vbp, everything worked fine. But of course that defeats the purpose of a program group. If I encountered a bug in VBCore while working on XEditor of Edwina, I couldn't debug it.

When all else fails, rethink the problem. Most of the ActiveX controls described in my book use standard modules and private classes rather than referencing VBCore. I used the Global Wizard described in Chapter 5, "The Global Wizard," page 231, to generate standard module versions of VBCore's global classes and private versions of its public classes. I ended up with some redundant code, but it seemed worth the price to make the controls independent of VBCore.

I didn't take this approach with XEditor because this control is more complex and uses more services from VBCore. But the more I thought about it, the more I liked the idea of eliminating the VBCore dependency. I won't say I changed my whole development strategy just to avoid a bug, but I can say that when I changed it, the problem went away.

Does this mean that you can never have two projects that reference another project in a program group? Maybe. Maybe not. Maybe sometimes. Product support claims the problem only occurs when moving from VB5 projects, but my experience seemed to contradict that theory. By the time I figured out my workaround, I was too beaten down and disgusted to go back and isolate the original problem.

After wrestling with these bugs on and off for several months, I decided to abandon the third edition of Hardcore Visual Basic. I still felt responsible for my own VB5 bugs, so I eventually resurrected the project for this online update. The update was supposed to be a smaller project, without a co-author and without some of the new material we had planned for the book. But software being what it is and me being who I am, the tiny online version grew into the monster you now see on your screen. Worst of all, I didn't really enjoy writing it.

Programming is supposed to be fun, but I can't say I've had much fun on this project.

The decline and fall of documentation
I can't end this discussion without a flame about a trend that makes me sick. Documentation of computer languages is falling to new lows. I'm not just talking about Visual Basic 6, although it provides many shocking illustrations.
Delphi 3 documentation was often incomplete and lacking in context, and I haven't been impressed with recent documentation on C++ or Java either. Windows API documentation for new functions is getting bad, and documentation of many of the new system interfaces is worse still.

It's not the same bad writer moving from department to department in a lone act of sabotage. No, it's an illegal conspiracy. I hereby call on the United States Justice Department to file an anti-trust action against language documenters for illegal collusion in a conspiracy to dumb down the level of technical discussion, thus making us vulnerable to attack by alien powers.

The incompetence of the writers for each language has its own special flavor. In Delphi they sometimes failed to document method parameters. I've seen plenty of Visual Basic parameters documented incompletely or incorrectly, but at least they feel obligated to say something, even if it's little more than repeating the name of the parameter. The new VB style is to describe the mechanics of a feature without giving the faintest clue of what the feature is for.

Pick any new language feature at random. Chances are when you look it up in the help reference you'll find one or more of the following problems.

·  Few Example buttons for new features. See Filter, InStrRev, Replace, etc.

·  Dumb or even idiotic examples when examples are given. See LoadPicture.

·  No context for understanding what a feature is for. A description of the syntax isn't going to help if you don't know what the feature is and can't see a reasonable example of how to use it. See Dictionary.

·  No topical reference. Want to see what file I/O or string manipulation statements are available to you? Tough luck. They expect you to go through the whole alphabetical list of statements and see which ones might be of use for your task. Comprehensive See Also's would help with this problem, but the ones in Visual Basic help are often incomplete. Visual Basic help used to have a topical reference section. At some point a Visual Basic documentation manager told a Visual Basic writer, "Your job today is to trash the Visual Basic documentation by removing the topical reference." Those individuals should be tried, convicted, and forced to work in the mines until they have dug enough salt to make full restitution for all our wasted time.

·  Inability to use common programming terms correctly. For example, nobody seems to know the difference between a class and an object.

·  Borrowing samples and documentation from VBScript without modifying them to fit Visual Basic. The examples for Dictionary and the FileSystemObject are atrocious in their use of Variant for all types and CreateObject when the class names are known. If you do what these samples show, your performance will suffer.

·  General ignorance and failure of writers to put themselves in the place of the user.

I wrote language manuals for many years before I wrote books, and I know how hard it is to write good ones. But I put in the extra work to do it, and so did many of my colleagues. The people writing manuals today are generally better educated in computer technology and have the benefit of our experiences, good and bad. They ought to be doing a better job.

As if these writer problems aren't enough, we also have to put up with a giant step backwards in help technology. If it ain't broke, don't fix it. There was nothing wrong with the VB5 help system, but there's plenty wrong with the MSDN viewer used in VB6:

·  It...starts...very...very...slowly.... First reference with F1 takes 6 to 9 seconds on my fast, memory-rich primary development machine. First reference with VB5 on a slower system with less memory takes 2 to 3 seconds. If you keep the help application running, subsequent references are fast on either system, but that's a big application to keep in memory all the time if you're not using it.

·  Too much stuff mixed in together. Even when you limit the documentation set to Visual Basic documentation, you never know when you're going to come across a topic about Visual Basic, VBScript, Dynamic HTML, Java, JScript, or something you've never heard of. For example, enter "event" in the Index tab and then try clicking on all the available topics. Want help with an error message or IDE dialog box? You get it mixed in with language help from the same monster application.

·  When you cut and paste from examples, the line breaks are often wrong. At first the behavior seemed random and unpredictable, but it turns out there is a pattern. If you paste the entire sample, everything comes out okay. If you copy paste a few lines from the sample, the lines will run together. I'm told this behavior is a bug in the HTML translation code shared by Internet Explorer and the MSDN viewer. It seems like a strange limitation for a language help viewer. All that help text is there to support samples, not vice versa

·  How could anybody even think of releasing a commercial product where the Page Down and Page Up keys don't work? I seem to get about a page and a half of text for every keyboard scroll. Fortunately, scrolling seems to work okay when you use scroll bars.

Microsoft is touting its move to HTML help as some sort of advantage for somebody, but if it can't make it work smoothly in its own products, why should we put it in ours?

I'll close this discussion by describing how I discovered one of the most shocking changes in VB6 help. Soon after I received the VB6 beta, I got to wondering whether the undocumented VarPtr, StrPtr, and ObjPtr functions had come out of the closet. Were they documented for VB6? I looked for VarPtr under the Index tab and didn't find it, but I thought perhaps it would be described, but not indexed. So I looked it up on the Search tab and found 16 references, eight of them from my book.

Wait a minute! How did my book become part of Visual Basic 6? I was shocked at first, but when I thought about it I could see how it happened. Soon after the book was published, Microsoft Press made a deal with MSDN to publish the entire text (but not the code from the CD) on MSDN CDs and on the MSDN Web site. I was a Microsoft Press employee at the time and thus Microsoft owned the text. Therefore, neither party to the deal felt any need to inform the author. I discovered it on the Internet by accident when doing a Web search. I was more than a little annoyed at not being consulted, but after I got over my pique I didn't disagree with the decision, although I wished they hadn't published my e-mail address in the online version. I did not feel obligated to support those individuals who hadn't purchased the book.

When Microsoft decided to use the MSDN viewer for VB help and combine MSDN content with VB documentation, my book came with the deal. You only get VB books and articles if you select the right Active Subset--such as Entire Collection. It's not normally in the Visual Basic Documentation subset, but you can improve that subset using the Define Subset command from the help viewer's View menu. I use a custom subset that includes VB docs, Win32 API docs, the Knowledge Base, and VB books, but does not include some of the irrelevant junk (VBScript and Dynamic HTML) in the original Visual Basic Documentation subset.

Next Page   Previous Page   Table of Contents