Bruce McKinney's Hardcore Visual Basic

 

 

 

 

 

 

Hardcore Visual Basic (cont'd)

Bug Fixes, Corrections, and Additions
The update part of this document has a little bit of everything--corrections of typos, irrelevant rants on obscure subjects, bug fixes in code, stuff I forgot to put in the book, apologies for misunderstandings, tributes to hardcore programmers, inside information and misinformation. Some of it you may want to mark in the paper copy of your book. Some of it you'll just read and shake your head.

The update has to accommodate readers of the online versions who have no page numbers and readers of foreign translations who have different page numbers. Therefore I use section titles in addition to page numbers when I reference the book.

A few of the minor wording changes mentioned in the update are also being made in the latest printing of the book, which will be available sometime after this update is published. Future readers may see errors described here that do not actually occur in their copy of the book. These differences won't be significant, since the only mistakes that could be corrected in the book were minor wording errors that did not change page count.

How to get the sample programs
I've provided the sample programs on the Web site as separate zip files. I've broken these programs up into several different files so that you don't have to download everything at once:

File

Description

HardCore3.zip

Source for sample program and components plus compare files, image files, and the Windows API Type Library--everything you need to build the VB components and samples.

HardCore35.zip

Same as above, but for VB5.

WinTlb3.zip

Source files for the Windows API Type Library--download only if you're interested in Interface Description Language (IDL).

CppForVB.zip

Some C++ articles included on the book CD--download only if you're interested in C++ for Visual Basic.

WinTlbU.zip

Unicode version of the Windows API Type Library--slightly more efficient for programs that will run only on Windows NT.

ComponentD.zip

Debug versions of the built components.

ComponentR.zip

Release versions of the built components.

Exes.zip

Sample programs as EXE files.

ComponentD5.zip

Debug versions of the built VB5 components.

ComponentR5.zip

Release versions of the built VB5 components.

Exes5.zip

Sample programs as VB5 EXE files.

The Component and Exe files are for your convenience only. You could build the same files from the source. The only required file is HardCore3.zip or HardCore35.zip.

Installation for VB6

Here is my recommended method of installation:

  1. Create a directory called Hardcore3 on your C drive. Actually, you can probably call it anything you want and put it on any drive you want, but this is the location I used. For owners of the second edition, I don't recommend installing over the original Hardcore2 directory, although I haven't tried it.
  2. Download HardCore3.zip and any of the optional zip files to the directory created in step 1.
  3. Unzip the files with the options that support creating files in their original directories. The options may vary depending on what zip tool you use. For example, if you're using a recent version of PkZip, the command line is pkzip -ext -dir Hardcore3.zip. If you don't unzip into the correct directories, nothing will work. Also, use a recent zip tool that understands long file names. If you unzip to short file names, nothing will work.
  4. Run the RegisterForVB batch file. This file registers the Windows API type library and copies the Cards32.dll and PSAPI.dll files used by some programs to the appropriate locations.

At this point you can use any of the VBG files for sample programs. You cannot use most of the sample VBP files because they reference components that haven't been registered. It's a good idea to get all the component files so that you can run any sample EXE or use any sample VBP. You can get the components in three ways:

  1. Download and unzip a component file (ComponentD.zip or ComponentR.zip). Again, make sure you unzip from the Hardcore3 root directory using options to create the correct directories. Run the ToDebug.bat or ToRelease.bat files to register all components. I recommend ToDebug initially. You can run ToRelease when preparing programs that use my components for distribution. These batch files (and UnregisterAll.bat) assume that RegSvr32.exe is in your path. If it isn't, you can either move it to a path directory or edit the batch files to point to the correct location. (By the way, I apologize for the original ToDebug and ToRelease, which assumed RegSvr32.exe was in a directory that existed only on my hard disk, and also ignored OCX files despite a message to the contrary.)
  2. Run the batch file BuildAll.bat. This will build either the Debug or Release components and executables. Pass d or r to the batch file to specify Debug or Release. If you didn't install Visual Basic in the default location, you'll have to specify the location in a VBEXE environment variable before you can build the files. The batch file identifies syntax problems and gives instructions if you get the syntax wrong. You can give a second argument to the batch file to build a group of files (components, controls, or clients) or to build an individual program.
  3. You can build each component project separately from its VBP file or from the VBG of a program that uses it. Most of the programs only require VBCore.dll and SubTimer.dll. Eventually you'll want to build VisualCore.dll, Notify.exe, and the controls--ColorPicker, DropStack, Editor, ListBoxPlus, and PictureGlass. The SieveCli program uses additional controls and components that have no practical use outside the program.

Installation for VB5

Some readers may not choose to update to VB6 since it is a minor release that offers as many problems as benefits for the hardcore programmer. All my programs are created with and tested under VB6, but it is possible to make everything work under VB5. There are several differences that must be fixed:

  1. VBP files created with VB6 have some statements that aren't recognized by VB5.
  2. CLS files have some properties that aren't recognized by VB5.
  3. I have used a few functions that take advantage of VB6 features.
  4. VBP and FRM files from VB6 use the new common control files that weren't available in VB5.

Most of these difficulties can be removed programmatically by running the VB6ToVB5.EXE program. That's how I created the files in Hardcore35.zip. Originally I was going to provide only one version of the source files and tell VB5 users to run VB6ToVB5.EXE to convert them. Ultimately, this strategy fell apart. There were too many minor incompatibilities. Instead I used the conversion program to create the files myself, fixed various build problems, and put the files in separate zip files. The code in these two zip files is supposed to be identical (and I think it really is). The references, objects, and attributes in the VBP files, and the objects in the FRM files are the only differences.

Visual Basic should have a predefined compile-time constant for the version number like civilized development environments. It doesn't, so I added one. All my VBP files define iVBVer = x. You can see the entry in the Make tab of the Project Properties dialog for any of my projects. The x is 6 in the originals, but the VB6ToVB5 utility changes the version number to 5. My components have conditional statements that disable VB6-specific code. I also have a module, VB6Funcs.bas, containing implementation of VB6 functions for VB5.

Once you download the correct VB5 files, the installation procedure is the same as for VB6.

Directory organization
Keith Pleas, my would-be co-author, was horrified at the original organization of the directories from the second edition. When I looked at the 386 files in the original Hardcore2 directory, I couldn't argue. Yes, that's a lot of unrelated files to cram in one directory. I admit it was rude. I guess I had so many other things to worry about that I didn't stop to think how intimidating this organization might look to users.

I've tried to improve the way I organized the directories for this release. The new organization may not be enough change to satisfy Keith, but it is an improvement. I want to find a compromise between books that have every program in a separate directory and those (like my second edition) that throw everything together in one big mess. Here's the new organization:

Hardcore3

Sample programs

Components

Source for components

Controls

Source for controls

LocalModule

General purpose private classes and standard modules

Compare

Compatibility compare files

Release

Release versions of components and controls

Debug

Debug versions of components and controls

Exes

Executable sample programs

TlbSource

Source for the Windows API Type Library

CPP

C++ articles

Cpp4VB

Four articles in HTML format on writing C++ DLLs

Sieve

First VBPJ article on writing ATL servers

ShortCutSvr

Second VBPJ article on writing ATL servers

This new organization still leaves a lot of files in the root directory, but at least they're all for programs you can run.

It's not my fault
I don't suppose you want to hear my
excuses if the installation procedure described above fails on your machine. But here they are anyway. I'm trying to be compatible with three operating systems--Windows 95, Windows 98, and Windows NT. Each of these systems may have different combinations of service packs or fixes contributed by different versions of Internet Explorer. In addition I want to be compatible with VB5 and VB6, knowing that VB5 has several service pack fixes and suspecting from the instability of VB6 that it will soon have a service pack or update too. Furthermore, I'm trying to juggle builds from the IDE with VBGs or VBPs and builds from batch files with VBPs even though VB's build mechanism is unstable. Finally, since I decided not to write a Setup program in order to "save time," I have to do everything from the wretched DOS batch language.

Don't be too shocked if some of my projects don't build correctly on the first try. They worked on my machine, and they'll work on yours eventually if you tinker enough with the project files.

Chapter 1

Page 3. Near the end of the "While/Wend" section I claim that the Exit Loop statement allows you to escape a loop cleanly. Ha! Fooled you, didn't I? There is no Exit Loop statement, only an Exit Do. But I didn't fool Yong Wang, who was the first to notice my error.

Page 5. The "Rem" section complains about the stupid, but harmless Rem comment syntax that nobody (well, hardly anybody) uses anymore. But the real problem with Visual Basic comments is the lack of a block comment syntax. In the old language wars, some languages had block comments (C and Pascal) while others had line comments (Basic and FORTRAN). Well, the opposing languages figured out that both forms are useful and adopted line comments to go along with their block comments. Visual Basic keeps upholding the faith, unaware that the other side has moved on. Instead of adding a multi-line block comment syntax (perhaps Comment/End Comment), Visual Basic gives us toolbar buttons that comment and uncomment selected text.

I would have killed for those buttons in VB3, but they're the wrong idea now--although they fit the general pattern of adding wizards instead of fixing the language. If I want to comment out code, I put it in an #If 0 Then/End If block. Unfortunately, VB won't let you put plain text in a false conditional. It checks the syntax of statements even when it knows they won't be compiled. We need a block comment syntax for function headers and other blocks of descriptive comment text. Maybe then the Visual Basic wizards would use the new feature instead of writing single line comments that stretch farther east than even the widest monitor could show.

Page 16. I'd like to add one more fruitless plea to the flame against those of you using the Form1/Command1 variable naming convention (not to mention the Project1/Module1 file naming convention). Every time I try to unzip one of your demo files into my test directory, your Form1 overwrites the Form1 of the last thoughtless correspondent. I have to live with your style if I voluntarily downloaded your sample from a Web site, but my patience wears thin if you sent me this sample in hopes that I'll ignore your rudeness and help you fix a bug. Rule one when asking help from authors: don't violate that person's publicly stated pet peeves.

Never mind. The Command1 fight is a hopeless battle. So is my futile attempt to promote a sensible Hungarian for Visual Basic (see "Basic Hungarian" on the same page). I see a lot of code with different naming conventions, but I don't think I have ever received code from a reader who followed the Basic Hungarian proposed in my book. Instead, the sloppy perversion of Hungarian in the Visual Basic manuals seems to be unstoppable. Well, there's something to be said for standardization. Perhaps a bad standard is better than no standard at all.

Page 23. The dialog between a Delphite and a VBer in the "Efficient Code" section turned out to be prophetic. The publication of my article in Delphi Informant magazine (April, 1998) sparked a mini language war with approximately the same level of sophistication shown in my imaginary debate. Some Delphites posted a quote from the article (out of context) bragging that the "Author of Hardcore Visual Basic goes to Delphi."

Well, rumors of my defection to Delphi are greatly exaggerated. Here's a real quote from the article:

"There's no clear winner when you compare Automation and other COM tasks in VB and Delphi. VB makes it easier to create and use simple Automation objects. It does this by hiding the irrelevant details. But once you move to more complex Automation problems, you may find that those details aren't as irrelevant as they first seemed; you may want to see and control them. If you're clever enough, you can usually hack your way around VB limitations, but in Delphi you can usually solve the problems without arcane hacks."

That may not be entirely complimentary to Visual Basic, but it's a lot tamer than many of the criticisms in my book. Whatever your terms of comparison, I think Visual Basic programmers can be thankful for the existence of Delphi. We can even wish that Delphi were more of a force in the marketplace, so that Visual Basic designers would have to compete more seriously in the areas where Delphi has a technological lead. Delphi 4 is going up against VB6, and in the area of language enhancements, VB will be the loser by a wide margin--although I doubt the difference will show up as even the tiniest blip on a graph showing comparative sales volumes of the two products.

Delphi has one important advantage over VB. It's written in Delphi. VB isn't written in VB. In fact, some of the C++ developers who create it don't even know Basic. Back when I was a Microsoft employee, I remember explaining Basic syntax to a VB developer who was trying to fix a bug I had reported. That's not so unusual. I used to be a developer of FORTRAN even though I didn't know any FORTRAN when I started the job and was only marginally proficient when I finished. That's not the case with Delphi developers. If they want to add some feature to their IDE, they first add that feature to the language library. To borrow a phrase from developers of Microsoft Visual C++ (who do write C++ in C++), they "eat their own dog food." Remember when VB5 shipped with IE-style flat toolbars in the IDE but no way to create them in Visual Basic? You won't see that in Delphi.

For example, Delphi 4 supports the wheel introduced on the Microsoft Mouse several years ago. Visual Basic might get around to supporting it in the VB7 IDE and perhaps a MouseWheel event will appear in the language in VB8. Similarly, multiple-monitor support is touted as an important feature of Windows 98 and Windows 2000 (formerly Windows NT 5.0). Does Visual Basic support it with an array of Screen objects? No, but Delphi 4 has a language feature that enables it. The list goes on. Visual Basic still has the most limited runtime library of any major language. The language itself still appears to be based on Windows 3.1, ignoring most 32-bit operating system features not directly related to databases or Internet.

None of this changes my mind about Delphi. I admire many parts of it, but I still think the overall model is crude and old-fashioned. I prefer the VB programming model--which is why I feel so bitter that Visual Basic hasn't lived up to its potential. Frankly, I won't be using either of these languages for my future development work. I've looked at Eiffel and Java, but haven't made a final decision.

By the way, after my Delphi article was published, a Delphi program manager sent me mail asking if I would be interested in writing a white paper for Inprise (Borland at the time) comparing the two languages. My response: "Trust me. You don't really want to pay me to write a Delphi-VB competitive white paper. I have a reputation for biting the hand that feeds me." Visual Basic program managers might be disappointed that I didn't go over to Delphi and harass them for a while. Perhaps when designers of other languages such as Java and Eiffel hear that I'm looking for a new language, they'll be pointing at each other saying, "choose them."

Page 26. "Everybody wants efficient code, and starting with Visual Basic 5 all you had to do to get it was click a few buttons to turn on native code with maximum optimization." If you believe that myth, perhaps you also believe in the code fairy who helps all good little programmers ship their products on time as long as they use Option Explicit and put a comment in front of every statement. The last paragraph of "Compiled Code--Not a Panacea" states that the next version of VB had better have a full-featured compiled language with no major compromises. Alas, VB6 doesn't have even obvious fixes to compiler limitations.

One of the most obnoxious limitations is that the Advanced Optimizations are on a dialog that applies to the whole project even though most of these optimizations should be applied at module or even procedure level. They come from Microsoft's C++ compiler, which applies them at a module level. The only reason Visual Basic can't do the same is because no one wrote a user interface to enable it. This might have been an acceptable compromise for the first version with a compiler, but there's no excuse in the second version.

For example, consider my VBCore component and the Remove Array Bounds Checks optimization. This component has about 100 modules and most of them have no need to check array bounds. But the Vector classes described in "Vectors As Resizable Arrays," Chapter 4, page 179, specifically make use of error trapping to identify and handle out-of-bounds array accesses. I can't apply this optimization to those classes, and therefore I can't apply it to the many other modules where it would give a surprisingly large speed improvement. Several of the advanced optimizations specifically disable important features of Visual Basic. You never want to apply these optimizations indiscriminately to everything in a large project. But that's still the only way Visual Basic lets you apply them.

Of course it's not really Visual Basic that does the optimization. It's C2.EXE, the second pass of the C++ compiler. VB invokes this program with different options to produce different optimizations. You can see exactly how this process works and even change it using a technique I first saw described in Advanced Visual Basic 5, the Mandelbrot Set, 1997. The author, Peter Morris, renames the real C2.EXE to a different name and replaces it with his own C2.EXE, which writes all the command lines it gets from VB to a log file and then passes the same command line on to the real renamed C2.EXE. By studying the log file, you can see exactly what VB is doing when you set different optimizations. It turns out that VB generates a temporary intermediate file (probably the same kind generated by the first pass C1.EXE of the C++ compiler) and passes the name of that file to C2 for compilation.

Hardcore programmer Donald Moore took that process a step further. He inserted his own options in the command line. His C2 added the /FAcs option to generate an assembly language source file for each compilation. This technique is extremely cool for an old assembly hacker like myself. I have a jerry-rigged system that allows me to generate and examine assembly language files any time I need to know what VB is really doing. I didn't polish this system to the point where I feel comfortable passing it along with this update, but you probably want something different anyway.

It wouldn't take too much effort to create a real option system that bypasses VB's half-baked options dialog and allows you to apply optimizations at the module level. First you need an add-in with a dialog that allows you to select any module in the project and apply any optimization you want. This is a relatively simple user interface problem. This add-in should write its results to a file or the registry-someplace where the fake C2 can find them. The fake C2 must then parse the command line generated by VB and replace it with a doctored command line that gives the optimizations requested for the current module. This should be an interesting little weekend project for some hardcore programmer.

Page 29. That last bulleted item in "What You Don't Know Hurts You" asserts that "anything you do to time your code will itself take time, thus changing your results." I stand corrected. The Heisenburg Uncertainty Principle doesn't apply here except in its most philosophical interpretation. If you use my ProfileStart and ProfileStop procedures, you'll see no noticeable loss of accuracy due the act of measuring. This case is unfortunate because I really liked the second and third sentences of this item. Too bad they aren't true.

Page 29, 30. "Examining Code" describes one way of examining p-code. Although the suggested method works after a fashion, it's rude and inconvenient. The book suggests putting a DebugBreak statement just before the code you want to examine, and then running the program in the Visual Basic IDE. When you hit the breakpoint, you'll see a prompt that allows you to break into the debugger that has been assigned as the system debugger. One problem with this technique is that the messages you see on your way into the debugger can easily give the impression that your program crashed, although it hasn't. Also, it's likely you'll never come back to the Visual Basic IDE. In my experience, it usually terminates when you unload the system debugger.

A much better way to debug a program is to compile it to p-code and then load the resulting EXE directly into a debugger. For example, you could enter a command like the following from a DOS command line or from the Run menu command on the Start menu:

"C:\Obnoxiously long path with spaces\msdev.exe" myprog.exe

If you're using Microsoft Visual C++ as your debugger, you can also start the C++ IDE and specify your program in the executable for the debug session in the Debug tab of the Project settings dialog. The commands will vary slightly if you use a different debugger.

Page 31. The program described in "The Time It Application" has some new tests and fixes for old tests.

Page 33-34. "Short-Circuiting Logical Expression: A Timing Example" explains why Visual Basic can't short circuit logical expressions with the And and Or operators. But there's an easy fix for this problem that made its way into a Visual Basic spec several years ago, and then failed to make it into the product. All you need are new operators that do logical rather than bitwise evaluation. For example, the C++ language has & and | respectively as its bitwise AND and OR operators; it has && and | | as its logical AND and OR operators.

A logical operator bases its result on whether the entire value of an expression is True or False rather than on the combination of bits. This kind of operator can be safely short-circuited. If I remember correctly, the original proposal was to add new Visual Basic operators called AAnd and OOr as logical operators. I'm not too fond of those names, but I do like the concept and the names don't matter.

While they're adding operators, the Visual Basic designers ought to add shift operators (Shl and Shr). Surely these operators would be more useful than VB's obscure Eqv and Imp operators, which I have never seen used in a real program.

Page 34. All the timing numbers in Performance sidebars should be taken with a grain of salt. The measurements were done with VB5 on a 90 MHz Pentium, which would now be obsolete in many shops. On the other hand, I don't think there's anything in VB6 that would significantly change the relative percentages.

Page 40-42. In "Other Debug Messages," I say that I don't implement event logging in my custom assert and message system in the Debug module. Well, now I do. I could write a better and more reliable logging system than VB's except for one problem--the Visual Basic library provides no means of committing file writes. Most runtime libraries have a commit function that forces a write of any data cached in buffers. Visual Basic has zip. You used to be able to fake it by getting the file handle from FileAttr and passing it to the Windows FlushFileBuffers function, but the designers broke that late in the VB4 beta cycle (breaking some of my code in the process). Since the VB file I/O system is written in C++, I'm sure it would take somebody on the library team at least five minutes to write a VB wrapper for the C++ _commit function.

In the meantime, if you're debugging a crashing program (the kind you most need to debug with a log system), you should flush all your log writes as soon as you make them. Otherwise, if you crash, handles won't be closed, buffers won't be flushed, and your data won't be written to the files. But there is no way to flush the buffers, and that's where the VB log system comes in. The system is not very good, but sometimes it's all you have (even though you only have it in compiled EXE programs).

So I enhanced BugMessage so that it would initialize logging on first call and write messages to either the NT event log or to a file, depending on the value assigned to the afDebug constant in the Conditional Compilation field of the Make tab in the Project Properties dialog. I'm not going to show the ugly enhancements to BugMessage here, but I will tell the values you need to Or into the afDebug constant:

afLogfile = 1

 

Value

Description

&H1

Write to my log file (appname.dbg)

&H2

Write to message boxes

&H4

Write to the debug window

&H8

Write to their log file (appname.log)

&H10

Write to the NT event log

&H18

Write to event log on WinNT or to log file on Win95

I used BugMessage with the NT event log to debug very messy problems in the Notify server as described in notes to Chapter 11.

Page 31-44. The debug and profile system described in the "Timing in Basic" and "Assert Yourself" section has a risk that I can't really explain, but that I must warn you about. When Paul Thomas reported this problem, I have to admit I thought he might be a little crazy or at the least confused. But when I got the same error in similar circumstances, I began to suspect that VB was the one suffering delusions.

Paul reported that when he ran the Notify server (Chapter 11, page 640) for a long time (about 20 minutes), he got an error claiming that an imprecise value had been assigned to a floating point variable. This error is a little weird, considering that Notify doesn't use any floating point variables. In fact, the closest it comes to a floating point variable is a fixed-point Currency variable called secFrequency in the Debug.bas module. This variable is used by the profiling procedures, none of which are used in the Notify server code. The Notify server did, however, use the BugAssert procedure, and thus had to include Debug.bas in the project.

Paul found the one currency value in the project and changed its type from Currency to Double. The bug disappeared. He sent me mail describing the problem. I replied that his eyes were deceiving him and that he could not have gotten that message. If he wanted to be sure there were no floating- or fixed-point variables in the program, he could remove the BugAssert statements from the Notify code and remove Debug.bas from the project. Paul replied that with the type changed from Currency to Double, he now got the error after running Notify for four to five hours. He then removed Debug.bas and all the BugAsserts and the bug went away completely--even after runs of 17 hours.

Well, this is not the kind of bug any programmer likes to think about. What was I going to do about it? Run Notify for four hours until the bug appeared for me? What would the error message tell me that I hadn't already heard and disbelieved? So I did what any self-respecting programmer would do. I forgot all about it. Or at least I tried. Then several months later I left the VB6 version of Notify running overnight for a completely different reason. When I returned to my computer, there it was--the floating-point error I had doubted.

I recently made some minor changes to Debug.bas, and now the bug seems to have gone away. I ran Notify overnight without any bad effects. I can't say I fixed the bug because I don't understand what caused it.

I don't know the moral to this story. Perhaps the error message was a judgment on me for the filthy hack to fake 64-bit integers with Currency as described in "Large Integers and Currency," Chapter 2, page 62. Perhaps it's just a general punishment for questioning the design of Visual Basic. Perhaps it only happens to Aquarians on days divisible by nine that fall on Thursday. All I can suggest is, if you get it, remove any calls to my debugging procedures and delete Debug.bas.

Page 39. Now it can be told. The sidebar "Conditional Compilation for Blockheads" was censored. The original title was "Conditional Compilation for Dummies," but my editor feared this title might violate some sort of copyright or trademark rights of IDG Books Worldwide, publisher of the "Dummies" series. I don't think so. And even if it did, IDG would be more likely to thank me than prosecute me.

Chapter 2

Page 46. "Never Write Another Damn Declare Statement" praises type libraries, but based on mail from readers, some of you aren't buying it. Apparently some programmers just like writing Declare statements. Maybe you're a control freak who has to write your own, and since I don't explain how to write type libraries, you're stuck with Declare statements. Maybe you're a double hardcore programmer who skips all the wimpy stuff in the first few chapters and goes directly the hardest techniques you can find in the final chapter, and then sends me mail when you can't figure out where I hid the Declare statements. Maybe you don't believe that type library entries add no overhead to your programs except for the entries you use. Or perhaps you believe, despite assurances to the contrary, that you have to ship the type library to clients. For whatever reason, many of you insist on using Declares, although no one has ever given me a valid reason other than the incompleteness of my type library.

Well, that's okay with me. Use Declare statements if you like for whatever reasons you have, but don't ask me to write them for you. Almost all my samples depend on the Windows API Type Library, and if you'd rather use my code with Declare statements, you'll have to write them yourself. And don't complain to me if the weird declare choices in Win32API.txt don't match the ones in my type library.

By the way, the TimeIt application has a test that proves that type library entries are faster than equivalent Declare statements. This is not, however, a reason not to use Declares. I had to call GetVersion 300,000 times to get a measurable difference. That kind of performance enhancement isn't likely to matter in a real application.

One more bit of type library trivia: I recently discovered that the Windows API Type Library from the second edition is distributed as part of Windows 98. It's not installed by default, but if you check the disc you'll find it as part of the Microsoft Windows 98 Resource Kit Sampler. If you install the sampler, it will be copied to your system directory. As far as I can tell, the documentation provided does not give any clue of what the library is or what it can do. Ordinary Windows 98 users aren't likely to discover the library. Why and how it got there are beyond me. I did send the complete library with source over to the Visual Basic department before I left Microsoft. They could have updated it and provided it with VB6, but apparently they turned it loose to wander the halls until it landed on the desk of someone preparing the resource kit.

Page 50. Visual Basic now comes with a new and improved version of the WIN32API.TXT file described in "Rolling Your Own." Alas, many of the bugs and weird design decisions are unchanged. For example, WriteProfileString has been updated with a sensible style, but WritePrivateProfileString still has a VB3-style declaration. I recommend that you never use anything from this file without inspecting it carefully to make sure it makes sense.

Page 62. The technique described in "Large Integers and Currency" is somewhat risky, as described earlier. This whole stupid hack should be unnecessary. Visual Basic should have added a 64-bit integer type by now. Visual Basic is built with Microsoft's C++ compiler, which has a native 64-bit integer type. It should be easy to map this internal type to a new type called VeryLong or whatever. As 64-bit integers become more important for database work and for measuring disk sizes that now commonly exceed four gigabytes, most languages (including Delphi 4) already have this type. Visual Basic should join them.

Page 63. The text in "Large Integers and Currency" speaks of a type library constant called CURRENCY-MULTIPLIER. No such constant exists. The correct name is CURRENCY_MULTIPLIER (with an underscore rather than a hyphen).

Page 70. The note in "Typeless Variables" refers you to page 240 for a sidebar called "Better Basic Through Subclassing." The sidebar doesn't actually start on page 240 and I'm not going to tell you what page it does start on because I now regret writing it. Renaming built-in functions with your own versions is so questionable that I suggest you pretend this note and that sidebar don't exist.

Page 71. A cross-reference in the "Typeless Variables" section refers you to Chapter 6 for more information about bitmap handles. The information is actually in Chapter 7.

Page 77. The last paragraph of the "Null and Empty" sidebar claims that you can save space in your EXE program by using string constants rather than literal strings. I tested this claim when I wrote it, but I think that was back in the VB4 era. When I did a similar test with VB6, I could not detect any difference. One reader actually reported a size increase from using string constants on a very large program. There may be good reasons to use constants rather than strings in code, but EXE size is apparently no longer one of them. A smart compiler should be able to detect and consolidate repeated string literals, and apparently VB now does this.

Page 78. The "Wrapping String Functions" section says that optional arguments initialized to vbNullString don't work. The example shown is:

Optional Class As String = vbNullString

Fortunately this VB5 bug has been fixed in VB6.

Pages 78, 79. The VBGetWindowText example in "Wrap It Up" illustrates a bad coding habit. The error also occurs earlier in a code fragment in "Getting Strings from the Windows API." The first problem with VBGetWindowText is that the function was renamed after I wrote this section and now appears in the WinTool module as WindowTextFromWnd. But that's not the real problem.

I discovered this mistake in two stages. First hardcore programmer Mauro Puddinu reported an overflow in WindowTextFromWnd. When I checked the code, I immediately saw what I thought was the source of the problem:

Function WindowTextFromWnd(ByVal hWnd As Long) As String
    Dim c As Integer, s As String
    c = GetWindowTextLength(hWnd)
    If c <= 0 Then Exit Function
    s = String$(c, 0)
    c = GetWindowText(hWnd, s, c + 1)
    WindowTextFromWnd = s
End Function

What, I wondered, was that Integer doing there? It's almost always wrong to use Integer rather than Long when dealing with the Windows API. I'll go further. It's almost always wrong to use Integer in 32-bit programming. There are exceptions. Some parameters in VB's event procedures are Integer, so you should use matching Integer variables to store copies. You can also use Integer in UDTs to save storage space. But the default size of an integer (note the lowercase) in 32-bit operating systems is Long, and you should use that size unless you have a good reason to do otherwise. I suspect that in this case my only reason for using Integer was negligence. I just left the code unchanged from the first edition of the book. Fortunately I didn't repeat the mistake in the "Life as a string" figure on page 80.

I was so excited about finding this error that I didn't notice the other weird thing about this function. Why is it testing for negative lengths and exiting early when it finds them? I eventually found out the hard way. It seems that some versions of Windows sometimes return bogus numbers from the GetWindowsTextLength function. When this happens, these numbers tend to be very large.

Apparently when I first encountered the problem (back when winhexadesaurus roamed the earth), the number must have been greater than 33,767 and less than 65,667--which would wrap into a negative Integer. When Mauro got the error, it was greater than 65,535 and caused an Integer overflow. He may have been hitting the same window I encountered where the GetWindowsTextLength function claimed it had a string 285,212,672 characters long. That's one heck of a long string. In fact, it's so long that when you try to create a string of that length with the String$ function, Visual Basic gives up with an "out of string space" error.

Unfortunately, this error occurred when I was executing the recursive IterateChildWindows function in WinWatch. It can be very difficult to track down memory bugs in recursive functions. You can't just set a breakpoint, because you may have to call the function thousands of times before you hit the problem. At first I blamed VB6 for what I assumed was a memory change that would cause any deep recursion to fail. Fortunately, I encountered the bug again in a situation that was easier to debug (the TEnum.vbg project). Based on my experiments, Windows seems to give completely wrong results in these situations, so it's best to bail out rather than truncating the string. Here's the fixed version of the code:

Function WindowTextFromWnd(ByVal hWnd As Long) As String
    Dim c As Long, s As String
    c = GetWindowTextLength(hWnd)
    ' Some windows return huge length--ignore 0 <= c < 4096
    If c And &HFFFFF000 Then Exit Function
    s = String$(c, 0)
    c = GetWindowText(hWnd, s, c + 1)
    WindowTextFromWnd = s
End Function

I'm setting a relatively high maximum to accommodate windows that play tricks with the window text, using it as the entire contents of the window rather than just the caption.

Back to the original point: it's not a good idea to use Integer variables for variables that Windows will fill with Long values. Unfortunately, I found several Integer variables in WinTool and other modules when I searched the rest of VBCore. They're fixed now.

Page 83. The "Unicode Versus Basic" section has a bulleted list telling how various operating systems and programs handle Unicode. Time has passed and it is now necessary to add two more items to this list:

  • Windows 98. Windows 98 handles Unicode exactly the same as Windows 95, warts and all. Even the serious Unicode limitations of Windows 95 that I'll flame about in Appendix A are unchanged. The only difference is that a certain controversial program that used to stand on its own has become "part of the operating system," and that program, with all its DLLs, handles Unicode better than the rest of the system.
  • Microsoft Internet Explorer. IE was mostly developed by the same team that did the new Windows shell for Windows 95, which was later retrofitted to Windows NT 4.0. Somewhere along the line they must have learned Unicode, because much of the underlying functionality of IE (in both its standalone and Windows 98 incarnations) exposes both ANSI and Unicode entry points. Hardcore programmers who want to experiment with calling functions in IE DLLs (such as SHLWAPI.DLL) can take advantage of the more efficient Unicode entry points even under Windows 95 or 98. We'll be looking at SHLWAPI in detail in Appendix B.

By the way, the introduction of Windows 98 raises some vexing problems for writers. How do you refer to the two operating systems that are essentially the same for most programmers? It gets cumbersome to say that Windows 95 and Windows 98 behave one way, but Windows NT behaves another. I used to abbreviate these systems as Win95 and WinNT. Can I now say Win95/98? Or should I say Win9x? Is this terminology going to crash in the year 2000? Maybe it's more Y2K-compliant to say Winxx. I'm told that Windows 98 is the last of its line, and that all future versions of Windows will use the NT codebase. If that's true, let's hope that the dumb idea of naming operating systems after years will die and be buried in the same grave with the last fragments of MS-DOS legacy code.

(I wrote the preceeding paragraph before I learned that Microsoft has decided to rename their operating system in order to associate it in the public mind with the end of the world. At first I thought the name Windows 2000 must be a hoax perpetrated by the National Enquirer, but truth is stranger than fiction.)

Page 84. The first code fragment in the "What Is Unicode?" section can be improved. Try this code in the Immediate pane or in a program:

For i = 0 To 255
    Debug.Print i & vbTab & Chr$(i) & vbTab & Hex$(AscW(Chr$(i)))
Next

I claim that the results will show non-zero high bytes in characters 145-156 and character 159. Well, that is a common result, but I've gotten other results on different machines. Try it for yourself if you really want to know.

Page 87, 88. The "Unicode API Function" section shows you how to write functions that register a type library. These functions were used in the RegTlb program provided with my book. I don't want to mess with that code because it illustrates several alternative ways of handling Unicode strings in API functions. But if you look at RegTlb as a utility rather than a teaching tool, you'll find a much easier way to register type libraries, which I use in the new RegTlb program. The old code shown in the book is moved to a separate program called RegTlbOld. Study it as an example, but use the new RegTlb to register type libraries.

The new method uses the TypeLib Information component (TLBINF32.DLL) provided with Visual Basic. This component is the one that makes Visual Basic's object browser work, and that you can use to write your own object browsing tools--if you can figure out how it works without documentation. I mention it in Chapter 10, "What's next," page 588. I wanted to use it to create a class inheritance wizard, but unfortunately never found time to figure out the details. The one downside of using this DLL in your programs is that you must ship it to customers.

Fortunately, hardcore programmer Martin Naughton pointed out that you can easily use TLBINF32 to register and unregister type libraries. Unregistering type libraries is a messy business. You can do it with an API, but the API requires the GUID rather than the file name of the server to be unregistered, and you have to do messy hacking (which I never did) to get the GUID from the file name. I was relieved to find that TLBINF32 already handles it for you. This code registers type libraries:

Function RegTypeLib(sLib As String) As String
    Dim mgr As TLIApplication
    On Error GoTo FailRegTypeLib
    Set mgr = New TLIApplication
    mgr.TypeLibInfoFromFile(sLib).Register
    Exit Function
FailRegTypeLib:
    ' Pass error message back to caller
    RegTypeLib = Err.Description
End Function

Unregister is the same except that it uses the Unregister method.

Page 88. The RegTypeLib sample near the end of the "Unicode API Functions" section has a minor change. The tlb variable in the ordTypeLib conditional block now has type IVBTypeLib rather than ITypeLib. This is due to a change in the Windows API Type Library in order to be consistent with naming conventions described in Chapter 3.

Page 92. The description of how VarPtr works on strings in the last paragraph of the "Bring Your Hatchet" section is completely wrong. It states incorrectly that VarPtr returns a pointer to the temporary ANSI string created when calling API functions. In fact, VarPtr returns the address of a pointer to the string data (a BSTR). I stand corrected due to Francesco Balena's article "Play VB's Strings" in the April, 1998, issue of Visual Basic Programmer's Journal. I hereby award Balena the Joe Hacker Guru award for this article and for other hardcore discoveries he has publicized recently.

Once you understand what VarPtr really returns, you can come up with techniques to swap strings by swapping their addresses. The pointers and addresses in the following SwapStrings procedure were so confusing to me that I had to draw pictures in the comments to figure out what I was doing:

Sub SwapStrings(s1 As String, s2 As String)
    Dim pTmp As Long, pAddr1 As Long, pAddr2 As Long
    ' Save pointer to first string in variable
    pTmp = StrPtr(s1)
    ' 1000+---------+   <== VarPtr(s1) = 1000
    '     |  2000   | s1    StrPtr(s1) = 2000 ==>  "String 1"
    ' 1004+---------+   <== VarPtr(s2) = 1004
    '     |  3000   | s2    StrPtr(s2) = 3000 ==>  "String 2"
    ' 1008+---------+
    '     |  2000   | pTmp
    '     +---------+
 
    ' Copy the pointer at second address to first address
      (both ByVal)
    CopyMemory ByVal VarPtr(s1), ByVal VarPtr(s2), 4
    ' 1000+---------+
    '     |  3000   | s1
    ' 1004+---------+
    '     |  3000   | s2
    '     +---------+