How to Build and Use a Simple DLL

by Jun 6, 1996

 Technical Information Database

TI2195C.txt   How to Build and Use a Simple DLL
Category   :General
Platform    :All
Product    :C/C++  All

Description:
This document discusses how to create dynamically linked
libraries (DLLs) using version 5.0 of the Borland C++ compiler.
Topics covered include 16-bit and 32-bit DLLs, linking explicitly
or linking on startup, exporting functions and classes, ordinal
values, and module definition files.
All code designated as 16-bit in this article will compile
correctly for the 32-bit platform.
Creating the Simplest DLL
=========================
The steps below are all that is necessary to create a basic DLL.
You may read the following explanations if you so desire.
32-Bit
======
1. Click on the New Project toolbar button in the Borand C++ IDE.
   The New Target dialog appears.
2. Enter a project path and name the target "SAMPLE1".
3. Select "Dynamic Library [.dll]" as the Target Type.
4. Click OK.
5. In the Project window, delete the .RC and .DEF nodes.
6. In the .CPP file, type the following,
#include 
extern "C"
void __declspec(dllexport) WINAPI YourFunctionNumberOne ()
{
   MessageBox (NULL,
               "We are now inside your function in your DLL",
               "Your Message Title",
               NULL );
}
7. Click Build Project on the toolbar.  Your "SAMPLE1.DLL" is now
   complete.
16-Bit
======
1. Click on the New Project toolbar button in the Borand C++ IDE.
   The New Target dialog appears.
2. Enter a project path and name the target "SAMPLE1".
3. Select "Dynamic Library [.dll]" as the Target Type.
4. Select Platform as Windows 3.x (16).
4. Click OK.
5. In the Project window, delete the .RC and .DEF nodes.
6. In the .CPP file, type the following,
#include 
extern "C" void __export WINAPI YourFunctionNumberOne ()
{
   MessageBox (NULL,
               "We are now inside your function in your DLL",
               "Your Message Title",
               NULL );
}
7. Click Build Project on the toolbar.  Your "SAMPLE1.DLL" is now
   complete.
Calling the DLL from an EXE
===========================
1. Go to the Project menu and select New Target.
2. Enter a path and name the target "SAMPLE2".
3. Set the Platform to coincide with that of the DLL.
4. Click OK.
5. In the Project window, delete the .RC and .DEF nodes.
6. In the .CPP file type the following,
32-bit
======
#include 
extern "C"
void __declspec(dllexport) WINAPI YourFunctionNumberOne ();
int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int)
{
   YourFunctionNumberOne ();
	return FALSE;
}
16-bit
======
#include 
extern "C" void __export WINAPI YourFunctionNumberOne ();
int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int)
{
   YourFunctionNumberOne ();
	return FALSE;
}
7. In the Project window, right click on the "SAMPLE2.EXE" node.
8. Click on Add Node and add "SAMPLE1.LIB" to the project.
9. Make and run the project.
Extern "C"
==========
If you want it simple, use,
      extern "C"
in all function prototypes.  It suppresses name mangling.
Omitting this modifier on a DLL function's prototype can cause
much frustration.  When a C++ module is compiled, function names
are generated that include an encoding of the function's argument
types.  This is known as name mangling.  The syntax for such
mangling of function names is unique for each manufacturer's
compiler.  Using the 'extern "C"' modifier prevents name mangling
thus providing an easy way to export functions from a DLL.  By
not  mangling a function's name, other applications can call the
function in the DLL using exactly the same name as it appears in
the DLL source code.
In the above example, the extern "C" modifier can be omitted
from the function's prototype in both CPP modules with no
ramifications.  This is because both modules are compiled with
Borland C++ version 5.0, so the function name is mangled with
the same syntax in each module.  Omitting the extern "C"
modifier, however, would force applications not compiled with
Borland C++ version 5.0 to use the mangled name in order to call
the function within the DLL.  Please see the sections "Module
Definition Files" and "LoadLibrary and GetProcAddress" for how
to export C++ functions.
__declspec(dllexport) and __export
==================================
Functions
=========
The simplest way to make a function in a DLL visible to other
applications is to use on of the following modifiers:
  __declspec(dllexport) in a 32-bit DLL
  __export              in a 16-bit DLL
