Stuff About the Compiler

In this section, we'll examine applications compiled to native code. We won't deal much with p-code (packed code) at all, aside from a brief introduction and some comparisons with native code.

As you probably know, Visual Basic 6 applications, just like their Visual Basic 5 counterparts, can now be "properly" compiled, unlike Visual Basic version 4 and lower, which produced p-code executables. In other words, as well as producing p-code executables, Visual Basic 6 can produce a native code binary. Which compile option you choose is up to you. I suspect that most corporate developers will want to know more about this compiler process than they ever wanted to know about p-code.

A Little About P-Code

P-code applications are usually smaller (and slower) than native code applications. With p-code, an interpreter compresses and packages your code. Then, at run time, this same interpreter expands and, of course, runs your application. P-code applications are usually ported more easily to different processors.

The term p-code was derived from the term "pseudocode" because p-code consists of a RISC-like set of instructions for a "make-believe" processor. At run time, this processor, usually known as a stack machine (because it uses a stack for practically all its operations), is simulated by the built-in interpreter. (Just so you know, a "normal" processor uses registers and a stack primarily to pass values to and from function calls.) Because of its imaginary nature, the processor's instruction set never needs to change; instead, each instruction is mapped, via a lookup table, to a real instruction on any given processor. Logically, then, all that's required to move code from one processor to another is this mapping—code generation remains largely unaffected.

In a nutshell, p-code is an intermediate step between the high-level instructions in your Visual Basic program and the low-level native code executed by your computer's processor. At run time, Visual Basic translates each p-code statement to native code.

With p-code, typical size reduction from native code is more than 50 percent. For example, when the VisData sample that is included on the Visual Basic 5 CD is compiled to p-code, the resulting executable is less than half the size it would be if compiled to native code (396 KB vs. 792 KB). Additionally, compiling p-code is a lot faster than compiling to native code—around seven times faster. (Some of the reasons for the speed of p-code compiling will become evident later in the chapter.) You'll need to keep this compile-time difference in mind during your development and testing phases. These compile timings, and all the other timings in this chapter, were made using prerelease builds of Visual Basic 6. If you're interested in these timings, you should conduct your own tests using the actual product.

Back in the days of Visual Basic 4 and earlier, a native code compiler was, I think, one of the most requested features, so I'm not surprised to find that Microsoft put it in Visual Basic 5—and, of course, the native code compiler is basically the same feature in Visual Basic 6. Personally, however, I think that native code compilation, for many reasons (and forgetting for a second that it typically executes faster) is a backward step. I'm still convinced that p-code is ultimately a superior technology compared to native code generation, as does, apparently, Sun Microsystems, because Java is essentially doing the same thing! Ever since the first version of Visual Basic, its p-code output has been, or could have been, roughly equivalent to Java's bytecodes. If a Visual Basic program had to be instantiated using the Visual Basic "virtual machine" (that is, something like vbrun100 <AppName>) and if that virtual machine were to be ported to different, non-Intel architectures, Visual Basic could have perhaps led the way to what's now become "bytecode nerdvana" instead of being criticized in its p-code form for being both slow and interpreted (just like pure Java is, in fact) Not convinced? Here's one of Sun's own descriptions of bytecode technology.

[On Java being a portable technology] "The Java compiler does this by generating bytecode instructions that have nothing to do with a particular computer architecture. Rather, they are designed to be both easy to interpret on any machine and easily translated into native machine code on the fly." I'm sure the similarity between the two is obvious.

If you want to read some more stuff about p-code that isn't specific to Visual Basic, search MSDN for "Microsoft P-Code Technology" and see Andy Padawer's excellent paper on the subject.

Generating Code

You select the code generation model you want via the somewhat hidden dialog box shown in Figure 7-1. You get to this dialog box by choosing Properties from the Project menu.

Figure 7-1 Visual Basic's compiler options dialog boxes

