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 *
. . .
} SYSTEM_INFO;
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
. . .
} SYSTEM_INFO;
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
|
SYSTEM_INFO UDT
|
Used by
System.cls
|
GetObjectBitmap
Declare (alias for GetObject)
|
Used by
PicTool.cls and PalTool.Cls
|
BITMAP UDT
|
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
Refresh
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:
- The project group must
include an ActiveX DLL that has GlobalMultiUse classes.
- The group must have an
.EXE with a reference to the GlobalMultiUse ActiveX DLL.
- The group must have an
ActiveX control with a reference to the GlobalMultiUse ActiveX DLL.
- 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
|