in the function prototype.  For efficiency, set the compiler
options for Entry/Exit code to "Windows DLL Explicit Functions
Exported" (-WDE,-WCDE).
Another technique is to list the function you want to make
visible to other applications in the EXPORTS section of a module
definition file.  If this is done and the __declspec(dllexport)
or __export modifier is not used in the function prototype, then
you MUST use the compiler option for Entry/Exit code "Windows DLL
All Functions Exportable" (-WD,-WCD).  Please don't overlook this
requirement.  If a function is made visible by listing it the the
EXPORTS section, but does not get the proper prolog code
generated, runtime crashes will occur.  Also please see the
section below, "Module Definition Files", for further
information.
Please refer to chapter six of the Borland C++ Programmer's
Guide, section "Prologs, epilogs, and exports: A summary" for
a complete discussion of exportable and exported functions.
Data
====
32-bit
======
In a 32-bit DLL an easy way to export data is the following:
   //yourdll.cpp
   int __declspec(dllexport) MyGlobalVariable;
   //yourexe.cpp
   extern int __declspec(dllimport) MyGlobalVariable;
When building YOUREXE.EXE, link in the import library for the
DLL (YOURDLL.LIB in this example).
16-bit
======
In a 16-bit DLL an easy way to export data is the following:
    //yourdll.cpp
    int __export MyGlobalVariable;
    //yourexe.cpp
    extern int __far  MyGlobalVariable;
    //yourexe.def
    .
    .
    IMPORTS
        YOURDLL.MyGlobalVariable;
If you don't want to use a DEF file and you want to load your
DLL explicitly,  you can use LoadLibrary and GetProcAddress as
follows,
    //yourexe.cpp
    HINSTANCE hinstDLL = LoadLibrary ("YOURDLL.DLL");
    int * pSomeVariable;
    pSomeVariable = (int *) GetProcAddress (hinstDLL,
                     "_MyGlobalVariable");
Classes
=======
The '__declspec(dllexport)' and '__export' modifiers can also be
used to export a class from a DLL.  As a 32-bit example, modify
the DLL source file SAMPLE1.CPP so it contains only the
following:
#include 
class __declspec(dllexport) YourClassNumberOne
{
 private:
	 int MemberOne;
 public:
	 static int MemberTwo;
         int FunctionGetMemberOne ();
};
int YourClassNumberOne::MemberTwo;
int YourClassNumberOne::FunctionGetMemberOne ()
{
   MemberOne = 55;
   return MemberOne;
}
Make the SAMPLE.DLL target.  By using the __declspec(dllexport)
modifier, all non-inline member functions and static data members
of the class are exported.
Now change the EXE source file SAMPLE2.CPP, to contain only the
following:
#include 
class __declspec(dllimport) YourClassNumberOne
{
 private:
	 int MemberOne;
 public:
	 static int MemberTwo;
         int FunctionGetMemberOne ();
};
int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int)
{
 YourClassNumberOne A;
 char szMessageString[40];
 int i = A.FunctionGetMemberOne ();
 wvsprintf (szMessageString, "The number is %i", &i);
 MessageBox (NULL, szMessageString, "Your Application", NULL);
 A.MemberTwo = 59;
 wvsprintf (szMessageString, "The number is %i", &A.MemberTwo);
 MessageBox (NULL, szMessageString, "Your Application", NULL);
 return FALSE;
}
Make and run SAMPLE2.EXE.
For 16-bit, use this same example, as above, but simply replace
     '__declspec(dllexport)' with '__export'
and
     '__declspec(dllimport)' with '__import'
One important caution.  Classes are a  C++ feature.  With that
feature comes name mangling.  Therefore all member functions of
exported classes will be mangled.  To call those member
functions from other applications, the mangled names must be
used.  In this example, since both SAMPLE1.CPP and SAMPLE2.CPP
are compiled with Borland C++ version 5.0, the member function
name is mangled with the same syntax.  Again, please see the
sections "Module Definition Files" and "LoadLibrary and
GetProcAddress" for cases necessitating this precaution.
WINAPI
======
In the 32-bit Windows header file the macro WINAPI is defined as
follows:
   #define WINAPI __stdcall
