You are currently offline, waiting for your internet to reconnect

How To Write C DLLs and Call Them from Visual Basic

This article was previously published under Q106553
This article has been archived. It is offered "as is" and will no longer be updated.
SUMMARY
This article outlines how to use DLLs with Visual Basic. It covers thefollowing issues:

Section A

  • 1.0 What Is a DLL?
  • 1.1 Why Use a DLL?
  • 1.2 Anatomy of a DLL.
  • 1.3 DLL Memory Management Issues.
  • 1.4 Building a DLL Using Visual C++.
  • 1.5 Example C DLL.

Section B

  • 2.0 Calling DLLs from Visual Basic.
  • 2.1 DLL Parameters.
  • 2.2 Troubleshooting.
  • 2.3 Example Visual Basic Calling Program.
MORE INFORMATION

SECTION A

1.0 What Is a DLL?

DLLs (Dynamic Link Libraries) are an important aspect of Windows. A DLLcontains functions that your executable program can call during execution.In other words, a DLL is a library of functions that your program can linkwith dynamically.

A link can be static or dynamic. Static links don't change. All the addressinformation needed by your program to access the library function is fixedwhen the executable file is created and remains unchanged during execution.

Dynamic links are created as needed. When your program needs a functionthat is not in the executable file, Windows loads the dynamic link library(the DLL), making all of its functions available to your application. Atthat time, Windows resolves the address of each function and dynamicallylinks it to your application.

All Custom controls used in Visual Basic are DLLs. The only difference isthat they require special handling in terms of messages received fromVisual 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 functions 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 programs that are well written in 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.

1.2 Anatomy of a DLL

Every DLL must contain a LibMain function and should contain a Windows ExitProcedure (WEP) in addition to the exported functions that can be called byan 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:
    - HANDLE : Handle to the instance of the DLL.
    - WORD : Library's data segment.
    - WORD : Heap size.
    - LPSTR : Command line parameters.
  • WEP:

    The WEP (Windows Exit Procedure) performs cleanup for a DLL before the library is unloaded. Although a WEP function was 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 you 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 defined as static or global (defined outside of afunction) in the program's heap space, and C stores all other variables onthe stack.

In the small and medium model, all pointers are near by default. This meansthat the data is accessed by 16-bit offsets to either the data segment (DS)register, or the stack segment (SS) register. Unfortunately, the compilerhas no way of knowing whether the offset is from the DS or the SS. In mostprograms this would not be a problem because the DS and SS point to thesame segment. A DLL, however, is a special case.

A DLL has its own data segment but shares its stack with the callingprogram. This means that the DS and the SS do not point to the samelocation. The easiest solution to this problem is to build the DLL in thelarge memory model where all variables are referenced by a 32-bit value.

Why Allocate Memory Dynamically?

Allocating memory dynamically is a Windows-friendly technique. Declaringlarge arrays of data takes up space in either your program's stack, whichis limited to 64K, or you program's Data Segment, which wastes disk spaceand Windows memory. It is better to ask Windows for the memory when youneed it, and then free it when you have finished.

Allocating Memory

In Windows, you can dynamically allocate two types of memory, local andglobal. Local memory is limited to 64K, and in the case of a DLL, localmemory is shared with the program that called the DLL. Global memory isall of the memory available to Windows after it has loaded.

Local memory is allocated and managed using the LocalAlloc, LocalLockLocalUnlock, 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 beshared amongst all programs that are calling the DLL. It is best to uselocal memory when small, short lived blocks of memory are required.

Global memory is allocated and managed using the GlobalAlloc, GlobalLockGlobalUnlock, and GlobalFree functions -- as in this example:
   HGLOBAL hglb;   char* pszBuffer;   hglb = GlobalAlloc (GHND, 2048);      // GHND allocates the memory as moveable and      // initialized to 0      // 2048 is the amount of memory 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, youshould allocate it using the GMEM_SHARED flag. If you want to share thememory through DDE, you must allocate it by using the GMEM_DDESHARE flag.

Be Careful When Storing Data in Static Variables

If you try to store data in a DLL using global or static variables, don'tbe surprised if these values have changed when you next call your DLL. Thedata stored in this way will be common to all applications that access thisDLL. No matter how many applications use a DLL, there is only one instanceof the DLL. The best way to get around this is to return structures fromthe DLL and pass them in again when they are needed.

File Handles

It is not possible to share file handles between applications or DLLs. Eachapplication has its own file-handle table. For two applications to use thesame file using a DLL, they must both open the file individually.

1.4 Building a DLL Using Visual C++

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 Project menu. Or enter your code directly in the Visual C++ editing window. (See the .C and .DEF example code listed below.)
  4. From the Project menu, choose the Build <yourname>.DLL option.

1.5 Example C DLL

