Chapter 19

VBScript and DLLs

by Ramesh Chandak


CONTENTS

The concept of DLL, which stands for dynamic link library, first appeared with the release of the Windows operating system. Simply put, a DLL is a collection of functions. It is dynamically linked at runtime with your application's executable and helps you extend the functionality of the base function call library of any Windows application. (See Figure 19.1.) This chapter introduces DLLs and discusses their advantages and disadvantages. It compares and contrasts DLLs with static libraries. You will learn more about the structure of a DLL. You will also learn about where and how you can define your own functions within a DLL. This chapter discusses VBScript's lack of support for DLLs and how it affects your code.

Figure 19.1 : A dynamic link library is loaded into memory at runtime.

DLLs are likely to create distribution nightmares. Just like any other software, a DLL will probably get upgraded over time. Consequently, you end up having several different versions of DLLs. When writing your installation utility, you need to consider the possibility of older versions of DLLs residing on your user's system and be careful when you install new versions of the same DLLs. With the introduction of 32-bit-based Windows 95, programmers and developers have to be aware of the 16- and 32-bit versions of the same DLLs. DLL management becomes important in such cases.

DLLs might have any file extension (.res, .fon, .exe, or .dll, for example). A DLL program does not include the main() or winmain() functions. Instead, it contains the functions Entry, LibMain, and Exit.

A large pool of third-party developers for Visual Basic and other Microsoft programming tools have created a tremendous number of enhanced controls and libraries for use with your Windows applications. Most of these controls and libraries are nothing but DLLs. Developers have created DLLs that handle not only complex mathematical calculations, but also provide enhanced user interface controls. A DLL is usually written using C or C++, which is a big advantage because it provides speed and efficiency. Using DLLs to handle numeric-intensive tasks, while using your favorite GUI tool for the user interface of your application, is highly recommended as it offers code modularity, speed, and efficiency.

Windows 95 has three main DLLs : gdi32.exe, user32.exe, and kernel32.exe. Windows 3.1 comprises of the same three DLLs, but uses their 16-bit versions: gdi.exe, user.exe, and kernel.exe.

NOTE
Usually DLL files have the .dll extension. Windows DLLs have the .exe extension; however, they are still DLLs.

Each DLL consists of function calls that constitute the entire Windows function call library. By default, these DLLs reside in the system subdirectory of your Windows directory (c:\windows\system, for example).

Windows 3.1 is a single-threaded, cooperative multitasking system. Unlike OS/2 or Windows NT, only one thread of execution can be active at any given time in a given application. The primary difference between a Windows application and a Windows DLL is that Windows tasks get entered into TDB-the task database. The task database is an internal Windows object. Therefore, a Windows application gets the resources, such as stack segment, message queue, file handle table, DOS path, and environment block, but a DLL does not get these items directly. The DLL borrows them from the Windows application that calls it.

Static Versus Dynamic Library

A static library is linked into your executable code. It is part of the executable, thus creating a larger executable file. A larger executable file requires more RAM and disk space. On the other hand, a static library is faster than a DLL because it has already linked into the executable and loaded into memory when the executable is first run. Before it can be used, a programmer-defined DLL must be loaded into memory, if that has not already occurred.

NOTE
A Windows DLL is already loaded when Windows starts up, so it does not have to be specifically loaded into memory at runtime.

A large executable takes more time to load into memory. Also, the static library has to have a link into every executable that needs it. Consequently, if you have three different executables that need a static library, this static library must be linked into all three executables. And if all three executables run at the same time, you have three copies of the static library loaded into the memory space, which makes for inefficient use of memory space. No code sharing exists across applications, which is where dynamic link libraries come into picture. A dynamic link library is loaded only at runtime. And only one copy of the library needs to get loaded into the memory space. The library can be shared across applications if more than one application needs it. An application can even unload the DLL if it is not needed at any given point in time, and thus free up some memory.

The executable file contains references to the functions in the DLL. If you create ten different programs that use the same library, you need to create only one DLL, which saves memory and disk space. Primarily, a DLL works to reduce the load image of an EXE, as well as share resources across multiple executables or instances of an executable. You can update these DLLs without re-linking because the executable contains references and not the actual code.