As you can see, some extra compilation options become available when you select Compile To Native Code. I'll discuss some of these options a little later.

When you compile to native code, the Visual Basic 6 native code generator/compiler, C2.EXE, is run once for each code component in the project. For example, if a project has a form, Form1; a standard module, Module1; and a class module, Class1; C2.EXE is run a total of three times. Each invocation's options are the same depending on which you selected in the dialog box; that is, the options you select are used to compile the entire project. In case you're interested, C2.EXE runs as a multithreaded, Win32, 32-bit console process.

Each time the native code compiler is run, a hidden process (described as 16-bit by the Windows 95 Task Manager) is started and the code generator/compiler, also run as a hidden process, is run attached to this process. (In Windows 95, this process is run from the file WINOA386.MOD, with a process description of "Non-Windows application component for 386 enhanced mode." This file is not required if you're running under Windows NT.) As each invocation of C2.EXE terminates, the instance of WINOLDAP (the module name given to WINOA386.MOD) in which it was run is also terminated. You should now start to see why this process might be slower than selecting p-code generation (which is an internal process and doesn't use C2.EXE, although it does use LINK.EXE). Here's what the command-line arguments of a typical compilation look like (with no optimizations):

C2 -il C:\WINDOWS\TEMP\VB603389 -f Form1 -W3 -Gy -G5 -Gs4096  -dos -Zl -FoC:\TEMP\Form1.OBJ -QIfdiv -ML -basic 

These flags are explained in Table 7-1.

Table 7-1. Command-line flags for the C2 Compiler

Flag Explanation
-il C:\WINDOWS\TEMP\VB603389 Undocumented but also used for C program; probably used to "name" intermediate language files
-f Form1 The input file to be compiled
-W3 Warning level 3
-GyEnable function-level linking
-G5 Optimize for Pentium
-Gs4096 Turn off stack probes
-dos Undocumented but also used for a C program
-Zl Remove default library name from OBJ file
-Fo C:\TEMP\Form1.OBJ Name of output file
-QIfdiv Perform Pentium FDIV erratum fix
-ML Create a single-threaded executable file
-basic Undocumented but appears to be a new flag for Visual Basic compilation

Some of the flags are described in more detail here as well:

-il This flag is undocumented but "intermediate language" is a good guess for what "il" stands for. Files produced are <Signature>GL, SY, EX, IN, and DB. I have no idea what these files contain. In the command-line example in Table 7-1, the following files (long filenames shown) are generated temporarily while the application is being built:

  • VB603389GL

  • VB603389SY

  • VB603389EX

  • VB603389IN

  • VB603389DB

-G5 The option optimizes the generated code to favor the Intel Pentium processor. Here's what the Microsoft Developer Network (MSDN) says about the same Visual C++ flag: "Use this option for programs meant only for the Pentium. Code created using the /G5 option does not perform as well on 80386- and 80486-based computers as code created using the /GB (Blend) option." Interestingly, by default, the -G5 switch is always used—even when you compile on a 486 machine.

-Gs[size] If a function requires more than size stack space for local variables, its stack probe is activated. A stack probe is a piece of code that checks whether the space required for passed parameters and local variables is available on the stack before any attempt to allocate the space is made. -Gs0 is the same as -Ge, turn stack probes on; -Gs4096 is the default.

-ML This option places the library name LIBC.LIB in the object file so that the linker will use LIBC.LIB to resolve external symbols. This is the compiler's default action. LIBC.LIB does not provide multithread support, by the way.

Don't bother to scan your Visual Basic 6 documentation for information about these flags because you won't find any—they are all undocumented. If you have a set of documentation for the Visual C++ compiler, however, you might be in luck. It seems that C2.EXE is taken from the Visual C++ compiler (this file is called C2.DLL in version 6 of Visual C++, although in Visual Basic 5, both Visual Basic and Visual C++ shared exactly the same file—C2.EXE). C2.EXE is, in fact, the compiler from Microsoft's Visual C++ product. Nevertheless, the above interpretation of the flag meanings is mine alone. Microsoft doesn't document how its C++ compiler works beyond describing CL.EXE (the front end to the C compiler).

