Advanced Visual Basic 6: Power Techniques for Everyday Programs Readme
This is the fourth major update to the PowerVB tools since the book shipped. The first upgrade added Post-Build editing support, the second added full buildable source code and binary compatibility editing. The third drop (October 2001) added support for all tools on the Windows 95/98/Me operating systems, and a full complementary set of add-ins designed to run with VB5. The third drop added a handful of new tools (PowerVB Automatic OCA Sweeper, PowerVB Console Application Add-In, PowerVB Compatibility Extension Checker, and Debug Object for AddressOf Hook Procedures), as well as added template-level (meaning they don't do much) samples related to creating threads in Dlls using the various threading architectures. You may still have occasional problems with saving an edited library with the 'PowerVB Type Library Editor' add-in because VB doesn't like to let go of the typelib (even after a full project reload).
Upgrade Installation
Upgrading the CD software to the current drop is easy to do. Download UpdatesMay2002.zip, UpdatesMay2002WithVB5Binaries, or UpdatesMay2002SourceOnly.zip and
expand these files in the PowerVB directory (C:\Program Files\PowerVB by default). For the full download, run RegisterMay2002.bat. If you
only downloaded source files, run BuildMay2002.bat, which calls Build.bat in the Tools\Src directory. To build the VB5 versions of the add-ins, run
BuildMay2002ForVB5.bat immediately after BuildMay2002.bat (BuildMay2002ForVB5.bat calls Tools\Src\BuildForVB5.bat). If you previously downloaded the
source for the October 2001 updates, you can use UpdatesMay2002IncrementalSource.zip in place of UpdatesMay2002SourceOnly.zip. If you encounter problems with building locally,
please let me know. UpdatesMay2002Binaries.zip and UpdatesMay2002BinariesForVB5.zip are also available on the web site if you encounter build problems.
Using the tools with VB5 requires a normal setup install from the CD, plus the normal updates, plus the *VB5* add-ins. You can register these add-ins
for VB5 usage with PowerVB\Tools\SetupVB5.exe. Note that the VB5 add-ins will not load if you attempt to use them in VB6. If you build the VB5 add-ins
on a machine without VB5 installed, I would recommend that you copy and register VB5ext.olb on the build machine before building. The VB5 add-ins are
written in VB6 to target VB5. The effort of a wholesale port to VB5, while possible, was not worth the effort.
Return to top
New Samples for May 2002
There are four new samples in this drop:
Known issues for the core type library editor
All of the add-ins share a core type-library editing engine, contained in TLBEditor.ocx, which is responsible for accepting valid edits, and recording, serializing, and playing back those edits as well. The editor is designed to run off context menus, which will remain essentially the same for all editor containers. You can edit very quickly if you use the context menu key on an extended keyboard, and F2 to start label edits in the treeview. There are a handful of features that have not been completed as of this drop:
Fixed 1) There are no provisions for CustomData editing. This will eventually be available on the context menu for each item (CustomData is finer grained than help editing, which is per member, so it would require one tab per invoke kind, which is too much for the main UI).
2) Copy/Paste of members is supported, but does not appear on the context menus. Duplication of entire types is supported for external types, but not internal. Parameter Copy/Paste is also not implemented.
Fixed 3) You can put the uidefault and defaultbind flags on multiple elements. oleaut doesn't check this, but I will do so in the future to automically turn off old values.
4) You may get an obscure error message that says 'RedirectType replaced a type with Nothing, but the type is being used elsewhere in the library.' The bottom line is that the type is in use and you can't delete it, but it would be nicer if I told you what element was using it.
Partially fixed by enabling format options for for enum and constant editing 5) The constant editing will get a small shadow window that will show you resolved escape sequences, hex constant values, etc. You can type in hex with an &H prefix, but you can't see the text back in hex with the current implementaton.
Fixed with External Types dialog 6) Some of the most powerful editing features in the editor allow you to internalize and/or redirect external types. Unfortunately, the UI for these is obscure. The redirection commands for internal types are on the normal context menus, but external types don't have nodes in the tree, so they are less accessible. To redirect or internalize an external type, simply right click on the Type display to get the context menu. The context menu key with the focus on the type field provides keyboard access for this feature. You can distinguish external types by the LibName. prefix before the actual type.
Return to top
Post-Build Issues
One of the most sensitive operations with post-build editing is updating the exe/dll/ocx file itself to make it properly load records
with non-GUID_NULL guids that have been internalized into the typelib. This build adds support for records that are redirected as well
as those that are internalized. However, there are limitations to what I can do. VB stores a single LIBID for all records from a given
library, not one per record. This means that all types from a given library must be redirected to the same library. You can't move a
partial library, or split one library into two. If you hit this, you will see a warning message indicating the type that is causing
the problem. If the type mentioned is not in the library, you can always add an alias type of the given type, redirect or internalize
the type as needed, then delete the alias. The guid tracker will remember the mapping. This lets you redirect records to external libraries
that are not referenced directly in your main library. The registry requirements for record guids are discussed in chapter 15.
You should not try to edit the GUID, name, or base of a VB-generated type. I don't block this, so making sensible edits only is left
up to you. GUID changes to VB-implemented types should be made in the compatibility file, not during a post-build step.
As you step through the post-build playback mechanism, you now have a 'Select Target' command available on the context menu or with a double-click
that will show you the object you are about to make the change to. You can't delete edits before the current statement, so selecting the target can
help you avoid needing to restart your playback script to delete a command you didn't want to execute. To backout a line once you've passed it,
you need to run the Restart command, and use a breakpoint or the Run To Cursor command to stop on the command in question.
Return to top
Post-Build Help Modification
The Post-Build editor lets you specify a Help Modifier class, which you program yourself. Changes made via this class do not show up in
the editor as they are made during the Save phase. This lets you use a database of you own choosing to put help information in your projects,
and lets you use a satellite Dll for localized help information if you like. The interface uses TLI (TypeLib Information) objects. You can
get a help file for these objects from http://msdn.microsoft.com/vbasic/downloads/addon.asp.
A sample implementation is shown below. Note that the only unusual thing here is that you will get two requests for Type help because some help information must
be specified before a type is added, and some after. Generally, you will use the library's HelpStringDll settings (alternate HelpFile settings don't work on types),
so you only need to pay attention to the hfNonFileFields FieldMask values. The IModifyHelpData interface is defined in TLBEditor.ocx, which you'll have to add with
Browse/References to use it without the controls.
Implements IModifyHelpData
Note that if you specify a HelpStringDll for member fields, you must also specify a HelpStringDll at the library level. If you don't have
a HelpStringDll for the library, you should specify a non-zero length string. Ole Automation will handle multiple HelpStringDll values, but
only if one is specified at the library level. If you're using a HelpStringDll, you might see different strings depending on the type library
browser unless you check 'Help Options/Don't save HelpString if HelpStringDll is set' in the PostBuild type library editor's context menu. There
are two standard ways to read documentation strings. With the first method (ITypeInfo::GetDocumentation), the string in the typelib wins out
over the HelpStringDll. With the other (ITypeInfo2::GetDocumentation2), the string in the typelib itself is used only if the HelpStringDll can't
be found. TLBEditor uses the former method as it provides the most information. If you clear the HelpString field in IModifyHelpData callbacks, then
you will achieve the same affect as using the Help Options setting. Code for a sample Dll is shown in the new PowerVB\Samples\ModifyHelpData\SatelliteDllFramework
directory.
Private Sub IModifyHelpData_SetLibraryHelpData(ByVal Library As TLI.TypeLibInfo, HelpData As TLBEditor.HelpData)
HelpData.HelpString = "Help for library " & Library.Name
End Sub
Private Sub IModifyHelpData_SetMemberHelpData(ByVal TypeInfo As TLI.TypeInfo, ByVal Member As TLI.MemberInfo, HelpData As TLBEditor.HelpData)
HelpData.HelpString = "Help for member " & TypeInfo.Name & "." & Member.Name
End Sub
Private Sub IModifyHelpData_SetTypeHelpData(ByVal TypeInfo As TLI.TypeInfo, ByVal FieldsMask As TLBEditor.HelpFields, HelpData As TLBEditor.HelpData)
If FieldsMask And hfNonFileFields Then
HelpData.HelpString = "Help for type " & TypeInfo.Name
End If
End Sub
This drop contains a PostBuild enhancement that lets you implement IModifyHelpData in a loaded add-in instead of in a separate project. To avoid a
dependency on the add-in loading order, PostBuild looks for a registered class object as described in Chapter 7. A sample addin project
demonstrating how to integrate your own add-in with PostBuild is shown in PowerVB\Samples\ModifyHelpData\AddInFramework directory. This gives
you a good starting point for integrating your company's own help and localization tools into the build process.
If you want to make post-build changes during an automated build process, then you need to set the /out option on the VB6 command line build in
addition to /make. VB6 won't let you write to its log file, so PostBuild writes to one called PostBuild.[file name specified after /out]. This
file will only be created in failure cases, so its existence (or lack thereof) can be used to determine if the PostBuild operations succeeded.
Return to top
Editing Binary-Compatibility Files
The binary compatibility editor is new for this drop. There are four commands provided for your enjoyment. Executing any of these commands to completion creates a new
binary compatibility file instead of editing the existing one. You can decide yourself which files you need to keep and which are no longer
important. I would suggest keeping your own history of why the changes were made to each successive compatibility file.
The first command, Edit Type Library..., will open the type library editor in a very limited mode and let you make edits as you see fit.
Many of the abilities you'll see other places, such as changing help strings, setting the base class, and setting the alignment on a record
type, are not available here. The attributes you are allowed to change is also very limited, and you are not allowed to add types because VB's
type order is best left to VB. You can add and delete members and parameters, however. You can also rename and reguid items, although renames
are somewhat limited (I'm considering adding more robust name checking in the future to check for VB keywords, etc). The vtable order dialog is
enabled, but you should not delete any leading holes in the vtable for anything but .cls classes. The compatibility editor also enforces VB's
non-standard MemberID number when adding new types (VB starts with &H60030000 for functions and &H680030000 for properties instead of the
standard &H60020000 that Ole Automation expects for IDispatch-derived interfaces).
Although it all happens behind the scenes, one of the main functions of the compatibility editor is to synchronize GUID and alias changes with
the _IID_* and _SRCIID_* resources also stored in the compatibility file. If you reguid or delete an interface or an alias, the corresponding
change is made automatically in the other resources. Similarly, if you rename an interface, then the resource name will also change. It is
important to note that interface version numbers are always synchronized with their coclass when you save your work, and aliases are renamed
and renumbered to match any renaming of the original interface. If an interface or dispinterface is associated with a coclass, you can only
rename the coclass. The implied interfaces will be renamed automatically.
The second command, Edit Event Guids..., is provided to modify the _SRCIID_* resources directly. As with interfaces, VB supports multiple
guids for an event interface, but these secondary event guids do not need to be registered, and, hence, are not listed in the typelib. The form
allows you to insert, delete, and modify guids for all but the entry corresponding to the event interface currently listed in the typelib.
A powerful side effect of control over the secondary event interface guids is the ability to implement any event set in a public
type. If you add a known guid to the event guids and match the MemberID and function signatures for the events (matching the name is optional,
but recommended), then your object will support a WithEvents request for that event set. For example, if you have two public classes in a dll
with matching event sets and your second class implements the first, then adding the primary event guid for the first class to the implementing
class's event guids will enable you to use the same WithEvents variable against both classes. Of course, there is no Implements-like compiler
support for this activity, so you must be disciplined and make sure that the event interfaces actually match.
The third command, Extend Interfaces, automatically extends the interfaces in your compatibility file with any new public functions
currently defined in your project. This command always attempts to compile your project to get a current state. It will abort if there
are any compatibility breaks, which is easily determined by comparing the version numbers in the current and compatibility type libraries. You
should fix any breaks by editing the compatibility typelib before running this command. This is the only command of the four that requires a
compile step. The compatibility editor coordinates with the post build editor here to make sure that post-build changes are skipped. This
command is designed to maintain what I called Development Compatibility in chapter 15.
The fourth command, Clean Aliases..., prompts you for a directory that contains all previous versions of your executable that you care
about. It then scans these exes to see if the aliases in the current compatibility file have any real world counterparts. You will be given
the option of recognizing non-alias types only, or also acknowledging alias types as being relevant. If you have all previously shipped versions of
your component, then recognizing non-alias types only is safe. However, this does a very thorough scrub, so you should use it with discretion.
You can also do this manually with the type library and event guid editors, but it is much easier to do it automatically.
Note that if there are some compatibility edits you would like to make that are not offered by the type library editor, you can always
use the unconstrained EditTLB.exe to make your changes, then reinsert the resource yourself. However, you should not make any guid or
alias changes, or delete types, unless you are inside the compatibility editor. You can replace the resource using VC or ReplaceResource.exe,
which is used during the build process. ReplaceResource.exe can be found in PowerVB\Tools\Src\BuildTools. See PowerVB\Tools\Src\Build3.bat for
sample usage. Also note that replacing icon resources is a much more complicated process than replacing a single resource. To replace the primary icon
in your exe, use ReplaceIcon.exe in the same location as ReplaceResource.exe. ReplaceIcon.exe is very
useful if you have a formless exe that needs an icon (like OCARecordFix.exe, which is what prompted me to build it), and is similarly used in Build3.bat.
Return to top
Monitoring Binary Compatibility Files
VB will warn you if you break compatibility, but it does not warn you if you have added items to your built executable that are not in your
compatibility file. In this scenario, failing to update your compatibility file to contain the new items will make your executable a moving
target because the interface guids can change whenever you compile your component. The 'PowerVB Compatibility Extension Checker' add-in does
a simple version check on the typelib of the generated executable to determine if VB has extended the executable beyond the compatibility. It
then prompts you to rectify this condition using the 'Extend Interfaces' command in the compatibility editor. Of course, you are also free to
keep the new interface identifiers and use a copy of the new executable as your next compatibility file. The BeyondCompat.dll add-in will generate
a file called BeyondCompat.[file name after /out] if you are running a silent command line build and a compatibility extension is detected.
This lets you verify that your automated build is consistently generating identical executables.
processed
Return to top
Automatic OCA Sweeper
In most cases when you project uses a custom control, VB will correctly change any publicly visible reference to a type defined in an OCA file
into the corresponding type in the OCX. This is a good thing, because you never want any evidence of the OCA file to leave your machine. The OCXDirect.dll
and OCARecordFix.exe tools that originally shipped with the book also deal with this area, but do not fully address the problem. The main issue
lies with PublicNotCreatable types in an OCX. These types are not controls and are not extended in the OCA. VB makes a simple copy of the type. If
you use one of these types in a public method, then VB will automatically remove the OCA reference and replace it with the appropriate OCX reference
when it builds the type library. However, if you Implement the interface, then VB does not perform this mapping. This means that it is very easy to
end up with a typelib that references the same type in both the OCX and the OCA! 'PowerVB Automatic OCA Sweeper' (OCASweeper.dll) takes care of this
problem automatically by checking every built library to ensure that there are no remaining OCA references in it. OCASweeper will also clean up
record guids that would normally be fixed by OCARecordFix.exe, so this tool is no longer needed if you leave OCASweeper.dll turn on. This add-in is
a silent benefactor: it does its job quietly and has no UI associated with it.
Return to top
Support for Debuggable Windows Hooks
The ability to hit breakpoints in the VB IDE with AddressOf subclassing active is provided by DbgWProc.dll and SubClass.bas. However, the same debugging
capabilities are not currently supported with hook procedures. To combat this, I've added DbgHProc.dll as the hook version of DbgWProc.dll, and
WindowsHook.bas as the complement to SubClass.bas. I've also provided two sample projects (in PowerVB\Samples\WindowsHook) that use WindowsHook.bas. The
first, CatchTab, uses a keyboard hook to catch the tab key on a form. The second, ControlsAddCBTHook, uses a CBT_HOOK to dynamically add a multiline
textbox using Controls.Add. Both of these exercises are otherwise impossible in VB5/6. Pay close attention to the signature of the hook function in
ControlsAddCBTHook.bas: WindowsHook.bas sends the next hook handle as an added first parameter if you do not provide a this pointer. This lets you call
CallNextHookEx without providing a callback into a class module.
Return to top
Console Application Add-In
The 'PowerVB Console Application Add-In' is also provided as freeware at this site. The current drops contains this add-in and its source, along with
a sample project in Samples\ConsoleApp. The StdOutput.cls, StdInput.cls, and VBGraphics.tlb files used in the sample have been added to the PowerVB\Code
directory. See Console Application Add-In for more information.
Return to top
Library Edit History Files
Library Edit History (LEH) files are used by PostBuild.dll to playback changes made to a type library. This functionality is built into TLBEditor.ocx, but
has not been exposed previously outside of the post-build editor. The standalone type library editor (EditTLB.exe) now has a 'New Recorder Window' menu
item that lets you open one or more recorder windows, which record/edit/playback LEH files. Any open LEH file is live: any changes made to the type library
will be recorded at the current position. Any scripts that are played back will also be recorded in any other open recorder windows. You will find that
there are fewer restrictions in place in a recorder window than in the PostBuild environment. For example, you can set the current statement to any position,
allowing you to skip or repeat certain edits. EditTLB has full command line support for applying LEH files to a given typelib. Run EditTLB /? to see the
support command line options.
Return to top
VB5 Command Line Builds
The biggest issue porting the add-ins to support VB5 is that VB5 does not load any add-ins during a command-line build. This is a major handicap because
several of the PowerVB add-ins were designed to run during the build process. I've provided support for what I considered to be the most critical add-ins
so that you can leverage them without building by hand.
PostBuild editing is the most important feature missing with a command line build. However, the new LEH playback capabilities of EditTLB.exe enable
you to produce the same modified typelib that PostBuild would produce. (You should not use the following technique for a VB6 build because you EditTLB
does not handle update record guids in the executable, which is needed in VB6 but was not yet an issue in VB5). EditTLB also does not support direct
resource replacement, so you will have to do this as a separate step. Use the following to perform the postbuild editing step for MyDll.dll, built by
MyDll.vbp, during a build process.
start /w VB5 /make MyDll.vbp start /w PowerVB\Tools\EditTLB /tlb MyDll.dll /leh MyDll.leh /saveas MyDllMunged.tlb start /w PowerVB\Tools\Src\BuildTools\ReplaceResource MyDll.dll TYPELIB 1 MyDllMunged.tlb del MyDllMunged.tlbEditTLB also has options for specifying a help modifier, and for redirecting output to a log file on failure. Run EditTLB /? to see options.
Import Table Dll Name Casing Bug (Q281913)
There is an unfortunate bug in VB that creates executables with import sections that do not properly load across all operating systems. VB generates
import sections when you use a type library declare for an API function. The problem occurs when you use a different casing of the dll name for multiple
functions in the same Dll. This is a subtle bug in that the Exe/Dll/Ocx will generally run on the operating system it was compiled on (except possibly
NT4/SP6), but will crash on any other operating system. Unfortunately, VBoostTypes used a dllname of kernel32.dll and ThreadAPI used kernel32, which VB
extends automatically to kernel32.DLL. Since the threading files use both of these typelibs, this means that any threaded application built using my
framework code before this update is extremely vulnerable to this bug. Also, other common typelibs, such as Bruce McKinney's typelib mention on page 422
in the book, standardized on upper case names, while I standardized on lower. This means that the VBoostTypes type library is not compatible with Bruce's
win.tlb, which is clearly unacceptable. I've taken a three pronged approach to fixing this problem.
1) Standardized the VBoost type libraries to use all upper case Dll names. I would encourage you to do the same and following this casing standard in you own type libraries.
2) Created an add-in to check for this situation. This add-in appears as Import Table Casing Validation in the Add-In Manager and resides in CheckImportCasing.dll, which now builds with the other PowerVB tools. The add-in has no UI unless it catches an error, when it displays the conflicting DllNames and the type library declarations that might be causing the conflict. I would recommend that you leave this add-in running. The overhead is minimal unless an error is found.
3) Worked with Microsoft to get a patch that fixes this problem up front. The patch will be officially available after SP5 ships, but I will also be posting a version on PowerVB.com that works with SP4.
Return to top
void Property Procedures
The book states that you cannot return void from a Property Get procedure. In truth, this is a MIDL limitation, not an oleaut/typelib limitation, and you can set these up nicely with the typelib editor for use with lightweight objects (VB won't let you Implement anythng that doesn't return an HRESULT). The advantage of the void property get definitions is that the retval parameter is expected as the return value, not an [out,retval], for values which are returned on the stack (<8 bytes). For example, a void property get returning a long has a signature in VB of 'Function MyProp(This As xxx) As Long' instead of 'Function MyProc(This As xxx, ByRef retVal As Long) As Long'. Larger types (Variants, other types >8 bytes) also do not use the last parameter, instead using a ByRef variable for the parameter immediately after the This parameter. A property that takes a long index parameter and returns a Variant would look like 'Sub MyProp(This As xxx, retVal As Variant, ByVal Index As Long)'. Void return types make lightweight objects cleaner because you can get the performance and code generation benefits of the void return type without losing the ease of use of properties in the calling code. Remember that the HRESULT is just baggage for internal lightweights because VB translates it back into an exception anyway after catching it, so you might as well throw directly and save yourself the code generation associated with HRESULT values, and the hassle of retVal parameters in your lightweights.
Return to top