Advanced Optimizations
Microsoft generally encourages you to play around with what they call the safe compiler options. Naturally, these are options that aren't situated beneath the Advanced Optimizations button. For those options Microsoft usually provides a disclaimer: "These might crash your program." Let's see what these Advanced Optimizations are about and why this warning is given. (See Table 7-5)
Table 7-5 Advanced Optimizations Options
| Option | Description |
| Allow Unrounded Floating Point Operations | Allows the compiler to compare floating-point expressions without first rounding to the correct precision. Floating-point calculations are normally rounded off to the correct degree of precision (Single or Double) before comparisons are made. Selecting this option allows the compiler to do floating-point comparisons before rounding, when it can do so more efficiently. This improves the speed of some floating-point operations; however, this may result in calculations being maintained to a higher precision than expected, and two floating-point values not comparing equal when they might be expected to. |
| Assume No Aliasing | Tells the compiler that your program does not use aliasing (that your program does not refer to the same memory location by more than one name, which occurs when using ByRef arguments that refer to the same variable in two ways). Checking this option allows the compiler to apply optimization such as storing variables in registers and performing loop optimizations. |
| Remove Array Bounds Checks | Disables Visual Basic array bounds checking. By default, Visual Basic makes a check on every access to an array to determine if the index is within the range of the array. If the index is outside the bounds of the array, an error is returned. Selecting this option will turn off this error checking, which can speed up array manipulation significantly. However, if your program accesses an array with an index that is out of bounds, invalid memory locations might be accessed without warning. This can cause unexpected behavior or program crashes. |
| Remove Floating Point Error Checks |
Disables Visual Basic floating-point error checking and turns off error checking for valid floating-point operations and numeric values assigned to floating-point variables. By default in Visual Basic, a check is made on every calculation to a variable with floating-point data types (Single and Double) to be sure that the resulting value is within the range of that data type. If the value is of the wrong magnitude, an error will occur.
Error checking is also performed to determine if division by zero or other invalid operations are attempted. Selecting this option turns off this error checking, which can speed up floating-point calculations. If data type capacities are overflowed, however, no error will be returned and incorrect results might occur. |
| Remove Integer Overflow Checks | Disables Visual Basic integer overflow checking. By default in Visual Basic, a check is made on every calculation to a variable with an integer data type (Byte, Integer, Long, and Currency) to be sure that the resulting value is within range of that data type. If the value is of the wrong magnitude, an error will occur. Selecting this option will turn off this error checking, which can speed up integer calculations. If data type capacities are overflowed, however, no error will be returned and incorrect results might occur. |
| Remove Safe Pentium FDIV Checks | Disables checking for safe Pentium floating-point division and turns off the generation of special code for Pentium processors with the FDIV bug. The native code compiler automatically adds extra code for floating-point operations to make these operations safe when run on Pentium processors that have the FDIV bug. Selecting this option produces code that is smaller and faster, but which might in rare cases produce slightly incorrect results on Pentium processors with the FDIV bug. |
Integer Overflow
Dim n As Integer n = 100 * 200 * 300
Disassembly (without Integer Overflow check)
' Do the multiplication - ax = 300 mov ax,offset Form::Proc+4Ch ' Signed integer multiplication. 300 * 20000 ' The 20000 is stored in Form::Proc+51h and was ' created by the compiler from the constant exp. ' 100 * 200 and held as 'immediate data' imul ax,ax,offset Form::Proc+51h n = Result mov word ptr [n],ax
Disassembly (with Integer Overflow check)
' Do the multiplication - ax = 100 mov ax,offset Form::Proc+4Ch imul ax,ax,offset Form::Proc+51h ' Jump to error handler if the overflow flag set jo ___vbaErrorOverflow Else, n = Result mov word ptr [n],ax
Array Bounds
Dim n1 As Integer
Dim n(100) As Integer
n1 = n(101)
Disassembly (without Array Bounds check)
' Sizeof(Integer) = 2, put in eax push 2 pop eax ' Integer multiplication. 2 * 101 (&H65) = result in eax. imul eax,eax,65h ' Get array base address in to ecx. mov ecx,dword ptr [ebp-20h] n(101) (base plus offset) is in ax mov ax,word ptr [ecx+eax] n1 = n(101) mov word ptr [n1],ax
Disassembly (with Array Bounds check)
' Address pointed to by v1 = 101, the offset we want mov dword ptr [unnamed_var1],65h ' Compare value thus assigned with the known size of array + 1 cmp dword ptr [unnamed_var1],65h ' Jump above or equal to 'Call ___vbaGenerateBoundsError' jae Form1::Proc+6Dh ' Zero the flags and a memory location. and dword ptr [ebp-48h],0 ' Jump to 'mov eax,dword ptr [unnamed_var1]' jmp Form1::Proc+75h ' Raise the VB error here call ___vbaGenerateBoundsError ' Store element number we want to access mov dword ptr [ebp-48h],eax ' Get the element we wanted to access into eax mov eax,dword ptr [unnamed_var1] ' Get array base address in to ecx. mov ecx,dword ptr [ebp-20h] ' n(101) is in ax (* 2 because sizeof(Integer) = 2 mov ax,word ptr [ecx+eax*2] ' n1 = n(101) mov word ptr [n1],ax Floating Point Error Dim s As Single s = s * s
Disassembly (without Floating Point Error check)
' Pushes the specified operand onto the FP stack fld dword ptr [s] ' Multiplies the source by the destination and returns ' the product in the destination fmul dword ptr [s] ' Stores the value in the floating point store (ST?) ' to the specified memory location fstp dword ptr [s]
Disassembly (with Floating Point Error check)
fld dword ptr [s] fmul dword ptr [s] fstp dword ptr [s] ' Store the floating point flags in AX (no wait) fnstsw ax ' Test for floating point error flag set test al,0Dh ' Jump if zero flag not set jne ___vbaFPException
You should now have more of a feel for why these options are left partially obscured like they are, and for the warning given by Microsoft. Without a native code debugger it's really hard to see just how your code's being affected. Even with a debugger like the one that comes with Visual Studio it's not a straightforward task to read through the assembly-language dumps and state that your code is cool and that the optimizations you've chosen are safe!
Library and object Files
From Table 7-3, you'll notice that VBAEXE6.LIB is linked in with our own OBJ file (created from our files and modules). The library contains just one component (library files contain object files), NATSUPP.OBJ. (NATSUPP might stand for "native support.") You can find this object by using DUMPBIN /ARCHIVEMEMBERS VBAEXE6.LIB. (DUMPBIN.EXE is the Microsoft Common Object File Format [COFF] Binary File Dumper.) NATSUPP.OBJ can be extracted for further examination using the Microsoft Library Manager, LIB.EXE:
lib / extract:c:\vbadev\r6w32nd\presplit\vbarun\obj\natsupp.obj vbaexe6.lib
The reason for including the path to the OBJ file is that the library manager expects us to specify exactly the name of the module-including its path. (This is embedded into the library file when the object file is first put into it and is discovered using DUMPBIN /ARCHIVEMEMBERS.) In other words, the object file probably "lived" at this location on someone's machine in Redmond! Similarly, we can tell that the source code for this object file was named NATSUPP.ASM and was in the directory C:\VBADEV\RT\WIN32. It was assembled using Microsoft's Macro Assembler, Version 6.13. (6.11 is the latest version available to the public, I believe.) Interestingly, it doesn't contain any code-just data-although what looks like a jump table (a mechanism often used to facilitate calls to external routines) appears to be included. To call a routine, you look up its address in the table and then jump to it, as shown in Table 7-6.
Table 7-6 Contents of NATSUPP.OBJ
| Name | Size | Content |
| .text | 0 | Readable code |
| .data | 4 | Initialized readable writable data |
| .debug$S | 140 | Initialized discardable readable data |
| .debug$T | 4 | Initialized discardable readable data |
- .text is where all the general-purpose code created by the compiler is output. (It's 0 bytes big, which probably means no code!)
- .data is where initialized data is stored.
- .debug$S and .debug$T contain, respectively, CodeView Version 4 (CV4) symbolic information (a stream of CV4 symbol records) and CV4 type information (a stream of CV4 type records), as described in the CV4 specification.
As well as statically linking with this library file, other object files reference exported functions in yet another library file, MSVBVM60.DLL This is a rather large DLL installed by the Visual Basic 6 Setup program in the WINDOWS\SYSTEM directory. (The file describes itself as Visual Basic Virtual Machine and at the time of writing was at version 6.0.81.76-or 6.00.8176 if you look a the version string.) Using DUMPBIN /EXPORTS MSVBVM60.DLL on this DLL yields some interesting symbolic information. For example, we can see that it exports a number of routines, 635 in fact! Some interesting-looking things, possibly routines for invoking methods and procedures, are in here as well: MethCallEngine and ProcCallEngine. Additionally, there are what look like stubs, prefixed with rtc ("run-time call," perhaps?), one for apparently all the VBA routines: rtcIsArray, rtcIsDate, rtcIsEmpty, … rtcMIRR , … rtcMsgBox, … rtcQBColor, and so on. And as with most DLLs, some cryptic, yet interesting exports, such as Zombie_Release, are included.
In addition to this symbolic information, the DLL contains a whole bunch of resources, which we can extract and examine using tools such as Visual C++ 6. Of all the resources the DLL contains, the one that really begs examination is the type library resource. If we disassemble this using OLEVIEW.EXE, we can see its entire type library in source form.
The type library contains all sorts of stuff as well as the interface definitions of methods and properties, such as the hidden VarPtr, ObjPtr, and StrPtr routines.
It turns out that this MSVBVM60.DLL is probably the run-time support DLL for any Visual Basic 6 native and p-code executable; that is, it acts like MFC42.DLL does for an MFC application. (MFC stands for Microsoft Foundation Classes, Microsoft's C++/Windows class libraries.) We can confirm this by dumping a built native code executable. Sure enough, we find that the executable imports routines from the DLL. (By the way, the Package And Deployment Wizard also lists this component as the Visual Basic Runtime.)
By dumping other separate object files, we can gather information about what is defined and where it is exported. For example, we can use DUMPBIN /SYMBOLS MODULE1.OBJ to discover that a function named Beep will be compiled using Microsoft's C++ name decoration (name mangling) regime and thus end up being named ?Beep@Module1@@AAGXXZ. Presumably, this function is compiled as a kind of C++ anyway; that is, in C++ it is defined as (private: void __stdcall Module1::Beep(void)). Or better yet, we can use DUMPBIN /DISASM ????????.OBJ to disassemble a module.
The same routine-Beep-defined in a class, Class1 for example, looks like this:
?Beep@Class1@@AAGXXZ (private: void __stdcall Class1::Beep(void)).
Maybe now we can see why, since Visual Basic 4, we've had to name modules even though they're not multiply instantiable. Each seems to become a kind of C++ class. According to the name decorations used, Beep is a member of the C++ Classes Class1 and Module1.