Table 7-2 provides a summary of the C2 compiler incarnations at the time of this book's writing.

Table 7-2. Comparison of Visual C++ and Visual Basic C2 Components

Component Product Version Compiler Description
C2.EXE (from Visual Basic 6) 6.0.8041.0 32-Bit 80x86 Compiler Back End
C2.DLL (from Visual C++ 6) 6.0.8168.0 32-Bit 80x86 Compiler Back End
C2.EXE (from Visual Basic 5) 5.0.0.7182 32-bit Visual Basic Compiler Back End

Visual Basic itself evidently provides the compiler's first pass, unlike Visual C++ in which the first pass (the parser and some of the optimizer) of C and C++ files is provided by either C1.DLL or C1XX.DLL, respectively. In terms of compilers, VB6.EXE is seemingly analogous to CL.EXE.

The Loggers

Either the C application or the Visual Basic application listed at the end of this section (and on the CD) can be used to replace the real C2.EXE file. To replace it, follow these steps for the C version:

  1. Make backup copies of C2.EXE and LINK.EXE.

  2. Rename C2.EXE to C3.EXE.

  3. If you want to rebuild the C application, make sure that the first real line of code in the OUTARGS.C source file reads as follows:

    strcpy(&carArgs[0], ".\\C3 ");

    The binary version on the CD already includes this line of code.

  4. Copy the EXE (OUTARGS.EXE) to C2.EXE.

    copy outargs.exe c2.exe

  5. Your original C2.EXE is now C3.EXE, so no damage is done.

    Use Visual Basic 6 as you normally would.

The steps for using the Visual Basic version are a little different. To replace C2.EXE with the Visual Basic application, follow these steps:

  1. Make backup copies of C2.EXE and LINK.EXE.

  2. Compile the code to OUTARGS.EXE (make sure your project contains just the OUTARGS.BAS standard module—no forms or anything else).

  3. Rename C2.EXE to C3.EXE. Rename LINK.EXE to L1NK.EXE. (Note that the "i" has been changed to a "1".)

  4. Copy the EXE (OUTARGS.EXE) to C2.EXE and LINK.EXE. Your original C2.EXE is now C3.EXE and your LINK.EXE is now L1NK.EXE, so no damage is done.

  5. Run REGEDIT.EXE, and under HKEY_CURRENT_USER\Software\VB and VBA Program Settings insert two new keys (subkeys and values as shown here):

    HKEY_CURRENT_USER\Software\VB and VBA Program Settings\           \C2              \Startup                 \RealAppName    ".\C3"           \LINK               \Startup                 \RealAppName    ".\L1NK" 

  6. Use Visual Basic 6 as normal.

The purpose of the Visual Basic version of OUTARGS.EXE is to have the same binary self-configure from a Registry setting. This means that you only need one OUTARGS.EXE (renamed appropriately) to "spy" on any application.

The output of the Visual Basic application is a little less fully featured than that produced by the C application. After you've carried out either of these steps, the following will happen: When Visual Basic 6 runs (to compile to native code), it will run C2.EXE. C2.EXE, which is really our OUTARGS.EXE program, will log the call made to it to the file C2.OUT. (Our application logs to a file based upon its own name, <EXEname>.OUT; because our application is renamed C2.EXE, the log file will be C2.OUT.) Information logged includes the parameters that have been passed to it. C2.EXE will then shell C3.EXE (the "real" C2), passing to it, by default, all the same parameters that it was passed. The net effect is that you have logged how C2 was invoked.

The Visual Basic OUTARGS program will also be used to log the linker, if you followed the steps above. Listing 7-1 is a typical C2.OUT log (C version).

