Previous Page TOC Next Page


20 — Incorporating DLLs

by Ron West

This chapter complements the "Calling Procedures in DLLs" chapter in the Visual Basic 4.0 Programmer's Guide by showing you how to build a simple DLL and demonstrating some of the tools that Microsoft has provided to enable you to investigate the DLL's characteristics.

This chapter does not discuss the inline OLE servers you can create within Visual Basic 4.0 because they are implemented as logically partitioned sections of the same interpreted threaded p-code, which is used for all other Visual Basic 4.0 executables. Although these inline OLE servers perform a function similar to a true OLE server (which you might be able to make if you are an expert with the latest version of the Microsoft foundation classes and the C++ language), they are not optimized machine code DLLs in any way that is commonly understood. You should consult the Visual Basic 4.0 manuals for example code and other information on inline OLE servers.

Understanding DLLs

Dynamic link libraries (DLLs) are executable modules containing functions that Windows applications can call to perform useful service tasks. DLLs play a central role in Windows applications; they make its resources and functions available to Windows applications. Actually, all Windows libraries are dynamic link libraries. These libraries are called dynamic because they are linked with the application that needs them at runtime, instead of being statically linked as a final act of the software's build process.

DLLs are not loaded into memory until a function in the DLL is called for the first time.

The different linking methods have different benefits. Static linking can give you a stand-alone program that you know is complete, but dynamic linking enables a modular approach in which different components can be replaced individually. The DLL approach enables components to share common executable code instead of duplicating the runtime code for each program.

From the point of view of a given DLL, any of its functions available for use by other applications are known as exported functions; if the DLL uses functions from other libraries, these are its imported functions. One DLL's exported functions are another DLL's imported functions.


NOTE

To further illustrate the qualities of and differences between static linked programs and dynamic linked libraries, I've included Listings 20.11 onwards at the end of the chapter, where I build one of each type of link using the minimum tools necessary. The capability to build DLLs using the fundamental tools is invaluable if you ever have to create them on a low-speed/low-memory computer that sags under the weight of a GUI-based environment.

By the way, not all DLLs have DLL file name extensions—the Windows system files have .EXE extensions, Visual Basic controls are packaged DLLs with .VBX or .OCX extensions, drivers have .DRV extensions, and fonts have .FON extensions.

You need to be careful when running programs that use DLLs because the link is not performed until runtime, which means that you might pick up the wrong DLL if you have more than one copy of the DLL on your system. I have included some sample source code (in Listing 20.3) that your main program can use to ask Windows components for their version number.


TIP

Unless a path is specified explicitly, the directory (folder) sequence Windows uses to search for DLLs follows:

1. Current directory

2. Windows directory

3. Windows system directory

4. Any of the directories in the MS-DOS PATH environment variable

5. Any directory in the list of directories mapped in a network

Examining the Limitations of Visual Basic

Visual Basic does not directly support pointers to variables, which causes difficulties when accessing a considerable portion of Windows API functions (which frequently pass pointers as parameters). This is not as big a problem as it might seem, however, because Visual Basic can hold the value of pointers in integer variables (usually Long values). As long as you don't need to explicitly know what the contents of a pointer mean, you can receive a pointer value from one API and pass it back into another API. As long as the first and last API calls in a sequence will take and return Visual Basic-compatible variables, respectively, you can proceed without any further complications.

Listing 20.1 shows a code example where hOldWnd% and lbhWnd% are used to validly hold the values of non-Visual Basic data because Visual Basic does not need access to the values. A Windows API call SendMessage() is used to make a Visual Basic listbox behave in a way beyond what usually is available in Visual Basic by appearing to support synchronized multiple columns. Figure 20.1 shows the resulting listbox.


Figure 20.1. Using Windows APIs to extend the functionality of the Listbox control.

   Declare Function GetFocus Lib "user" () As Integer

   Declare Function SendMessage Lib "user" (ByVal hWnd As Integer, ByVal wMsg As              _Integer, ByVal wp As Integer, lp As Any) As Long

   Declare Function PutFocus Lib "user" Alias "SetFocus" (ByVal hWnd%) As Integer

   Const WM_USER = &H400

   Const LB_SETTABSTOPS = WM_USER + 19

   Sub Form_Click()

      Dim retVal&, R%                    'API return values

      Dim hOldWnd%, lbhWnd%              'WinCtrl handles

      Static tabs(3) As Integer

      tabs(1) = 10                       'Set up array of defined tab stops.

      tabs(2) = 70

      tabs(3) = 130

      hOldWnd% = GetFocus()              'Remember who had the focus.

      Form1.Show                         'Showing the form avoids  "Illegal

                                         ' Function Call" on 'List1.SetFocus'

      list1.SetFocus                     'Set the focus to the list box.

      lbhWnd% = GetFocus()               'Get the handle to the list box.

      'Send a message to the message queue.

      retVal& = SendMessage(lbhWnd%, LB_SETTABSTOPS, 3, tabs(1))

      R% = PutFocus(hOldWnd%)            'Restore handle to whoever had it.

      'Place some elements into the list box, separated by TAB chars:

      list1.AddItem "Last Name" + Chr$(9) + "First Name" + Chr$(9) + "Year"

      list1.AddItem "Washington" + Chr$(9) + "George" + Chr$(9) + "1789"

      list1.AddItem "Adams" + Chr$(9) + "John" + Chr$(9) + "1797"

      list1.AddItem "Jefferson" + Chr$(9) + "Thomas" + Chr$(9) + "1801"

      list1.AddItem "Madison" + Chr$(9) + "James" + Chr$(9) + "1809"

      list1.AddItem "Monroe" + Chr$(9) + "James" + Chr$(9) + "1817"

      list1.AddItem "Adams" + Chr$(9) + "John Q." + Chr$(9) + "1825"

   End Sub