NOTE
Static libraries offer better performance. DLLs offer better memory management.

DLLs have been known to create distribution nightmares. Just like any other application is upgraded over time, a DLL is also likely to be upgraded. You may add new functions to a DLL or enhance existing ones. You may port a 16-bit DLL to a 32-bit or upgrade simply because the host application has been upgraded. To accommodate such scenarios, you need to consider in your installation utility that older versions of DLLs might reside on your user's system-so be careful when you install new versions of the same DLLs. Consider this real-world example. A Visual Basic 3.0 application was designed using Microsoft Access 1.1 as the back-end database. Another Visual Basic 3.0 application was designed that uses Microsoft Access 2.0 as the back-end database and the Jet Engine compatibility layer to make Visual Basic 3.0 talk to Microsoft Access 2.0. After installing the second application on the same machine as the first one, the first application stopped working. It gave an error message, Reserved Error (which is not documented in any of the Visual Basic or Microsoft Access documentation). It became very difficult to debug. Later it was realized that the first application did not work anymore because it was using the Jet Engine compatibility layer, although the database was still version 1.1-not upgraded to version 2.0. It was using the Access version 2.0 DLLs when in fact it should have been using the version 1.1 DLLs. The version 2.0 DLLs were resident in the same directory as the version 1.1 DLLs-the \windows\system directory.

Both static and dynamic libraries have advantages and disadvantages. The trend in Windows development utilizes DLLs. DLL technology is developed and promoted by Microsoft. Microsoft anticipates, expects, and hopes every user will use its products, from Microsoft Word to Visual FoxPro to Microsoft Access. Many of these applications utilize the same DLLs, which supports why DLL technology makes sense. If you are using Microsoft Word and Microsoft Excel, for example, and they utilize the same three DLLs, only one copy of each DLL gets loaded into memory.

In order for you to run a program that uses a dynamic library, the library must be present on the disk, either in the current directory, a directory accessible through the PATH string in MS-DOS, the Windows directory, or the SYSTEM subdirectory of the Windows directory. If the library does not appear in any of these locations, you get a runtime application error. The best place to install your application DLLs is in your application directory, and you should include it in the PATH string. Any Windows DLLs must be installed in the \windows\system directory.

With the introduction of 32-bit-based Windows 95, programmers and developers have to think about the 16- and 32-bit versions of the same DLLs. DLL management becomes important in such cases.

NOTE
DLL distribution and management becomes critical for the success of any DLL-based application. When you release upgrades and fixes, make sure the installation utility does not cause any unnecessary overwrites and deletes of existing DLLs.

Structure of a DLL

Any standard Windows application includes the main() or winmain() function. You can run an application by double-clicking its executable (.exe). The main() or winmain() function is the starting point for the application. Within the main() function, you include all the code necessary to perform the required tasks, including calling other functions or functions within a DLL. On the other hand, you cannot run a DLL simply by double-clicking on it.

The Entry Function

Once the Windows loader has loaded a DLL into memory, it transfers execution to the DLL's entry-point function, which performs whatever initialization the DLL needs to function properly. The name of LibEntry.Obj is different for different platforms. LibEntry's most important task is to initialize the DLL's local heap, if it has one. Without a local heap the DLL cannot use any of Windows' local memory management APIs. Once it has initialized the DLL's local heap, LibEntry usually calls a programmer-specified function to perform any additional initialization required by the DLL.

You may wonder why all initialization does not occur in LibEntry instead of calling a programmer-specified function. LibEntry is written in Assembly language for performance reasons. Any changes or additions to the LibEntry.Obj have to occur in Assembly language. Programming is easier in a high-level language, such as C or C++, than in Assembly language.

Writing the minimum code necessary in LibEntry using Assembly language is easier; then you call a function and write that function in a high-level language. LibEntry.Obj, a runtime object file, is linked by the built-in C++ compiler at build time. If this function fails, Windows unloads the DLL from memory; otherwise it calls LibMain. If LibMain returns false, Windows unloads the DLL from memory. Note that you cannot have multiple instances of the same DLL in memory; moreover, LibEntry and LibMain get called only once, no matter how many applications share the same DLL. If you want to perform additional initialization for each instance of your application, you should provide an exported function and call that function instead.