__stdcall is the 32-bit Windows API function calling convention.
It stipulates function parameters are pushed on the stack from
right to left, the called function cleans up the stack on
function exit, and the function name is not modified upon
compilation.
In the 16-bit Windows header file, the macro WINAPI is defined as
follows:
   #define WINAPI __far __pascal
The Pascal calling convention is used for all 16-bit Windows
API functions.  It stipulates parameters are pushed on the stack
from left to right, the called function cleans up the stack
upon exit and the function name is changed to all upper case upon
compilation.
The '__far' modifier specifies references to the function will
be made with 32-bit pointers (Segment:Offset).  This is necessary
since the Windows API functions will not exist in the local
segment of the calling application.
16-Bit Considerations
=====================
When compiled for 16-bit, Pascal function names are changed to
all upper case.  C is a case sensitive language, so in order to
call such a function from another application, all upper case
must be used in the function name.  By contrast, C function
names are not changed to upper case but are prefixed with an
underscore when compiled.  Similarly, in order to call a C
function from another application, the function must be specified
with an underscore added to the beginning of name.
In the above example, a link to the DLL is made using an
import library.  This technique does not require the use of
modified names for Pascal or C functions.  Explicitly loading a
DLL and finding a function's address at run time does require the
use of modified names for Pascal and C functions.  Please see
the section "LoadLibrary and GetProcAddress" below.
In addition, be sure to use the Pascal calling convention for
callback functions in 16-bit Windows.   For other functions, be
sure to prototype precisely the function in the module importing
a function exported from a DLL.  Don't mix Pascal, __stdcall and
C calling conventions.  Failure to heed these precautions would
result in stack corruption upon function exit manifested as
corrupted data and general protection faults.
Loading and Linking the DLL
===========================
Below are discussed three methods of linking to functions in a
DLL.  Please see the above section "__declspec(dllexport) and
__export" for how to link to data and classes exported from a
DLL.  This section includes,
   1. Import Libraries,
   2. the LoadLibrary/GetProcAddress functions
   3. Module Definition (.DEF) files.
Import Libraries
================
By including SAMPLE1.LIB in the SAMPLE2.EXE project above, the
SAMPLE1.DLL is loaded into memory automatically when SAMPLE2.EXE
is executed.  This is the simplest method to link a DLL with
another application.  SAMPLE1.LIB is called an import library
and can be created using the IMPLIB.EXE utility.  Import
libraries are created automatically in Borland C++ when compiling
a DLL from the IDE.
LoadLibrary and GetProcAddress
==============================
DLLs can be loaded explicitly in an application using the Windows
API function LoadLibrary.  After the DLL is loaded, the
application must call the API function GetProcAddress to find the
address for each function in the DLL it wants to call.
Please note this technique cannot be used for non-static member
functions of classes exported from a DLL.  This is due to the
hidden 'this' pointer in a member function's argument list.
Because of the hidden 'this' pointer, the return value from
GetProcAddress, FARPROC, cannot be cast into the correct
function pointer type.
Using the example from section "Creating the Simplest DLL",
1. Delete SAMPLE1.LIB from the SAMPLE2.EXE project.
2. The next step depends on the target.
32-Bit
======
3. Modify SAMPLE2.CPP so it contains only the following.
#include 
int PASCAL WinMain (HINSTANCE, HINSTANCE, LPSTR, int)
{
 HINSTANCE hinstDLL = LoadLibrary ("SAMPLE1.DLL");
 FARPROC lpYFNO = GetProcAddress (hinstDLL,
                                  "YourFunctionNumberOne");
 if (lpYFNO != NULL)
	(*lpYFNO)();
 else
	MessageBox (NULL, "No Go", NULL, NULL);
 FreeLibrary (hinstDLL);
 return FALSE;
}
If the extern "C" modifier had not been used in the function
prototype in the DLL, then the call to GetProcAddress would have
to include the mangled name of the function.
 FARPROC lpYFNO = GetProcAddress (hinstDLL,
                                  "@YourFunctionNumberOne$qv");
