
DLLS (Dynamic Link Libraries) are an important aspect of Windows. A DLL contains functions that your executable program can call during execution. In other works, a DLL is a libary of functions that your program can link with dynamically.
A link can be static or dynamic. Static links don't change. All the address information needed by your program to access the library function is fixed when the executable file is created and remains unchanged during execution.
Dynamic links are created as needed. When your program needs a function that is not in the executable file, Windows loads the dynamic link library (the DLL), making all of its functions available to your application. At that time, Windows resolves the address of each function and dynamically links it your application.
All Custom controls used in Visual Basic are DLLs. The only difference is that they require special handly in terms of messages received from Visual Basic.
1.1 - Why Use DLLs ?Here are four reasons why you might want to use a DLL:
- Access to C Run-Time Functions:The C run-time library has many useful fuctions that would not be available to Visual Basic programmers were it not for DLLs. For example, the _dos_getdiskfree function allows you to calculate the total amount of disk space and the free disk space available on a drive.
- Access to Windows API (Application Programming Interface) Functions that Require Callback Routines:Some Windows API functions require a callback function. A callback function is a function that Windows will call while executing the API call. An example of this sort of function is EnumTaskWindows, which will give the handle of all windows that are owned by a particular task.
- Speed:C is a fully compiled language that works at a level that is fairly close to native machine code. This means that the execution of the programs that are written ing C will be fast.
- Load on Use:Code and data from a DLL are loaded only when needed. A DLL can be organized such that only required parts are loaded as opposed to the entire DLL. This reduces the amount of memory required and the time taken to load.
Every DLL must contain a LibMain function and should contain a Windows Exit Procedure (WEP) in addition to the exported functions that can be called by the executable program.
- LibMain:A DLL must contain the LibMain function. The LibMain function is called by the system to initialize the DLL. LibMain is called only once -- when the first program that requires the DLL is loaded. The following are the parameters passed to LibMain:
- WEP:- HANDLE : Handle to the instance of the DLL.
- WORD : Library's data segment.
- WORD : Heap size.
- LPSTR : Command line parameters.
The WEP (Windows Exit Procedure) performs cleanup for the DLL before the library is unloaded. Although a WEP function was not required for every DLL in previous versions of the Windows operating system, for version 3.1 it is optional. A WEP should be included in the module definition file (.DEF) in Visual C, for example:
EXPORTS
WEP
- Exported Functions:
These are the functions your want to call from your DLL. They are denoted by _export. _export is used for backward compatibility. All the functions you want to call must also be listed in the (.DEF) file of your DLL.
1.3 - DLL Memory management issues.Use the large memory model.
C stores all variables as static or global (defined outside of a function) in the program's heap space, and C stores all other variables on the stack.
In the small and medium model, all pointers are near by default. This means that the data is accessed by 16-bit offsets to either the data segment (DS) register, or the stack segment (SS) register. Unfortunately, the compiler has no way of knowing whether the offset is from the DS or the SS. In most programs this would not be a problem because the DS and SS point to the same segment. A DLL, however, is a special case.
A DLL has its own data segament but shares its stack with the calling program. This means that the DS and the SS do not point to the same location. The easiest solution to this problem is to build the DLL in the large memory model where all variables are referenced by a 32-bit value.
Why Allocate Memory Dynamically?Allocating memory dynamically is a Windows-friendly technique. Declaring large arrays of data takes up space in either your program's stack, which is limited to 64k, or you program's Data Segment, which wastes disk dpace and Windows memory. It is better to ask Windows for the memory when you need it, and then free it when you have finished.
Allocating MemoryIn Windows, you can dynamically allocate two types of memory, local and global. Local memory is limited to 64k, and in the case of a DLL, local memory is shared with the program that called the DLL. Global memory is all of the memory available to Windows after it has loaded.
Local memory is allocated and managed using the LocalAlloc, LocalLock, LocalUnlock, and LocalFree functions -- as in this example:
char* pszBuffer;
...
pszBuffer = (char *) LocalAlloc (LPTR, 20);
...
LocalFree (pszBuffer);
It is faster to allocate local memory than it is to allocate global memory. But allocations from the local heap are limited to 64k, which must be shared amongst all programs that are calling to the DLL. It is best to use local memory when small, short lived blocks of memory are required.
Global memory is allocated and managed using the GlobalAlloc, GlobalLock, GlobalUnlock, and GlobalFree functions -- as in this example:
HGLOBAL hglb;
char* pszBuffer;
hglb = GlobalAlloc (GHND, 2048);
// GHND allocates the memory moveable and
// initialized to 0
// 2048 is the amount to be allocated...
pszBuffer = GlobalLock (hglb);
...
GlobalUnlock (hglb);
GlobalFree (hglb);
The GlobalAlloc function allocates memory in multiples of 4K.
If you want to share memory allocated in the DLL with other programs, you should allocate it using the GMEM_SHARED flag. If you want to share the memory through DDE, you must allocate it by using the GMEM_DDESHARE flag.
Be Careful When Storing Data in Static VariablesIf you try to store data in a DLL using global or static variables, don't be surprised if these values have changed when you call your DLL. The data stored in this way will be common to all applications that access this DLL. No matter how many applications use a DLL, there is only one instance of the DLL. The best way to get around this is to return structures from the DLL and pass them in again when they are needed.
File HandlesIt is not possible to share file handles between applications or DLLs. Each application has its own file-handle table. For two applications to use the same file using a DLL, they must both open the file individually.
Here are the steps necessary to build a DLL using Visual C++:
1. Start Visual C++.
2. Create a new project by choosing New from the Project menu. Select the following options:
- Set the Project Type to "Windows dynamic-link library (.DLL)" - Clear the "Use Microsoft Foundation Classes" check box.
You can also set or view these options later by choosing Project from the Options menu.
3. Add your existing .C and .DEF files to the project by using the dialog box that comes up when you choose Edit from the Prohec menu. Or enter your code directly in he Visual C++ editing window. (See the .C and .DEF example code listed below.)
4. From the Project menu, choose the Build
The following DLL contains the GetDiskInfo function, which can be called from Visual Basic. It will return the disk space available, the current drive name and the volume name.
C Code Example, DISKINFO.C:
#include#include int CALLBACK LibMain (HANDLE hInstance, WORD wDataSeg, WORD wHeapSize, LPSTR lpszCmdLine) { if (wHeapSize > 0) UnlockDate (0); //Unlocks the data segment of the library. return 1; } void __export CALLBACK GetDiskInfo (char *cDrive, char *szVolumeName, unsigned long *ulFreeSpace) { unsigned drive; struct _diskfree_t driveinfo; struct _find_t c_file; _dos_getdrive (&drive); _dos_getdiskfree( drive, & driveinfo ); if ( !_dos_findfirst( "*.*", _A_VOLID, &c_file )) wsprintf) szVolumeName, "%s", c_file.name); else wsprintf ( szVolumeName, "NO LABEL"); *cDrive = drive + 'A' -1; *ulFreeSpace = (unsigned long) driveinfo.avail_clusters * (unsigned long) driveinfo.sectors_per_cluster * (unsigned long) driveinfo.bytes_per_sector; }
Use the following DISKINFO.DEF file in Visual C++:
7
LIBRARY diskinfo
DESCRIPTION 'GetDiskInfo Can be called from Visual Basic'
EXETYPE WINDOWS 3.1
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE SINGLE
HEAPSIZE 4096
EXPORTS
GetDiskInfo @1
NOTE: The LIBRARY name in the .DEF file must be the same as the DLL filename, or else Visual Basic will give you "Error in loading DLL." For example, create the file DISKINFO.DLL using LIBRARY DISKINFO statement in the .DEF file above.
2.0 - Calling DLLs from Visual BasicIn Visual Basic, all functions, including DLL functions, that you want to call must first be declared by using the Declare statement. You can declare your functions in the declarations section of a Form or a Module. If you declare a DLL procedure or fucntion in a Form, it is private to that Form. To make it public, you must declare it in a Module. The following is an example Delcare statement:
Declare Sub getdiskinfo Lib "c:\somepath\diskinfo.dll" (ByVal mydrive As String, ByVal muvolume As String, free As Long)
You must enter the entire Declare statement as one, single line. This particular Declare statement declares the user-defined procedure GETDISKINFO located in the user-created DISKINFO.DLL file.
One you declare the function, you can call and use the function just as you would call and use a Visual Basic function.
2.1 - DLL ParametersBecause DLLs are typically written in C, DLLs can use a wide variety of parameters not directly supported by Visual Basic. As a result, when passing parameters, he programmer has to find the appropriate data type to pass.
Passing Arguments by Value or by ReferenceBy default, Visual Basic passes all arguments by reference. (When passing by reference, Visual Basic supplies a 32-bit far address.) However, many DLL functions expect an argument to be passed by value. This can be achieved by placing the ByVal keyword in front of the argument declaration.
8-Bit to 16-Bit Numeric PartametersPass 8-bit to 16-bit numeric parameters (int, short, unsigned, int, unsigned, short, BOOL, and WORD) as Integer.
32-Bit Signed Integer ParametersMBR>
Pass 32-bit numeric parameters (long, unsigned long, and DWORD) as LONG.
32-Bit Signed Integer ParametersPass 32-bit signed integer parameters as Currency or Double.
Object HandlesAll handles are unique 16-bit integer values associated with a Windows and are passed by value, so pass these parameters as Integer.
StringsStrings include the LPSTR and LPBYTE data types (pointer to characters or pointer to unisigned characters). Pass these parameters as (ByVal paramname As String). DLL functions cannot return Visual Basic strings. They do sometimes return LPSTRs, which can be copied into Visual Basic strings by using API functions.
To pass Visual Basic strings directly, pass them as (param As String).
NOTE: Visual Basic strings require special handling, so don't pass strings directly unless the DLL explicitly requires it.
Pointers to Numeric ValuesPass pointers to numeric values by simply not using the ByVal keyword.
StructuresIf the Visual Basic user-defined type matches the structure expected by the DLL, the structure can be passed by reference.
NOTE: Structures cannot be passed by value.
Pointers to ArraysPass the first element of the array by reference.
Pointers to functionsVisual Basic does not support callback functions, so DLL functions that have pointers to functions cannot be used with Visual Basic.
Null PointersIf a DLL expects a Null pointer, pass it as (ByVal paramname As Any). You can use &0 or &0H as the value of paramname.
2.2 - Trouble ShootingBelow are solutions to some problems you may encounter.
System Resources Keep Getting Lower After the DLL Is Called.If your DLL is using GDI objects, you must remember to free them after using them. This may not be obvious in Visual Basic, but when using the Windows SDK (software development kit) if you create a GDI object (for example, CreateBrushIndirect), you must delete it by using DeleteObject later on.
Bad DLL Calling Convention ErrorThis error is often caused by incorrectly omitting or including the ByVal keyword from the Declare statement. This error can also be caused if the wrong parameters are passed.
Error in loading DLLThis error occurs when you call a dynamic-link libraray procedure and the file specified in the procedure's Declare statement cannot be loaded. You can use the Microsoft Windows API function LoadLibrary to find out more specific information about why the DLL fails to load.
General Protection Fault (GPF)GPF faults occur when your program writes to a block of memory that doesn't belong to it. The two most likely reasons for this are:
A GPF fault can also occur when an incorrect varible type is passed to the DLL function.
2.3 - Example Visual Basic Calling Program
There are two parts to calling a DLL in a Visual Basic program. First you declare the function, and then you use it in event code.
Here is an example of a Declare statement. The Declare statement should be put in a module or in a form's General Declarations section
'Enter the following Declare as one, single line: Declare Sub getdiskinfo Lib "c:\dllartic\diskinfo.dll" (ByVal mydrive As String, ByVal myvolume As String, free As Long)
Specify ByVal statements exactly as shown, or else a GPF may occur.
Once the function is declared, you can use it in event code. The following example uses a function from the DLL in the Command1 Click event code:
Sub Command1_Click () Dim drive As String * 1 Dim volume As String * 20 Dim free As Long Call getdiskinfo(drive, volume, free) Text1.Text = drive Text2.Text = volume Text3.Text = Str$(free) End SubN.B:
Chris & Tim hold NO Copyright for this material.