The LibMain Function

LibMain is a programmer-defined initialization function called by LibEntry. Because LibEntry performs initializations that are common to all DLLs, you can write your DLL-specific initialization in LibMain. Because this represents a separate function from LibEntry, you can write it in a high-level language instead of writing in Assembly language. Listing 19.1 shows the LibMain function.


Listing 19.1. The LibMain function.

/* You may modify it in any way you wish but do not remove Libmain and WEP.

Without them you will be  unable to link your DLL. */ 

#include <windows.h>

int _export LibMain( HANDLE hmod, WORD dataseg, WORD heap, LPSTR cmdline) 

{

    hmod = hmod;                  // these assignements generate no code

    dataseg = dataseg;            // but prevent compiler warnings about

    heap = heap;                  // unreferenced variables

    cmdline = cmdline;

    return( 1 );

}


Remember that this function is called only once. It can only perform an initialization that is independent of the application instances. You may want to use it to load resources, such as bitmaps or icons, or to create data structures that the DLL manages. Remember, however, not to write code in LibMain that depends on other previously loaded DLLs, because when several DLLs are to be loaded at one time, Windows does not load them in any guaranteed order.

The Exit Function

Windows Exit Procedure (WEP) is the last function of a DLL to be called before Windows unloads the DLL from memory. WEP performs any cleanup a DLL needs to do before it is unloaded and is called only once. When a DLL's usage count drops to zero, the Windows loader calls the DLL's WEP and then unloads the DLL. The usage count for an implicitly loaded DLL becomes zero after all instances of all applications that are currently using it exit. Listing 19.2 shows the WEP function.


Listing 19.2. The Windows Exit Procedure (WEP).

int _export WEP( int res )

{

    res = res;

    return( 1 );

}


Windows 3.0 requires this function for all DLLs. In Windows 3.1 and later versions, WEP is optional.

Programmer-Defined Functions

These functions enable you to actually implement the functionality you want-they represent the real workhorses of the DLL. Programmer-defined functions implement the functionality of DLLs in two varieties: exported and non-exported.

When declaring functions within a DLL, certain conventions should be followed, as specified in the Windows SDK. You need to use the ANSI keywords far, pascal, and export.

The FAR declaration helps Windows change the code segment of any program as required by the memory manager. The references to specific keywords such as FAR or NEAR do not apply in a 32-bit environment.

PASCAL function calls are more efficient than C function calls. With PASCAL function calls, the responsibility of managing parameters and cleaning up the stack remains with the called procedure. Under C function calls, the calling procedure takes this responsibility. Thus, the PASCAL function calls eliminate any duplication of code because multiple instances could exist of one procedure calling a single instance of another procedure.

The EXPORT keyword tells the compiler what functions in the DLL should be made visible and accessible to the world outside the DLL.

Exported Functions

Exported functions define the programming interface of a DLL and are meant to be called by applications and other DLLs. They usually represent the highest abstraction level a DLL provides to its callers. To implement these high-level services, exported functions often call non-exported functions that perform the necessary operations to support their functionality.

Exported functions must be declared as far because they do not reside in the segments from which they are called. They may, however, use any naming and parameter-passing conventions that pass parameters on the stack. Two popular conventions are pascal and cdecl. Conventions that pass parameters in the CPU's registers may not be used because prolog code uses the CPU's registers when the function is called. Also note that exported functions that return floating-point values or structures and objects larger than 4 bytes must use the pascal calling convention.

Non-Exported (Internal) Functions

Internal functions can only be called by other functions within the same DLL; applications and other DLLs cannot call them and do not need to be aware that they even exist. Because they are internal to a DLL, you can use only non-exported functions to implement the functionality of the DLL's initialization, termination, and exported functions. Non-exported functions should be used in DLLs just as they are in applications-to build a modular structure and to break down the complexity. Internal functions in DLLs can use any naming and parameter-passing conventions supported by your compiler.

Why Use DLLs?