A further limitation of Visual Basic is that you have to explicitly declare your DLL function calls at design time, which makes runtime binding of unspecified device driver libraries impossible within Visual Basic alone.

Deciding Whether to Create Your Own DLLs

In order to provide the optimum design-time flexibility, Visual Basic 4.0 is implemented as an interpreter that processes semicompiled programs. The code is analyzed and internally represented so that the code can be reconstituted for display purposes, yet have a minimum interpretation overhead at runtime. Therefore, due to its interpreted nature, few of the optimizations are available that might otherwise be attempted during a true reduction to native machine code (such as that offered by the compiler shipped with Visual C++).

This limitation is not necessarily significant, because the target computer specification may be sufficiently generous to run the Visual Basic code within the user's limits of patience. However, if many iterative processes are required that take far too long to execute in spite of the careful removal of inefficiencies, you can take advantage of the superior performance of optimized pure machine code by rewriting the time-consuming sections of the program in a language that has a suitable compiler available.

Be aware, though, that the standard of programming competence and thoroughness required for writing Windows DLLs is very high. Failures of Visual Basic programs due to general protection faults (GPFs) almost always are due to rogue DLL programming—usually within libraries not supplied by Microsoft. You should try to remove all errors and warnings from your compiler output with the warning level set to the highest value available (with a few possible exceptions, such as the // comment symbol not being to the ANSI standard).

Using Mixed-Language Programming

Although there is no intrinsic reason why a DLL could not be built using a BASIC optimizing compiler, there are no vendors of Microsoft's size and presence that sell one (at least not at the time of this writing). Therefore, if you want to build a DLL to use with your Visual Basic program, you must write it in a different language. Although vendors such as Borland sell PASCAL compilers that can be used to create DLLs, Microsoft's languages of choice are C or C++, which can be compiled using Microsoft's Visual C++ product.

Building a DLL the Easy Way

Building a DLL is a much easier job these days than it used to be! The process I've put in Listings 20.11 and onwards (at the end of this chapter) used to be standard practice before visual tools such as Microsoft's Visual C++ or Borland's Pascal and C++ products for Windows became available.

To create a simple DLL using Visual C++, follow these steps:

  1. Choose Project | New. The New Project dialog box appears, as shown in Figure 20.2.

  2. Type a project name in the Project Name field. Then choose the Windows dynamic-link library (.DLL) option from the Project Type drop-down list and click OK.

    If you have pre-existing completed code that you want to make into a DLL, then proceed to Step 5 below.

  3. Choose Close in the New Project dialog box, then choose File | New from the main menu. A dialog box for your new code appears.

  4. Type the code into the UNTITLED window and save it with a filename of your choice using File | Save As. A simple example program is shown in Figure 20.3 (with the code window maximized).

    Add this new code to the project using Project | Edit... from the main menu (whose dialog is virtually identical to that of Project | New).

    The MS-proprietary _far and _pascal keywords have been added to the declaration in order to ensure that the parameters are not 16-bit and are passed in the correct order (see Figure 20.3). In practice, you should include the WINDOWS.H header file and use the defined constants FAR and PASCAL, but I've bypassed that step here for simplicity.

  5. Add the code to the project by first clicking on the desired item within the File Name list and then by clicking on the Add button (see Figure 20.4). Choose Close in the New Project dialog box when all files to be added to the project have been added.

  6. Choose Project | Rebuild All. Reply Yes to the Add default definition file and edit it? question in the message box that appears.

  7. Add the names and matching Ordinal Numbers of your Exports to the EXPORTS section of the dialog that appears (as I have done with SimpleCalc and @1, respectively in Figure 20.5).

  8. Then click the Save button on the main toolbar (with the diskette icon on it). You may now close this dialog if you want to tidy your screen.

  9. Choose Project | Rebuild All. You may expect to see abbreviated compiler output appear on the screen such as in Figure 20.6—if there are no errors, your DLL should be within the same directory as the project files.

  10. OPTIONAL. If you're curious about what's going on behind the scenes, close the whole project and load the project file <projectname>.MAK into Notepad (or some other suitable ASCII Editor). Ignore the Do not modify comment at the top of the file and delete all the /nologo and /NOLOGO words. Save the project file, reopen it within Visual C++, and choose Rebuild All again. You will see a lot more text in the Output window; most of it is very similar to the manual compiler output shown in Listings 20.11 and following at the end of the chapter.


Figure 20.2. Visual C++—defining a new project.


Figure 20.3. Example source code within Visual C++.


Figure 20.4. Choosing files to add to the project.


Figure 20.5. Adding and editing the Module Definition file.


Figure 20.6. Building the DLL.

For 32-bit compiling, you replace the "int _far _pascal" with a single keyword "__declspec(dllexport) int" and use VC++ version 2.0 onwards instead of the 16-bit VC++ version 1.5 :

/**************

 * SCALC_32.C *

 *************/

__declspec(dllexport) int SImpleCalc(int Parm1,int Parm2);

__declspec(dllexport) int SImpleCalc(int Parm1,int Parm2)

{

    return (Parm1 * Parm2);

}

Running the DLL from Visual Basic

The DLL you just created can be declared in the code, and then run from a Visual Basic 4.0 event routine, such as the one shown in Listing 20.2 and demonstrated in Figure 20.7.


Figure 20.7. Running a DLL with Visual Basic.

   Declare Function SimpleCalc% Lib "C:\SC_WIN\SCALCWIN" (ByVal Parm1%, ByVal       _Parm2%)

   Private Sub Label3_Click()

       Dim Parm1 As Integer

       Dim Parm2 As Integer

       Parm1 = Val(Text1.Text)

       Parm2 = Val(Text2.Text)

       Label3.Caption = Format$(SimpleCalc(Parm1, Parm2))

   End Sub

If you are building more sophisticated DLLs, you should consider using the Resource Compiler (RC.EXE) for features such as version information. You also might want to use IMPLIB.EXE to handle imports if your DLL calls any functions from other DLLs. See the Visual C++ documentation for more information on these utility programs.

Furthermore, if you're planning to make both 16-bit and 32-bit versions of your code, you should allow for certain subtleties in the function declaration, such as that Win32 is case-sensitive regarding function names but Win16 is not. Also note that 32-bit Windows API calls often have the number 32 appended to the end of the 16-bit function name.

The best way to deal with this problem is to localize Visual Basic 4.0's logical link to the DLL to one place only by using the Alias keyword within the Function declaration. The only line of your code that will need to be altered in order to migrate to 32-bit, therefore, is the Declare line.

Further 16-Bit versus 32-Bit Issues

One of the trickiest issues to arise from having both 16-bit and 32-bit versions of Visual Basic 4.0 is that of ANSI strings (VB4/16—one byte per character) and UNICODE strings (VB4/32—two bytes per character).

To be sure of always using the same number of bytes, you should use the new Byte data type instead of Strings. An exception to this necessity is when a DLL is called using the established Visual Basic declaration syntax, in which all versions of Visual Basic 4.0 will use the ANSI convention to pass strings. To force the passing of UNICODE, you should copy the string to a Byte array and pass that instead, or you should declare the function by using a TypeLib.

Likewise, with user-defined types (UDTs), it is important to ensure that the UDT defined in Visual Basic is of the same size as the C structure defined in the DLL. You can do this by enforcing a strict one-to-one alignment of corresponding members in both UDTs.

The 16-bit version of Visual Basic 4.0 packs the UDT members on byte boundaries, and the 32-bit version packs the UDT members on 4-byte (DWORD) boundaries. You therefore should compile your 16-bit DLL code using a single byte struct alignment compiler setting and your 32-bit DLL code using the 4-byte struct member alignment compiler setting.

You can aid full compatibility between versions by ordering the UDT members in a certain way: the >= 4-byte built-in data types first (Variant, Double, Currency, Single, and Long), followed by the remaining even-length data types, with the rest of the datatypes completing the structure. Then most of your data types (except the last-mentioned types) will be inherently impervious to this consideration.

Remember that, although integers always are two bytes long for Visual Basic and 16-bit DLLs, they are four bytes long for 32-bit DLLs. Therefore, always declare your two-byte integers as Short in your DLLs.

In Visual Basic 3.0, you can use the Visual Basic API routines to access and modify DLL data types. In Visual Basic 4.0, you should use the OLE APIs to access your DLL data types.

Table 20.1 lists some of the Visual Basic 3.0 API routines and their Visual Basic 4.0 OLE equi-valents.

Visual Basic 3.0 API Routine


Visual Basic 4.0 OLE Equivalent


VBArrayBounds

SafeArrayGetLBound/SafeArrayGetUBound

VBArrayElement

SafeArrayGetElement

VBArrayElemSize

SafeArrayGetElemsize

VBArrayFirstElem

N/A

VBArrayIndexCount

SafeArrayGetDim

VBCoerceVariant

VariantChangeType

VBCreateHlstr

SysAllocString/SysAllocStringLen

VBCreateTempHlstr

SysAllocString/SysAllocStringLen

VBDerefHlstr

N/A

VBDerefHlstrLen

N/A

VBDerefZeroTermHlstr

N/A

VBDestroyHlstr

SysFreeString

VBGetHlstrLen

SysStringLen

VBGetVariantType

N/A

VBGetVariantValue

N/A

VBResizeHlstr

SysReAllocStringLen

VBSetHlstr

SysReAllocString

VBSetVariantValue

N/A

Looking at Version Information

In Visual Basic 4.0, it is just as possible to save version information within executables as it always has been for DLLs via the Resource Compiler. This means that you can check all your software components at start-up to ensure that the correct combination of component-builds is available; if they are not available, the application can be terminated gracefully before anything unexpected occurs (such as a crash due to a critical component of yours having been overwritten by a third-party install program's different version of that component).

Listing 20.3 gives you some sample code so that you can read the version number of a DLL.

   Type VS_VERSION

      wLength            As Integer

      wValueLength       As Integer

      szKey              As String * 16 ' "VS_VERSION_INFO"

      dwSignature        As Long        ' VS_FIXEDFILEINFO struct

      dwStrucVersion     As Long

      dwFileVersionMS    As Long

      dwFileVersionLS    As Long

      dwProductVersionMS As Long

      dwProductVersionLS As Long

      dwFileFlagsMasks   As Long

      dwFileFlags        As Long

      dwFileOS           As Long

      dwFileType         As Long

      dwFileSubType      As Long

      dwFileDateMS       As Long

      dwFileDateLS       As Long

   End Type

   Declare Function GetFileVersionInfo% Lib "Ver.dll" (ByVal Filename$, ByVal           _dwHandle&, ByVal cbBuff&, ByVal lpvData$)

   Declare Function GetFileVersionInfoSize& Lib "Ver.dll" (ByVal Filename$,       _dwHandle&)

   Declare Sub hmemcpy Lib "kernel" (hpvDest As Any, hpvSrc As Any, ByVal cbBytes&)

   Function HIWORD (X As Long) As Integer

      HIWORD = X \ &HFFFF&

   End Function

   Function LOWORD (X As Long) As Integer

      LOWORD = X And &HFFFF&

   End Function

   Function VerInfo$ (FullFileName$)

       Dim X As VS_VERSION

       Dim FileVer$

       BufSize& = GetFileVersionInfoSize(FullFileName$, dwHandle&)

       If BufSize& = 0 Then

          MsgBox "No Version Info available!"

          Exit Function

       End If

       lpvData$ = Space$(BufSize&)

       r% = GetFileVersionInfo(FullFileName$, dwHandle&, BufSize&, lpvData$)

       hmemcpy X, ByVal lpvData$, Len(X)

       FileVer$ = LTrim$(Str$(HIWORD(X.dwFileVersionMS))) + "."

       FileVer$ = FileVer$ + LTrim$(Str$(LOWORD(X.dwFileVersionMS))) + "."

       FileVer$ = FileVer$ + LTrim$(Str$(HIWORD(X.dwFileVersionLS))) + "."

       FileVer$ = FileVer$ + LTrim$(Str$(LOWORD(X.dwFileVersionLS)))

       VerInfo$ = FileVer$

   End Function

Listing 20.3 can be run with a function call like this:

   Sub Form_Load ()

       Text1.Text = VerInfo$("c:\windows\system\vbrun300.dll")

   End Sub

See the "MS Knowledge Base" article Q112731 for more extensive information. It's available via CompuServe (GO MSKB) or via the MS Developer Network CDs.

Finding Out What's in a DLL

Some useful tools are provided within Windows and the Software Development Kit (SDK) bundled with Visual C++, which enable you to examine DLLs to find out useful things about their contents. These tools are described in the following sections.

EXEHDR

One of the most useful tools is EXEHDR.EXE, which enables you to read and print the DLL's header information in an intelligible format. Applying EXEHDR to the DLL you produced using Visual C++ above, for example, gives the output shown in Listing 20.4.

   C:\SC_WIN>exehdr scalcwin.dll

   Microsoft (R) EXE File Header Utility  Version 3.20

   Copyright  Microsoft Corp 1985-1993. All rights reserved.

   Library:                  SCALCWIN

   Description:              SCALCWIN.exe

   Data:                     SHARED

   Initialization:           Global

   Initial CS:IP:            seg   1 offset 00a8

   Initial SS:SP:            seg   0 offset 0000

   DGROUP:                   seg   2

   Heap allocation:          0400 bytes

   Application type:         WINDOWAPI

   Other module flags:       Contains gangload area; start: 0x140; size 0xa00

   no. type address  file  mem   flags

     1 CODE 00000160 007bd 007bd PRELOAD, (movable), (discardable)

     2 DATA 000009c0 00170 00170 SHARED, PRELOAD, (movable)

   Exports:

   ord seg offset name

     2   1  0000  WEP exported, shared data

     3   1  040e  ___EXPORTEDSTUB exported, shared data

     1   1  07b0  SIMPLECALC exported, shared data

Listing 20.4 demonstrates that SIMPLECALC is indeed an exported function from your DLL, along with two other Windows functions: WEP and _EXPORTEDSTUB, which were built-in automatically due to the inclusion of the /GD parameter when the source code was compiled. You also can derive from this listing an alternative means of calling the function; the first column gives the ordinal number of the function so that you can call SIMPLECALC by number as function #1 within the SCALCWIN library.

There also is an optional /VERBOSE parameter that you can apply to get even more information, such as the Windows functions that SCALCWIN.DLL uses itself (imports) as part of its operation. If you apply this parameter, you can derive that SCALCWIN uses the functions shown in Listing 20.5 from the Windows Kernel (which I have shown in both ordinal and name form).

    KERNEL.1         (FATALEXIT)

    KERNEL.3         (GETVERSION)

    KERNEL.4         (LOCALINIT)

    KERNEL.15        (GLOBALALLOC)

    KERNEL.16        (GLOBALREALLOC)

    KERNEL.17        (GLOBALFREE)

    KERNEL.18        (GLOBALLOCK)

    KERNEL.19        (GLOBALUNLOCK)

    KERNEL.20        (GLOBALSIZE)

    KERNEL.23        (LOCKSEGMENT)

    KERNEL.48        (GETMODULEUSAGE)

    KERNEL.91        (INITTASK)

    KERNEL.102       (DOS3CALL)

    KERNEL.131       (GETDOSENVIRONMENT)

    KERNEL.137       (FATALAPPEXIT)

    KERNEL.178       (__WINFLAGS)

C++ Name Mangling

While on the subject of EXEHDR, there's one important thing to mention—try renaming the C source file shown in Figure 20.3 so that it has a CPP extension instead of a C extension. If you rebuild the SCALCWIN project once more using a CPP file name, you get the error shown in Figure 20.8.


Figure 20.8. Link failure caused by C++ name mangling.

This error occurs because of C++ name mangling (or name decoration, as Microsoft often describes it) where the compiler appends characters to the function name for its own internal purposes—which can render the function name unrecognizable to the calling program.

It's possible to get around this linking problem by using the _export keyword on the function prototype itself, as shown in Listing 20.6 (instead of declaring the function name in the DEF linker definitions file).

   /****************

    * SCALCWIN.CPP *

    ****************/

   int _export _far _pascal SimpleCalc(int Parm1, int Parm2);

   int _export _far _pascal SimpleCalc(int Parm1, int Parm2)

   {

       return (Parm1 * Parm2);

   }

However, even if you get around the linking problem, the effect of C++ name mangling is to mess up the EXEHDR Exports output, as shown in Listing 20.7.

   Exports:

   ord seg offset name

     1   1  0026  WEP exported, shared data

     2   1  045e  ___EXPORTEDSTUB exported, shared data

     3   1  0000  ?SIMPLECALC@@ZCHHH@Z exported, shared data

You need to add  extern "C"  to the function declaration to correct it :-

   /****************

    * SCALCWIN.CPP *

    ****************/

   extern "C" int _export _far _pascal SimpleCalc(int Parm1, int Parm2);

   extern "C" int _export _far _pascal SimpleCalc(int Parm1, int Parm2)

   {

       return (Parm1 * Parm2);

   }

HeapWalker

Another useful Windows tool found in the SDK is HeapWalker, which you can use to inspect the global heap (the system memory that Windows uses) and local heaps used by active applications or DLLs currently loaded into memory. It's useful for analyzing any memory allocation and deallocation effects when an application creates or destroys objects, so you can use it to verify that your application cleans up after itself properly when terminated—that is, it unloads all the DLLs it has used in order to free the computer's memory for other applications.

HeapWalker also is more than a simple Windows-based dump program; it can display graphical objects correctly rather than merely displaying screensful of bytes.

To see how HeapWalker works, exit Windows and reboot your machine so that you can be sure that no orphan memory allocations remain. Run Windows and HeapWalker alone before doing anything else, and then page down until you find the entries for SHELL in the OWNER column. If the correct Sort option has been chosen, you should see something like the lines shown in Listing 20.8 (probably with different memory addresses) for Windows 3.1x.

   ADDRESS  HANDLE   SIZE LOCK     FLG HEAP OWNER    TYPE

   806BD820 176E      512          D        SHELL    Code 1

   806BBDC0 1766     6752          D        SHELL    Code 2

   806BB3C0 175E     2560          D        SHELL    Code 3

   806B9FC0 1756     5120          D        SHELL    Code 4

   806B9D80 174E      576          D        SHELL    Code 5

   806B9C20 1746      352          D        SHELL    Code 6

   00015900 1716       32                   SHELL    Data

   0004BF80 170E     1120              Y    SHELL    DGroup

   0004B0C0 177E      864                   SHELL    Module Database

   0004D6A0 151E      128                   SHELL    Private

   00058960 1706      224          D        SHELL    Resource String

Minimize HeapWalker. Then choose Help | About from the main Windows menu and just cancel the resulting About dialog box. To perform this, a DLL had to be loaded; so if you maximize HeapWalker and search for the SHELL entries again, you should see that the following extra lines have appeared:

   8071EDE0 172E     3552          D        SHELL    Code 9

   00015680 150E      576          D        SHELL    Resource Dialog

   00059960 1FEE     2080          D        SHELL    Resource RCdata

These lines show that calling a Windows function has caused memory to be allocated but not freed afterward. You might wonder what these extra memory allocations are for; to find out, just double-click on the line that interests you. If you choose the Resource Dialog line, for example, you see a screen similar to Figure 20.9.


Figure 20.9. HeapWalker in action.

As you can see, the template for the dialog used for all the Help | About dialog boxes within Windows 3.1-supplied utilities has been located (in both graphical and as-loaded-into-memory formats). If you want to use this template within your own application, try using code like the code shown in Listing 20.9 to access the undocumented ShellAbout function.

   Declare Function ShellAbout Lib "C:\windows\system\shell.dll"

           (ByVal hWnd As Integer,

            ByVal Caption As String,

            ByVal Copyright As String,

            ByVal hIcon As Integer) As Integer     'All on one line!

   Sub Form_Load ()

      HInstance% = ShellAbout(hWnd, "My Name", "My Copyright", 0)

   End Sub

Likewise, if you choose the Resource RCData line within HeapWalker, you see a strange list of abbreviated names—none other than the credit list of Microsoft personnel responsible for Windows 3.1 development!

If you've never seen this list before, you can access the hidden list by choosing Help | About. Then press Ctrl+Shift and double-click about a millimeter to the left of the bottom left corner of the blue square in the Windows Flag icon. The first time, nothing happens. The second and third time you do it are much more interesting! The third time around gives you the results shown in Figure 20.10.


Figure 20.10. Hidden Microsoft credits scrolling in Win3.1 or Win 3.11.

This time around, it's Bill Gates as the MC. Other times you do it, you will get other illustrious 'Softies like Steve Ballmer or T-Bear....

A curious thing to note about this feature is that the credits text is not visible if you dump SHELL.DLL to text or view it with something like the Norton Utilities. This is because the credits text is encrypted by XORing each character with &H99 (99 hex), so HeapWalker has helped you detect something particularly unusual! If you want to prove this, run the code shown in Listing 20.10.

   Sub Form_Load ()

      ShStr$ = Space$(2100)

      Open "c:\windows\system\shell.dll" For Binary As #1

      'UNCOMMENT ONE OF THESE

      'Get #1, 38722, ShStr$       'Win3.1

      'Get #1, 38067, ShStr$       'WfW3.11

      Close

      For n = 1 To 2060

         tmp = Asc(Mid$(ShStr$, n, 1))

         If tmp >= &H20 Then

            Debug.Print Chr$(tmp Xor &H99);

         ElseIf tmp = 0 Then

            Debug.Print

         End If

      Next

   End Sub

As you can see, HeapWalker is a valuable tool that detects all sorts of objects that might be resident in memory as a result of your application's operation.

Special Note—Static versus Dynamic Linking

This section presents a detailed, behind-the-scenes account of the differences between static and dynamic linking, with an example of how to build one of each.

I will be using the same command-line tools for building both sorts—which demonstrates the similarities between the two methods and also might be useful if you are forced to use a low-speed/low-memory computer that sags under the strain of the full Visual C++ environment.

The most common examples of static-linked programs are MS-DOS ones, so I'll build a simple application where an MS-DOS BASIC program uses a simple C function to multiply two integers. I'll then implement the same functionality within Visual Basic 4.0 and a DLL, using the same command-line tools for a fair comparison.

Command-line tools, you might be saying, that's the hard way! Don't worry—it's not as hard as it looks at first sight!

Static Linking—An Example

Consider the Visual Basic-DOS program SCALCB.BAS shown in Listing 20.11. I've removed the user interface form SCALC.FRM and made it into a minimal, command-line program for the utmost simplicity.

    '——————

    ' SCALCB.BAS

    '——————

    DECLARE FUNCTION SimpleCalc% CDECL(BYVAL Parm1%, BYVAL Parm2%)

    DIM Parm1 AS INTEGER

    DIM Parm2 AS INTEGER

    INPUT Parm1

    INPUT Parm2

    PRINT "In BASIC, going into C..."

    PRINT SimpleCalc (Parm1, Parm2)

    PRINT "Back into BASIC!"

In summary, two variables are declared and given values by the user. They then are passed to a C function (SimpleCalc), which multiplies them and returns the answer to BASIC for output.

If you are unfamiliar with MS-DOS programming, note that a DOS program always assumes that it is the only application running and therefore does not need to specify a form to which to print. It just takes the input off the screen and throws the answer back out there afterward. Although Windows can provide multiple DOS sessions, each instance of DOS still behaves in this way within its respective window.

To compile this program, you use the BC.EXE compiler, which has a heritage going back even before Bill Gates' and Paul Allen's work with IBM in the early 1980s (when BC.EXE was known as BASCOM.EXE). This compiler is shown in Listing 20.12.

    C:\SMPLCALC>bc scalcb /o /d;

    Microsoft (R) Visual Basic (TM) for MS-DOS (R)

    Compiler - Professional Edition Version 1.00

    Copyright  1982-1992 Microsoft Corporation. All rights reserved.

    42901 Bytes Available

    42410 Bytes Free

        0 Warning Error(s)

        0 Severe Error(s)

This produces an object module—a skeleton of BASIC functionality without the explicit low-level machine code attached yet. A directory listing now includes something like these lines:

    SCALCB   BAS       298 06/06/95   15:58

    SCALCB   OBJ      1340 06/06/95   15:59

NOTE

The /o option is a compiler directive to produce a complete, stand-alone executable (instead of producing a small executable that uses the standard runtime executable supplied with BASIC). The /d option instructs the compiler to include some runtime error-checking code so that the machine won't lock up easily if a serious coding error is made.

Table 20.2 shows the full option list for the BASIC compiler.

Option


Function


/?

Displays command-line options

/A

Generates assembly listing

/Ah

Enables huge dynamic arrays

/C:n

Sets default COM buffer size

/D

Performs runtime error-checking

/E

Enables ON ERROR checking

/Es

Enables EMS sharing

/Fpa

Enables the alternate math pack

/G2

Generates code for 286

/G3

Generates code for 386

/Ib:n

Sets number of ISAM buffers; requires ASCII format source file

/Ie:n

Reserves non-ISAM EMS

/Ii:n

Sets number of ISAM indexes

/MBF

Supports Microsoft Binary Format numbers

/O

Compiles stand-alone EXE

/R

Stores arrays in row-major order

/S

Disables string compression

/T

Issues no compiler warnings (Terse)

/V

Tells ON EVENT to check each statement

/W

Tells ON EVENT to check each label

/X

Enables RESUME NEXT support

/Zd

Provides limited CodeView information

/Zi

Provides full CodeView information; requires ASCII format source file

Next, consider the matching C program, SCALCC.C, shown in Listing 20.13.

    /************

     * SCALCC.C *

     ************/

    int SimpleCalc(int Parm1, int Parm2);

    int SimpleCalc(int Parm1, int Parm2)

    {

        return (Parm1 * Parm2);

    }

In summary, two integer variables are received, multiplied, and returned to the calling program. This is compiled in a manner similar to the BASIC program shown in Listing 20.11, except that the C compiler CL.EXE is used instead. CL.EXE is supplied with Visual C++ and usually is found in the \MSVC\BIN directory. Listing 20.14 shows the compiler output.

    C:\SMPLCALC>cl /c /W4 /AL scalcc.c

    Microsoft (R) C/C++ Optimizing Compiler Version 8.00c

    Copyright  Microsoft Corp 1984-1993. All rights reserved.

    scalcc.c

This produces another object module—this time, a skeleton of C functionality. A directory listing now includes something like these lines:

    SCALCC   C         164 06/06/95   15:58

    SCALCC   OBJ       350 06/06/95   15:59

NOTE

The /c option is a compiler directive to compile the code and then stop without automatically invoking the subsequent link stage. The /W4 option directs the compiler to be as strict as possible with syntax errors or warnings. The /AL option instructs the compiler to use the large memory model, which means that four bytes are allocated for code and data addresses instead of the two bytes needed for pure 16-bit applications.

To get the full list of the 140 options for the C compiler, type cl /? and press Enter.

The next stage is to perform the static link. The BASIC and the C skeletons are joined together; the machine code routines to actually execute the program are copied from the Visual Basic-DOS library VBDCL10E.LIB and attached to the appropriate places on the combined skeleton. This is performed with the LINK.EXE program, as shown in Listing 20.15.

    C:\SMPLCALC>link

    Microsoft (R) Segmented Executable Linker  Version 5.60

    Copyright  Microsoft Corp 1984-1993. All rights reserved.

    Object Modules [.obj]: scalcb.obj +

    Object Modules [.obj]: scalcc.obj

    Run File [scalcb.exe]:

    List File [nul.map]:

    Libraries [.lib]: vbdcl10e.lib

    Definitions File [nul.def]:

This listing produces a stand-alone executable SCALCB.EXE:

    SCALCB   EXE     37012 06/06/95   15:59

This executable can be run by typing SCALCB at the DOS command prompt and pressing Enter. See the next section for what it looks like when run.

Dynamic Linking—A Similar Example

The process to create a dynamic link library is remarkably similar to that used for static-linked programs, except a few keywords are added to the program, the compiler parameters are slightly altered, and some extra definitions are added to the link stage in a separate DEF file.

Listing 20.16 shows the altered C program. The Microsoft proprietary _far and _pascal keywords have been added to the declaration in order to ensure that the parameters are not 16-bit and are passed in the correct order. In practice, you should include the WINDOWS.H header file and use the defined constants FAR and PASCAL, but I've bypassed that here for simplicity.

    /**************

     * SCALCWIN.C *

     **************/

    int _far _pascal SimpleCalc(int Parm1, int Parm2);

    int _far _pascal SimpleCalc(int Parm1, int Parm2)

    {

        return (Parm1 * Parm2);

    }

This is compiled in a manner similar to the original C program, as shown in Listing 20.17.

    C:\SC_WIN>cl /c /W4 /ALw /GD scalcwin.c

    Microsoft (R) C/C++ Optimizing Compiler Version 8.00c

    Copyright  Microsoft Corp 1984-1993. All rights reserved.

    scalcwin.c

As before, this listing produces an object module—this time, a skeleton of C DLL functionality. A directory listing now includes something like these lines:

    SCALCWIN C         196 06/06/95   16:55

    SCALCWIN OBJ       253 06/06/95   16:55

NOTE

The /ALw option is a modified form of the /AL compiler directive to compile the code for Windows; the /GD parameter automatically adds Windows library entry and exit routines. Furthermore, if you are using compilers from another manufacturer (such as Borland), you must change these parameters to the appropriate equivalents as used by that manufacturer.

An extra component that must be produced is the DLL link definitions file SCALCWIN.DEF. The contents are shown in Listing 20.18.

    LIBRARY   SCALCWIN                         ;Make a DLL...

    EXETYPE   WINDOWS                          ;     ... for Windows

    CODE      PRELOAD MOVEABLE DISCARDABLE     ;Build so that Windows can

    DATA      PRELOAD MOVEABLE SINGLE          ;   relocate it in memory

    HEAPSIZE  1024                             ;Free mem alloc'd to DLL

    EXPORTS   SimpleCalc    @1                 ;Available function(s)

              WEP PRIVATE                      ;Exit Procedure properties

Because you are producing a DLL that is independent of the calling application, you do not need to deal with the Visual Basic calling program at this stage. You merely link the C routine to the appropriate machine code libraries, as shown in Listing 20.19.

    C:\SC_WIN>link

    Microsoft (R) Segmented Executable Linker  Version 5.60

    Copyright  Microsoft Corp 1984-1993. All rights reserved.

    Object Modules [.obj]: SCALCWIN.OBJ

    Run File [SCALCWIN.exe]: SCALCWIN.DLL

    List File [nul.map]:

    Libraries [.lib]: libw +

    Libraries [.lib]: ldllcew

    Definitions File [nul.def]: SCALCWIN.DEF;

This listing produces a dynamic link library as follows:

    SCALCWIN DLL      2880 06/06/95   16:56

Figure 20.11 shows the dynamic-linked and static-linked programs running side by side.


Figure 20.11. Examples of dynamic-linked and static-linked programs.

The link between the BASIC code and the C code is made at build time for the static-linked program and at runtime for the dynamic-linked program.

Summary

In this chapter, you learned that Dynamic Link Libraries (DLLs) are core-important executable files containing functions that Windows applications can call to perform useful service tasks. You should use DLLs to extend the functionality or ultimate performance of your Visual Basic applications. Check the version number of your DLL when using it. Be aware of the tools in the SDK to analyze your DLLs.

Previous Page TOC Next Page