The following DLL contains the GetDiskInfo function, which can be calledfrom Visual Basic. It will return the disk space available, the currentdrive name and the volume name.
      C Code Example, DISKINFO.C:   #include <windows.h>   #include <dos.h>   int CALLBACK LibMain (HANDLE hInstance, WORD wDataSeg, WORD wHeapSize,   LPSTR lpszCmdLine)   // The following is required only under Windows version 3.1   // Win32 does not require or support UnlockData()   {      if (wHeapSize > 0)         UnlockData (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++:
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." Forexample, create the file DISKINFO.DLL using the LIBRARY DISKINFO statementin the .DEF file above.

SECTION B

2.0 Calling DLLs from Visual Basic

In Visual Basic, all functions, including DLL functions, that you want tocall must first be declared by using the Declare statement. You can declareyour functions in the declarations section of a Form or a Module. If youdeclare a DLL procedure or function in a Form, it is private to that Form.To make it public, you must declare it in a Module. The following is anexample Declare statement:
   Declare Sub getdiskinfo Lib "c:\somepath\diskinfo.dll"      (ByVal mydrive As String, ByVal myvolume As String, free As Long)				
You must enter the entire Declare statement as one, single line. Thisparticular Declare statement declares the user-defined procedureGETDISKINFO located in user-created DISKINFO.DLL file.

Once you declare the function, you can call and use the function just asyou would call and use a Visual Basic function.

2.1 DLL Parameters

Because DLLs are typically written in C, DLLs can use a wide variety ofparameters not directly supported by Visual Basic. As a result, whenpassing parameters, the programmer has to find the appropriate data type topass.

Passing Arguments by Value or by Reference

By default, Visual Basic passes all arguments by reference. (When passingby reference, Visual Basic supplies a 32-bit far address.) However, manyDLL functions expect an argument to be passed by value. This can beachieved by placing the ByVal keyword in front of the argument declaration.

The following sections show you how to convert parameters to Visual Basic.

8- to 16-Bit Numeric Parameters

Pass 8- to 16-bit numeric parameters (int, short, unsigned int, unsignedshort, BOOL, and WORD) as Integer.

32-bit Numeric Parameters

Pass 32-bit numeric parameters (long, unsigned long, and DWORD) as LONG.

Object Handles

All handles are unique 16-bit integer values associated with a Window andare passed by value, so pass these parameters as Integer.

Strings

Strings include the LPSTR and LPBYTE data types (pointer to characters orpointer to unsigned characters). Pass these parameters as (ByVal paramnameAs String). To pass Visual Basic strings directly, pass them as (param AsString).

For additional information on passing strings between Visual Basic and a CDLL, please see the following article in the Microsoft Knowledge Base:
118643How to Pass a String or String Arrays Between VB and a C DLL
NOTE: Visual Basic strings require special handling, so don't pass stringsdirectly unless the DLL explicitly requires it.

Pointers to Numeric Values

Pass pointers to numeric values by simply not using the ByVal keyword.

Structures

If the Visual Basic user-defined type matches the structure expected by theDLL, the structure can be passed by reference.

NOTE: Structures cannot be passed by value.

Pointers to Arrays

Pass the first element of the array by reference.

Pointers to functions

Visual Basic does not support callback functions, so DLL functions thathave pointers to functions cannot be used with Visual Basic.

Null Pointers

If a DLL expects a Null pointer, pass it as (ByVal paramname As Any). Youcan use 0& as the value of paramname when calling the DLL.

2.2 Troubleshooting

Below 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 afterusing them. This may not be obvious in Visual Basic, but when using theWindows SDK (software development kit) if you create a GDI object (forexample, CreateBrushIndirect), you must delete it by using DeleteObjectlater on.

Bad DLL Calling Convention Error

This error is often caused by incorrectly omitting or including the ByValkeyword from the Declare statement. This error can also be caused if thewrong parameters are passed.

Error in Loading DLL

This error occurs when you call a dynamic-link library procedure and thefile specified in the procedure's Declare statement cannot be loaded. Youcan use the Microsoft Windows API function LoadLibrary to find out morespecific information about why a DLL fails to load.

General Protection (GP) Fault

GP faults occur when your program writes to a block of memory that doesn'tbelong to it. The two most likely reasons for this are:

  • You overstepped an array boundary. C does not check that the array subscript you are writing to is valid. Therefore, you can easily write to memory you don't own.
  • You are using a pointer to a memory location that you have freed. The best option is to assign NULL to all pointers after you free their memory.
A GP fault can also occur when an incorrect variable type is passed to theDLL function.

2.3 Example Visual Basic Calling Program

There are two parts to calling a DLL in a Visual Basic program. First youdeclare the function, and then you use it in event code.

Here is an example of a Declare statement. The Declare statement should beput 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 GP fault may occur.

Once the function is declared, you can use it in event code. The followingexample 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 Sub				
Properties

Article ID: 106553 - Last Review: 12/04/2015 09:49:26 - Revision: 2.1

Microsoft Visual Basic 3.0 Professional Edition, Microsoft Visual Basic 3.0 Professional Edition

  • kbnosurvey kbarchive kbhowto kbprogramming KB106553
Feedback