DLLs represent important technology that has gained widespread use and acceptance since its inception. Many advantages to using DLLs exist. DLLs provide better memory management. A dynamic link library gets loaded only at runtime, and only one copy of the library needs to be loaded into the memory space. If more than one application needs it, the library is shared across applications. An application can even unload the DLL if it is not needed at any given time, which frees up memory. Consequently, you make efficient use of your system memory.

A wave of third-party libraries has hit the marketplace. Utilizing these existing DLLs directly with your application saves you a great deal of time and effort. It enables you to focus on the core of your application because the DLLs take care of some of the intricacies of your application.

A DLL is usually written using C or C++. Programs written in C or C++ operate quickly and efficiently, which improves the speed and response time of your application tremendously.

Consider this example. Your application involves complex mathematical calculations, and you are using a front-end GUI tool, such as Visual Basic or PowerBuilder, to build the user interface for your application. You can implement the mathematical calculations in two ways. You can write them as functions directly in Visual Basic or PowerBuilder, not the most efficient way to do it. On the other hand, you can write them as C or C++ functions embedded within a DLL, which is fast and efficient. Moreover, a DLL comprised of such functions can be used across different applications. You write it once and you use it multiple times, which encourages code sharing and reusability. Implementing the mathematical calculations this way makes the structure of your application modular. Furthermore, you can modify the functions within the DLL (without changing the name references) and do not have to worry about re-creating the executable. All you have to do is update your user's system with the latest version of the DLL.

A large pool of third-party developers exists for Visual Basic, the development environment for Windows. These developers have created a large number of third-party controls and libraries for Visual Basic. Most of these controls and libraries are nothing but DLLs. The developers have created DLLs that not only handle complex mathematical and geometrical calculations, but also provide enhanced user interface controls. Visual Basic 3.0's release included a basic set of user interface controls that left a big void to be later filled by the third-party developers. Examples of such controls include dropdown calendar, spin control, progress bar, etc. These controls can be programmed and created as part of a DLL, thus making them reusable across different applications. If you are using a third-party DLL, make sure you have all the necessary documentation on the DLL. Without proper documentation, you would have a hard time figuring out what functions are included within the DLL and what types of arguments those functions take.

NOTE
Even with the advent of OCX and ActiveX technologies, DLLs will exist for quite some time. The large established base of DLL-based applications cannot disappear so easily and quickly.

Disadvantages of Using DLLs

Although DLLs offer significant advantages, you need to know about certain drawbacks. Because DLLs are usually written in C or C++, a good programming knowledge of C or C++ becomes essential. Programming using C or C++ is not the easiest task. It has a steep learning curve, and understanding pointers and pointer management is important.

As indicated earlier, DLLs are likely to create distribution nightmares. For more on this topic, refer to the section titled "Static Versus Dynamic Library," earlier in this chapter.

Integrating DLLs with a front-end application written in a 4GL language calls for an effective implementation of error-checking protocol. An added component to debugging exists-in addition to debugging your front-end application, you also have to debug your DLL code if necessary.

A Sample DLL

Listing 19.3 shows the three main files for creating a DLL using any C++ compiler. These three files have the following extensions: cpp, def, and .rc. Listing 19.3 also shows the block of code that goes into these three files. Use the export keyword to denote what functions can be called by the calling application. Any function not declared with the export keyword is an internal function and cannot be called by the calling application.


Listing 19.3. The three main files for creating a Windows DLL.

BEGIN FILE : <dll.cpp>

#include <windows.h>

        

/*** Windows DLL Entrance & Exit Functions ***/

int FAR PASCAL LibMain  (HANDLE hInst, WORD wDataSeg, WORD wHeapSize, LPSTR lpszCmdLine)

{

if (wHeapSize > 0 )

     UnLockData(0);

return 1;

}



int FAR PASCAL _export WEP(bSystemExit)

int     bSystemExit;

{

   return 1;

}



/* enter the code for your function here */

int FAR PASCAL _export your_function(<param1, param2, param3,... ,paramN>)

{

   <the code for this function goes here>

}

END FILE dll.cpp



BEGIN FILE : dll.def               /* module definition file */

Library         dll_function       /* For WINDOWS EXE, use NAME */

                                   /* For WINDOWS DLL, use LIBRARY */

Exetype         WINDOWS

Description     'WINDOWS Sample DLL'