16-Bit
======
3. Modify SAMPLE2.CPP so it contains only the following.
#include 
int PASCAL WinMain (HINSTANCE, HINSTANCE, LPSTR, int)
{
 HINSTANCE hinstDLL = LoadLibrary ("SAMPLE1.DLL");
 FARPROC lpYFNO = GetProcAddress (hinstDLL,
                                  "YOURFUNCTIONNUMBERONE");
 if (lpYFNO != NULL)
   (*lpYFNO)();
 else
   MessageBox (NULL, "No Go", NULL, NULL);
 FreeLibrary (hinstDLL);
 return FALSE;
}
Please notice the function name had to be specified using all
upper case.  This is because the function was defined as PASCAL
in the DLL.  If PASCAL had not been used in the DLL, the function
would be defined as using the C-calling convention by default
In that case, the call to GetProcAddress would have to include
the original function name prefixed with an underscore.
 FARPROC lpYFNO = GetProcAddress (hinstDLL,
                                  "_YourFunctionNumberOne");
If the extern "C" modifier had not been used in the function
prototype in the DLL, then the call to GetProcAddress would have
to include the mangled name of the function.  For a PASCAL
function, it would be:
 FARPROC lpYFNO = GetProcAddress (hinstDLL,
                 "@YOURFUNCTIONNUMBERONE$QV");
And for a C function it would be,
 FARPROC lpYFNO = GetProcAddress (hinstDLL,
                  "@YourFunctionNumberOne$qv");
None of this name modification according to calling convention
occurs in 32-bit programs.  The PASCAL constant is defined in
the 32-bit Windows header as __stdcall.  So even if you recompile
16-bit code with a PASCAL modifier on a function, for 32-bit, the
new __stdcall calling convention will be used in the newly
compiled module.
Module Definition Files
=======================
Every Windows program requires a module definition (DEF) file.
Normally, if you don't provide one yourself, the linker will
use a default one.  In Borland C++ the default DEF file is
BORLANDCLIBSDEFAULT.DEF if BORLANDC is the directory in which
you installed the compiler.  See the Borland C++ User's Guide
for the Module Definition File Reference.  (Page 136 in version
4.5)
Functions can be exported from and imported into your program by
linking your own DEF file and making entries in its EXPORTS and
IMPORTS sections.  Exporting and importing by this technique is
not as simple as the others methods mentioned above.  The one
advantage of using a DEF file to export or import functions,
however, is the ability to assign them alias names.
To import functions into your application:
1.  Use IMPDEF to find the exported name and/or ordinal number
   of the function in the DLL which is exporting the function.
   When functions are exported from a DLL or EXE, they can be
   referenced by either their names or ordinal numbers.
   IMPDEF is a utility that "dumps" the functions exported  by a
   DLL or EXE, giving both their names and numbers.  You can use
   either to refer to these functions.
2. List the function names or numbers in the IMPORTS section of
   the calling program's DEF file.  For most cases, it is easier
   and safer to use name references.  So, for example, you run
   IMPDEF on MYFUNCS.DLL with:
      IMPDEF MYFUNCS.DEF MYFUNCS.DLL
   This would generate something like the following:
      LIBRARY     MYFUNCS
      DESCRIPTION 'MYFUNCS.DLL'
      EXPORTS
 WEP            @1
 _FunctionA     @2   ; FunctionA(char)
 FUNCTIONB      @3   ; FunctionB(char)
 @FunctionC$qc  @4   ; FunctionC(char)
  So using the information provided by IMPDEF, you could add the
  following to your program's DEF file:
 IMPORTS
   MYFUNCS._FunctionA    ; FunctionA(char)  cdecl extern "C"
   MYFUNCS.FUNCTIONB     ; FunctionB(char) PASCAL extern "C"
   MYFUNCS.@FunctionC$qc ; FunctionC(char) cdecl mangled C++
  Or, if ordinal numbers are used, then the IMPORTS section
  would look like:
      IMPORTS
        MYFUNCS.2
        MYFUNCS.3
        MYFUNCS.4
  Refering to functions by ordinal value is faster.
  The IMPORTS section can also designate alias names for
  functions to be used internally.  This eases importing of
  mangled names.
 IMPORTS
   ImportedFunction1=MYFUNCS._FunctionA
   ImportedFunction2=MYFUNCS.FUNCTIONB
   ImportedFunction3=MYFUNCS.@FunctionC$qc