Listing 7-1 Typical C2.OUT log file

 ********** Run @ Wed Jan 1 00:00:00 1998 * EXE file...         C2 * Command Line Arguments... 1       -il 2       C:\WINDOWS\TEMP\VB476314 3       -f 4       Form1 5       -W 6       3 7       -Gy 8       -G5 9       -Gs4096 10      -dos 11      -Zl 12      -FoC:\TEMP\Form14.OBJ 13      -QIfdiv 14      -ML 15      -basic * 'Real' program and arguments...         .\C3 -il C:\WINDOWS\TEMP\VB476314 -f Form1 -W 3 -Gy -G5          -Gs4096 -dos -Zl -FoC:\TEMP\Form14.OBJ -QIfdiv -ML -basic ********** Run End 

The Visual Basic team seems to have added a space between the -W and the 3, possibly causing C2 to interpret this as two separate switches. Since C2 doesn't error or complain, I'm assuming that it knows to treat the switch as W3 (warning level set to 3).

By further altering the code (again, the C version is demonstrated here), you can change, add, or remove compiler switches. For example, you can add the following code to the argument processing loop to replace, say, -G5 with, say, -GB, the "blend" switch mentioned earlier in our discussion of -G5.

 if (0 == strcmp(argv[nLoop], "-G5")) {     (void)strcat(&carArgs[0], "-GB ");     continue; } 

NOTE

The C version OUTARGS.EXE doesn't like long pathnames that include spaces. Each "gap" causes the next part of the pathname to be passed to C3 as a separate command-line argument. To fix this, either alter the C code to quote delimit each pathname or copy your test Visual Basic project to, say, C:\TEMP before attempting to use it; that is, remove any long pathname. (Leave the renamed OUTARGS C2.EXE in the same folder as the real, now renamed, C3.EXE.) Note that the Visual Basic OUTARGS.EXE doesn't have the same problem.

To restore the "real" program, simply copy over C2.EXE with C3.EXE:

 copy c3.exe c2.exe 

The Linker

As I've already said, C2.EXE compiles each component to an object file. When all the components are compiled, they are linked using LINK.EXE. Table 7-3 lists the command line arguments you might find in a typical run when creating an EXE containing a single form, class module, and standard module. The only compile option switched on for this run was Create Symbolic Debug Info. This information was captured using the OUTARGS.EXE program.

Again, LINK.EXE is taken from the Visual C++ 6.0 compiler. At the time of writing, its version number was 6.00.8168.0—exactly the same version as that supplied with C2.DLL. See the Visual C++ documentation or MSDN for more information regarding these linker switches.

The linker is also used to create a p-code application, by the way. The difference in the invocation is that VBAEXE6.LIB is not linked in and that only one object file is used as input—ProjectName.OBJ.

Table 7-3 Command-Line Switches for the Linker