Stub            'WINSTUB.EXE'

Code            Shared Moveable Discardable Preload

Data            Single Moveable Preload

HeapSize        1024

StackSize       8192

Exports         WEP             /* identify the functions */

                dll_function    /* that are being exported */

END FILE dll.def



BEGIN FILE : dll.rc

Q7inf   RCData

Begin

     0x4337, 0x444D, 0x03E8, 0,0,0,0

End

StringTable

Begin   

     1000:"dll_function,1000"

End

END FILE dll.rc


NOTE
Creating a Windows DLL involves the use of three main files: .cpp, the C++ source code file; .def, the module definition file; and .rc, the resource file.

Next, you will undertake the task of writing an investment formula DLL. Depositing funds in an interest-bearing account represents a traditional method for accumulating savings. To quickly determine how much time is required to double a sum of money, you can apply an estimate commonly known as the Rule of 72. This estimate provides an approximation. The exact time depends on the compounding method being used.

The Rule of 72 says you divide 72 by the stated interest rate. The result shows the approximate number of years required to double a deposit. Listing 19.4 shows the Rule of 72 formula.


Listing 19.4. The Rule of 72 formula.

Formula : rule of 72

72 / Interest Rate = Years to double


For example, you deposit $5,000 in an account that pays 8 percent interest. It will take 9 years (= 72/8) to double to $10,000. Listing 19.5 shows the source code files for this DLL.


Listing 19.5. Source code for rule72.dll.

BEGIN FILE : rule72.cpp

#include <windows.h>

        

/*** Windows DLL Entrance & Exit Functions ***/

 int FAR PASCAL LibMain  (HANDLE hInst, WORD wDataSeg, WORD wHeapSize, LPSTR

 lpszCmdLine)

{

if (wHeapSize > 0 )

     UnLockData(0);

return 1;

}



int FAR PASCAL _export WEP(bSystemExit)

int     bSystemExit;

{

   return 1;

}



/* To simplify calculations integer values are used */

int FAR PASCAL _export rule72(int intrate)

{

   return(72/intrate);

}

END FILE rule72.cpp



BEGIN FILE : rule72.def            /* module definition file */

Library         rule72             /* For WINDOWS EXE, use NAME */

                                   /* For WINDOWS DLL, use LIBRARY */

Exetype         WINDOWS

Description     'WINDOWS Sample DLL'

Stub            'WINSTUB.EXE'

Code            Shared Moveable Discardable Preload

Data            Single Moveable Preload

HeapSize        1024

StackSize       8192

Exports         WEP             /* identify the functions */

                RULE72          /* that are being exported */

END FILE rule72.def



BEGIN FILE : rule72.rc

Q7inf   RCData

Begin

     0x4337, 0x444D, 0x03E8, 0,0,0,0

End

StringTable

Begin   

     1000:"RULE72(),1000"

End

END FILE rule72.rc


Use the small or medium memory model for compiling and creating Windows DLLs.

VBScript and DLLs

VBScript is a subset of Visual Basic, the programming language. Visual Basic uses the following convention for declaring and calling DLL functions. Because DLL procedures reside in a file external to your application, you have to give your application some information so that it can find and execute the DLL procedures you want to use. You provide this information with the Declare statement. Once you have declared a DLL procedure, you can use it in your code like any other procedure. Listing 19.6 shows the syntax for the Declare statement.


Listing 19.6. Syntax for Declare statement in Visual Basic.

Declare Sub publicname Lib "libname" [Alias "alias"] [([[ByVal] variable_ [As type]

[,[ByVal] variable 

[As type]]...])]

or

Declare Function publicname Lib "libname" [Alias "alias"] [([[ByVal] variable_ [As

type] [,[ByVal] 

variable [As type]]...])] As Type


If the procedure does not return a value, declare it as a Sub procedure. For example, Listing 19.7 shows the declaration of a subroutine, InvertRect, from the Windows DLL, user.exe.


Listing 19.7. Declaring the procedure-InvertRect.

Declare Sub InvertRect Lib "User" (ByVal hDC as integer, aRect as Rect)


If the procedure returns a value, declare it as a Function. For example, Listing 19.8 shows the declaration of a function, GetSystemMetrics, from the Windows DLL, user.exe.