3. Add the DEF file to your project.
To export functions from a DLL:
1. Keep it simple.  Use the __export keyword for all functions
   you wish to export and the -WDE/-WCDE compiler Entry/Exit
   code options, "Windows DLL Explicit Functions Exported".
2. Use the EXPORTS section of your module definition file to
   assign alias names to your functions if desired.  This is
   optional.
 EXPORTS
   Read1  =@__smread$qv     @1
   Read2  =@__smread$qcc    @2
   Write1 =@__smwrite$qv    @3
This technique can be a nice way to export C++ functions with
mangled names.  Exporting functions by ordinal also reduces
the memory required for the DLL to load.
3. Add the DEF file to your project.
LibMain, WEP, DLLEntryPoint
===========================
The Borland C++ compiler will supply default versions of these
functions if none are supplied in your DLL source.
LibMain and WEP are good places to allocate and free global
memory for your DLL.  LibMain is also good to initialize global
variables in your DLL.  Be sure to use GlobalAlloc with the
GMEM_SHARE flag when allocating global memory for your DLL.  If
you are using the Borland C++ memory suballocator ("new"
operator) then please see TI863 as mentioned below, about
changing the _WinAllocFlag variable.
DllEntryPoint is a place holder for a function defined in your
32-bit DLL.  The actual name must be specified on the linker
command line by the -entry switch.  This function replaces both
LibMain and WEP in a 32-bit DLL and consists of a 4-way switch
statement to handle process and thread attaching and detaching.
DllEntryPoint outline
=====================
BOOL WINAPI DLLEntryPoint (HINSTANCE hinstDLL,
                           DWORD     fdwReason,
                           LPVOID    lpvReserved)
{
  switch (fdwReason) {
    case DLL_PROCESS_ATTACH:
      // The DLL is being mapped into the
      // process's address space.
      break;
    case DLL_THREAD_ATTACH:
      // A thread is being created.
      break;
    case DLL_THREAD_DETACH:
      // A thread is exiting cleanly.
      break;
    case DLL_PROCESS_DETACH:
      // The DLL is being unmapped from the
      // process's address space.
      break;
  }
  return(TRUE);
}
Win32 calls this function whenever a DLL attaches to a process
and whenever a DLL detaches from a process.  The hinstDLL
parameter contains the instance handle of the DLL.  Like the
hInstance parameter to WinMain, this value identifies the virtual
address of where the view of the DLL was mapped in the process's
address space.  Usually, you'll save this parameter in a global
variable so that you can use it in calls that load resources
such as DialogBox and LoadString.
Please see the Win32 Online Help for further details of the
DLLEntryPoint function.  On the MSDN CD for October 95, there
is also a good article on the DllEntryPoint function in the
book "Advanced Windows NT", Chapter Seven Dynamic-LinkLibaries.
Associated Borland TI Documents
===============================
TI1718 - Configuring classes for use in DLL's and EXE's
         (16-bit issues)
TI1017 - Creating Windows DLL's using v3.1 of Object Windows
         (16-bit and OWL 1.
TN1840 - Class notes on C++ and DLLs
         Issues to consider when sharing a class between an .EXE
         and a .DLL.
         (An excellent discussion of how to export C++ classes
         from a 16-bit DLL.  This document must be requested by
         phone from a Borland C++ Tech Support engineer.)
         "Three ways to link with a Dynamic Link Library (DLL)"
         (Another discussion of the issues in this article)
TN2546 - Allocating memory with GlobalAlloc in a DLL, GMEM_SHARE
TI863  - Understanding Memory Allocation Under Windows
Books
=====
"Windows Programmer's Guide to DLLs and Memory Management"
Mike Klein, Sams Publishing, Carmel, IN, 1992
Borland C++ 4.5 "Programmer's Guide",
     "Writing Dynamic-Link Libraries", p. 201-206
     "Module Definition Files" .ff, p. 185-190.
Borland C++ 4.5 "User's Guide", p. 138-139, p. 143-146
"Advanced Windows NT", Chapter Seven Dynamic-Link Libraries.
Jeffery M. Richter, Microsoft Press, Redmond WA, 1994


Reference:


7/2/98 10:40:38 AM
 

Article originally contributed by