Switch Explanation
C:\TEMP\Form1.OBJ Form OBJ file
C:\TEMP\Module1.OBJ Module OBJ file
C:\TEMP\Class1.OBJ Class OBJ file
C:\TEMP\Project1.OBJ Project OBJ file
C:\PROGRAM FILES\VISUAL STUDIO\VB\VBAEXE6.LIB Library of Visual Basic OBJs
/ENTRY:__vbaS Sets the starting address for an executable file or DLL. The entry point should be a function that is defined with the stdcall calling convention. The parameters and the return value must be defined as documented in the Win32 API for WinMain (for an . EXE) or DllEntryPoint (for a DLL). This entry point is in your <project name>.OBJ file—here it will be in PROJECT1.OBJ. Note that neither Sub Main nor Form_Load is mentioned.
/OUT:C:\TEMP\Project1.exe The output file—the EXE!
/BASE:0x400000 Sets a base address for the program, overriding the default location for an executable file (at 0x400000) or a DLL (at 0x10000000). The operating system first attempts to load a program at its specified or default base address. If sufficient space is not available there, the system relocates the program. To prevent relocation, use the /FIXED option. The BASE generated by Visual Basic 6 for an ActiveX DLL is 0x11000000—something that's different from the default at last.
/SUBSYSTEM:WINDOWS,4.0 Tells the operating system how to run the .EXE file. (Options include CONSOLE | WINDOWS | NATIVE | POSIX.)
/VERSION:1.0 Tells the linker to put a version number in the header of the executable file or DLL. (This option has nothing to do with a VERSIONINFO resource.) The major and minor arguments are decimal numbers in the range 0 through 65535. The default is version 0.0. Visual Basic uses the Major and Minor settings on the Make tab of the Project Properties dialog box for these values. This switch is used to document the image version as shown by DUMPBIN.EXE (another Microsoft Visual C++ tool).
/DEBUG Creates debugging information for the executable file or DLL. The linker puts the debugging information into a program database (PDB). It updates the program database during subsequent builds of the program.
/DEBUGTYPE:{CV|COFF|BOTH} Generates debugging information in one of three ways: Microsoft format, COFF format, or both. CV is CodeView; COFF is Common Object File Format.
/INCREMENTAL:NO Specifies whether incremental linking is required.
/OPT:REF Excludes unreferenced packaged functions from the executable file. Packaged functions are created using the Gy flag at compile time (see Table 7-1). Packaged functions have several uses (not mentioned here) and are created automatically, sometimes by the compiler. For example, C++ member functions are automatically packaged.
/MERGE:from=to Combines the first section (from) with the second section (to), naming the resulting section "to". If the second section does not exist, LINK renames the section "from" as "to". The /MERGE option is most useful for creating VxDs and for overriding the compiler-generated section names.
/IGNORE:4078 Ignores certain warnings (defined in LINK.ERR). 4078 means that LINK found two or more sections that have the same name but different attributes.

Why these switches?

I have no idea why some of these switches are used explicitly (on the compiler also), particularly since some are set to the default anyway. Perhaps some of the reasons for using these switches will be documented at later.

Using the Compiler to Optimize Your Code

The effect of the optimization options (on the Compile tab of the Project Properties dialog box and in the Advanced Optimizations dialog box) on how C2.EXE and LINK.EXE are driven is summarized in Table 7-4 (for building a standard EXE).

Obviously, -G6 means favor the Pentium Pro.

Notice that most of the switches have no effect on how C2 or LINK are started (although the EXE size changes so that we know the option is making itself known!). Since most switches have no effect, we must assume they are being acted on within VB6.EXE itself (as it seems to contain the compiler's first pass). Or perhaps the mystery files shown earlier (VB603389GL, VB603389SY, VB603389EX, VB603389IN, and VB603389DB) have some way of influencing the code generator, thus sidestepping our efforts to understand how the process is being controlled.

Table 7-4 The Compiler Effect

Optimization Option C2.EXE Effect LINK.EXE Effect
Optimize For Small Code None None
Optimize For Fast Code None None
Favor Pentium Pro /G6 (from G5) None
Create Symbolic Debug Info /Zi /DEBUG
/DEBUGTYPE:CV    
Assume No Aliasing None None
Remove Array Bounds Checks None None
Remove Integer Overflow Checks None None
Remove Floating Point Error Checks None None
Allow Unrounded Floating Point Operations None None
Remove Safe Pentium(tm)FDIV Checks /QIfdiv Removed None

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.
By using the Visual C++ debugger (or any compatible debugger) with Visual Basic code that has been compiled to contain symbolic debugging information, it's possible to see more of what each option does to your code. By way of explanation, here are a few annotated examples (obviously you won't expect to see commented code like this from a debugger!):

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
The sections are as follows:

  • .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.

The Logger Code

As promised, Listing 7-2 shows the C source code for the spy type application we used earlier on the command line arguments of both C2.EXE and LINK.EXE. Note that a nearly equivalent Visual Basic version follows this application.