Listing 19.8. Declaring the function-GetSystemMetrics.

Declare Function GetSystemMetrics Lib "User" (ByVal n as Integer) as Integer


Because VBScript is a subset of Visual Basic, you would expect that it supports the calling of DLLs. Unfortunately, as of this writing VBScript does not support it. This might change in the future. VBScript is the new kid on the block and continues to evolve as it gains wider acceptance. Future releases of VBScript might support DLL integration.

As previously stated, lack of support for DLL integration limits VBScript. Thousands of DLLs exist that can be utilized and integrated very easily. With no support for DLL integration, you may wonder what options you have. You would have to use the built-in structures for declaring VBScript functions and procedures instead. For example, you would have to define your own function, rule72(), in VBScript to replace the rule72.dll's functionality. Listing 19.9 shows the VBScript rule72() function.


Listing 19.9. VBScript function rule72().

<SCRIPT LANGUAGE="VBScript">

<!--Option Explicit



    Dim strMsgBoxTitle

    Dim bValidOrder

    Dim nyears



    Function rule72(int_rate)

       nyears = 72 / int_rate

       return nyears

    End Function



    Sub Window_OnLoad

       strMsgBoxTitle = "MSFTD"

       Call BtnInit_OnClick

    End Sub



....

....

....



-->

</SCRIPT>


NOTE
If Microsoft decides to include support for DLLs, you would probably use syntax and format similar to the preceding example for declaring them and calling the DLL procedures.

Another option is to use ActiveX controls. VBScript supports integration of ActiveX controls with HTML. In fact, Microsoft Internet Explorer 3.0 is the only browser in today's market that includes full support for ActiveX controls and VBScript. You can use Visual C++ or Borland C++ to write ActiveX controls-the same tools that you use to write DLLs.

NOTe
Microsoft might not include support for DLLs in VBScript because it is actively promoting its ActiveX technology. In such a case, you would need to convert existing DLLs into ActiveX controls for use with your VBScript code.

Relevant Web Sites

Table 19.1 lists Web sites for more information on DLLs and VBScript. Many resources and examples exist on the Internet.

Table 19.1. Relevant Web sites.

PurposeSite URL
Overview of dynamic link librarieshttp://ipserve.com/jzhuk/dll.html
Using Windows to create DLLshttp://www.awu.id.ethz.ch/~didi/wxwin/wx/wx53.html
Windows Developer FAQ on DLLshttp://www.r2m.com/win-developer-FAQ/dlls
VBScripthttp://www.microsoft.com/vbscript
VBScript linkshttp://www.microsoft.com/vbscript/us/vbsmain/vbslinks.htm

Review

DLLs represent important technology that has gained wide acceptance over the years. Although DLL technology is a precursor to OLE and ActiveX, it is still very much in wide use. Understanding how to write DLLs helps you extend the capabilities of your application and maintain code modularity.

You can link a library with the executable in two ways: statically and dynamically. Static linking provides better performance. In the case of a DLL, it has to be first loaded before it can be used. In static linking, you do not have to worry about maintaining different versions of the library because it is completely embedded and linked into the executable. In dynamic linking, DLL maintenance becomes critical, because you would end up having different versions of the same DLL over time.

DLLs may have any file extension (res, fon, exe, or dll). A DLL program does not include the main() or winmain() function. Instead it contains the functions Entry, LibMain, and Exit.

A pool of third-party developers for Visual Basic and other Microsoft programming tools has created a large volume of enhanced controls and libraries that you can use with your Windows applications. Most of these controls and libraries are nothing but DLLs. A DLL is usually written using C or C++, which provides speed and efficiency. You should perform mathematically intensive tasks using DLLs, and use your favorite GUI tool for building the user interface. Your application gains improved speed and response time. And code modularity is maintained.

As of this writing, the current release of VBScript does not support DLL integration, which indicates a very serious limitation in VBScript. If and when Microsoft includes support for DLLs, the syntax and format for declaring them and calling the DLL procedures would probably be similar to that discussed in the "VBScript and DLLs" section. Another option is to convert existing DLLs into ActiveX controls and integrate them with your VBScript code.