HOWTO: Write C DLLs and Call Them from Visual Basic

Last reviewed: October 10, 1997
Article ID: Q106553
The information in this article applies to:

- Standard and Professional Editions of Microsoft Visual Basic

  for Windows, version 3.0

SUMMARY

This article outlines how to use DLLs with Visual Basic. It covers the following 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 DLL contains functions that your executable program can call during execution. In other words, a DLL is a library 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 to your application.

All Custom controls used in Visual Basic are DLLs. The only difference is that they require special handling 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 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 Exit Procedure (WEP) in addition to the exported functions that can be called by an 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 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 segment 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 space 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 Memory

In 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 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 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, 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 Variables

If you try to store data in a DLL using global or static variables, don't be surprised if these values have changed when you next 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 Handles

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

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 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 <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 file name, or else Visual Basic will give you "Error in loading DLL." For example, create the file DISKINFO.DLL using the LIBRARY DISKINFO statement in the .DEF file above.

SECTION B

2.0 Calling DLLs from Visual Basic

In 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 function 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 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. This particular Declare statement declares the user-defined procedure GETDISKINFO located in user-created DISKINFO.DLL file.

Once 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 Parameters

Because 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, the programmer has to find the appropriate data type to pass.

Passing Arguments by Value or by Reference

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

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, unsigned short, 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 and are passed by value, so pass these parameters as Integer.

Strings

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

For additional information on passing strings between Visual Basic and a C DLL, please see the following article in the Microsoft Knowledge Base:

   ARTICLE-ID:Q118643
   TITLE     :How to Pass a String or String Arrays Between VB and a C DLL

NOTE: Visual Basic strings require special handling, so don't pass strings directly 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 the DLL, 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 that have 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). You can 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 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 Error

This 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 DLL

This error occurs when you call a dynamic-link library 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 a DLL fails to load.

General Protection (GP) Fault

GP 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:
  • 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 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 GP fault 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 Sub
Keywords          : GnrlVb kbprg kbfasttip
Technology        : kbvba
Version           : WINDOWS:3.00
Platform          : WINDOWS
Issue type        : kbhowto


================================================================================


THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY.

Last reviewed: October 10, 1997
© 1998 Microsoft Corporation. All rights reserved. Terms of Use.