Listing 7-2 The OUTARGS logger application in C

 /******************************************************* Small 'C' applet used to replace Visual Basic 6 compiler apps so as to gather their output and manipulate their command-line switches. See notes in main text for more details. *******************************************************/ #include < stdio.h   > #include < string.h  > #include < time.h    > #include < windows.h > int main (     int    argc     // Number of command-line arguments.    ,char * argv[]   // The arguments themselves.    ,char * env []   // Environment variables.  ) {     /**************************************     ** General declares.     */      #define BUFF 2048                        auto FILE *      stream;        // File to write to.     auto struct tm * tt;            // Time stuff for time of write.     auto time_t      t;             // ----- " " -----         auto char        carBuff[255];  // Used for holding output                                      // file name.     auto char        carArgs[BUFF]; // Holds command line args                                     // for display.     auto int         nLoop;         // Loop counter.          /* ***************     ** Code starts ...     */      // Change according to what real (renamed) application you     // want to start.     (void)strcpy(&carArgs[0], ".\\C3 ");     // Get the system time and convert it to ASCII string.     (void)time(&t);     tt = localtime(&t);     // Going to need to append to our exe name, so write      // to temp buffer.     (void)strcpy(&carBuff[0], argv[0]);     // Now append .OUT - should contain ???.OUT after this where ???      // could be APP.EXE or just APP, depending upon how this program      // is run.     (void)strcat(&carBuff[0], ".OUT");     // Write to EXEName.OUT file (append mode)...     if (NULL != (stream = fopen(&carBuff[0], "a")))     {         // Write out the time.         (void)fprintf(stream, "********** Run @ %s\n", asctime(tt));         // Output name of EXE file.         (void)fprintf(stream, "* EXE file...\n\n");         (void)fprintf(stream, "\t%s\n", argv[0]);         /* *****************************************************         ** Output command line args (exclude our exe name argv[0]).         */         (void)fprintf(stream, "\n* Command Line Arguments...\n\n");         for (nLoop = 1; nLoop < argc; nLoop++)         {             (void)fprintf(stream,"%d\t%s\n", nLoop, argv[nLoop]);             // Append to args buffer.             (void)strcat(&carArgs[0]   , argv[nLoop]);             (void)strcat(&carArgs[0]   , " ");         }         /* *****************************         ** Output environment variables.         */         (void)fprintf(stream, "\n* Environment Variables...\n\n");         for (nLoop = 0; NULL != env[nLoop]; nLoop++)         {           (void)fprintf(stream, "%d\t%s\n", nLoop, env[nLoop]);         }         /* ***************************************************         ** Output name and args of other application to start.         */         (void)fprintf(stream, "\n* 'Real' program and arguments...\n\n");         (void)fprintf(stream, "\t%s\n", &carArgs[0]);         (void)fprintf(stream, "\n********** Run End\n\n\n");         // All done so tidy up.         (void)fclose(stream);         (void)WinExec(&carArgs[0], 1);     }     return 0; } 

And the (nearly) equivalent Visual Basic application in Listing 7-3:

Listing 7-3 The OUTARGS logger application in Visual Basic

 Sub Main()     If 0 <> Len(Command$) Then         Dim sRealAppName As String         sRealAppName = GetSetting(App.EXEName, "Startup", _                                   "RealAppName", "")         If 0 <> Len(sRealAppName) Then             Call Shell(sRealAppName & " " & Command$, vbHide)                  Dim nFile As Integer                  nFile = FreeFile             Open App.EXEName & ".out" For Append Access Write As nFile             Print #nFile, "****** Run at " & _                           Format$(Date, "Short date") & _                           " " & Format$(Time, "Long Time")             Print #nFile, sRealAppName & " " & Command$             Close nFile         End If     End If End Sub 


Ltd Mandelbrot Set International Advanced Microsoft Visual Basics 6. 0
Advanced Microsoft Visual Basic (Mps)
ISBN: 1572318937
EAN: 2147483647
Year: 1997
Pages: 168

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net