A Crash Course In Exporting From A DLL
With A Detailed Look At The DEF File
Using Visual C++ Professional 6.0
By: George Chastain
Date: 7/21/2000
When creating a new DLL, an Import Library (with a
".LIB" extension) is created.
This Import Library has to remain consistent with the DLL used
to create the library -- at least for the
code utilized by the client. Occasionally, you may find yourself delivering
multiple products to multiple
customers and some of these products may share a particular DLL. But what
happens if a customer
obtains a new version of one of those products but doesn't obtain new versions
of the other products
that use that particular DLL? It is possible that the new version of the DLL
delivered with the new
product could break the other, older product that the customer already has. This
can happen if
information contained within the new version of the DLL becomes inconsistent
with the information
recorded for the DLL by the Import Library used to link the other older
products.
Before describing how to help alleviate some the possibility of
this occurring, we will take a brief look
at what an Import Library contains.
The Import Library
The Import Library does not contain any code. It may be thought of as a
"road map" to the functions,
classes and other declarations provided by the DLL. When linking a client of a
DLL, the Linker needs
to record the information contained in this "road map" in the client
that will use the DLL. That
information will allow the client to locate the things provided, or exported, by
the DLL. See Figure 1.

Figure 1 DLL And Import Library
When a client of a DLL references something exported by the DLL, the client
utilizes the information
obtained by linking with the Import Library to find the item being referenced in
the DLL. There is no
actual code or resources in the Import Library. All of that is contained in the
DLL. The Import Library
just tells the client of the DLL where to find the things that the
client needs from the DLL.

Figure 2 Client Links To Import Library
But just how does a client find the stuff provided by the DLL?
Names And Numbers
When the Linker links a DLL, it assigns to the exported functions, classes or
data, unique names and
identifying numbers to all the exported items. For C++ functions and classes,
the unique names are
called Decorated Names. And the unique numbers assigned to the exported items
are called Ordinals.
Earlier, I mentioned a potential problem in which the Import Library used to
link a client can become
"inconsistent" with a newer version of the DLL associated with that
Import Library. To explain what
happens, we will take a look at two examples.
I will not go into detail on how to create a DLL using Visual C++. If you do
not know how to do this
you should review the subject in the MSDN library or any book on Visual C++
programming. The first
example we will look at exports in the conventional manner that most developers
are familiar with. I
have created a DLL project called ExportDemoDLL1. In that project I created a
header file called
MyFunctions.h. The contents are shown in Figure 3.

Figure 3 ExportDemoDLL1 MyFunctions.h
Notice that the DLL exports two functions using the "__declspec(dllex
A Crash Course In Exporting From A DLL
With A Detailed Look At The DEF File
Using Visual C++ Professional 6.0
By: George Chastain
Date: 7/21/2000
When creating a new DLL, an Import Library (with a
".LIB" extension) is created.
This Import Library has to remain consistent with the DLL used
to create the library -- at least for the
code utilized by the client. Occasionally, you may find yourself delivering
multiple products to multiple
customers and some of these products may share a particular DLL. But what
happens if a customer
obtains a new version of one of those products but doesn't obtain new versions
of the other products
that use that particular DLL? It is possible that the new version of the DLL
delivered with the new
product could break the other, older product that the customer already has. This
can happen if
information contained within the new version of the DLL becomes inconsistent
with the information
recorded for the DLL by the Import Library used to link the other older
products.
Before describing how to help alle
port)"
directive and a class using
the AFX_EXT_CLASS macro. Currently, the AFX_EXT_CLASS macro is simply defined to
be
AFX_CLASS_EXPORT by the Microsoft header file AFXV_DLL.h if a DLL is
being built. The
AFX_CLASS_EXPORT, currently, is itself defined to be __declspec(dllexport). If
an executable is
being built, Microsoft defines the AFX_EXT_CLASS macro as AFX_CLASS_IMPORT
which, in
turn is declared as "__declspec(dllimport)". You may occasionally see
classes written by developers
that make use of the AFX_CLASS_EXPORT or __declspec directly. However, you are
encouraged
to use the proper macro AFX_EXT_CLASS when creating a class to export in case
Microsoft changes
the way in which class exports must be made in a future version of Visual
Studio/Visual C++.
You will also notice the use of a preprocessor directive
"_EXPORTING". This, together with the use
of AFX_EXT_CLASS, makes it easy for you to create a single header file for use
by the DLL project
to export functions and data and for use by the client project to import the
functions and data. This helps
eliminate the need to maintain two separate header files. When you build your
DLL, specify the
preprocessor directive /D "_EXPORTING" in the list of compiler
options. Do not do this when
building the client and you will be able to use the same header file.
When the ExportDemoDLL1.dll is built, the exports are translated into
Decorated Names and Ordinal
Numbers as shown highlighted in the "Export Function List View"
windowpane of Dependency
Walker:

Figure 4 Export Function List View of Dependency Walker
The "Export Function List View" windowpane has two columns of
interest to us. The first is labeled
"Ordinal". This is the unique Ordinal Number assigned to the exported
function. The other column of
interest is labeled "Function" and it shows the unique Decorated Name
given to the exported function
within the DLL. The client makes use of this information when locating the
functions. The functions
are defined as shown in Table 1.
Table 1 ExportDemoDLL1 Exported Functions
|
EXPORTED FUNCTION |
ORDINAL |
DECORATED NAME |
|
Prod |
1 |
?Prod@@YAJJJ@Z |
|
Sum |
2 |
?Sum@@YAJJJ@Z |
|
CMyClass::CMyClass() |
3 |
??0CMyClass@@QAE@XZ |
|
CMyClass::~CMyClass() |
4 |
??1CMyClass@@QAE@XZ |
|
CMyClass::operator= |
5 |
??4CMyClass@@QAEAAV0@ABV0@@Z |
|
CMyClass::SAbout() |
6 |
?SAbout@CMyClass@@QAEXXZ |
|
CMyClass::SHowdy() |
7 |
?SHowdy@CMyClass@@QAEXXZ |
Later, I will explain how to obtain the Decorated Names for items you wish to
export and how to
convert Decorated Names to Undecorated Names.
You will notice in Figure 3 above that the class method CMyCLass::SAbout() is
implemented within
the class declaration. That is, the body is defined in the class declaration
instead of within the CPP file
for the class. When you fully define a class method within the class
declaration, it is normally treated as
an inline function. However, when you export an inline function with __declspec(dllexport),
the inline
function is always instantiated and exported, whether or not any module in the
client program references
the function. The function is presumed to be imported by another program. When
you export an entire
class using the AFX_EXT_CLASS macro as illustrated above you are, in effect,
exporting the inline
function SAbout(). This is why you see the method listed as an exported function
in the "Export
Function List View" of Dependency Walker
in Figure 4 above.
Now, assume that we have built a client application that makes use of this
ExportDemoDLL1 DLL.
Then we decide later to add a new function to the DLL and export it. We will try
this and add a
function called Sub() as shown in the new version of the header file in Figure
5.

Figure 5 New Version of MyFunctions.h
We now build the new version of the DLL and take a look at the exports in
Dependency Walker:

Figure 6 New Version of ExportDemoDLL in Dependency Walker
Look at what happened to the exported function Sum(). Its Ordinal Number is
now 8 where it was 7
in the previous version of ExportDemo1.DLL. If we do not re-link the client
application with the Import
Library created when building the new version of the ExportDemoDLL1.DLL the
application will be
looking for the function Sum() in the wrong location in the DLL!! The results
are unpredictable and
typically catastrophic. Further, you usually will have no clue as to why the
executable crashed! You
could spend a lot of time trying to debug this one.
Now that we understand the problem, what can we do about it? The answer is to
explicitly define
Ordinals for the exported functions so that the exported functions will always
receive the same ordinals
on every release of the DLL.
Defining Ordinals -- The DEF Way
For the next part of the discussion, we will assume that a new DLL project
has been created. I will refer
to it as ExportDemoDLL2. When you use the Visual C++ AppWizard to create a new
DLL project, the
wizard creates a file called the Module Definition File. The file has a
".DEF" extension and contains
information similar to that shown in the example in Figure 7.

Figure 7 Default DEF File
We will be making some additions to the contents of this file. But before we
do we need to remove the
export directives and macros from the header file MyFunctions.h that was created
for our first example,
ExportDemoDLL1. The header file for our new example DLL is shown in Figure 8. We
haven’t added
the function Sub() yet. We will do that shortly.

Figure 8 New MyFunctions.h For ExportDemoDLL2
Now that we no longer state that we want the entire class CMyClass exported
in the header (because
we removed the AFX_EXT_CLASS macro) you must add export directives to the DEF
file. The
required entries for the DEF file for ExportDemoDLL2 are shown in Figure 9.
Again, we will add our
function Sub() shortly.

Figure 9 New DEF File
I have explicitly assigned ordinals to the exported functions. They are shown
after the decorated names
following the "@" sign. Text appearing to the right of a semi-colon is
treated as a comment. The line
containing the keyword LIBRARY specifies the internal name of the DLL. The line
containing the
keyword DESCRIPTION defines a string to be written into an
.rdata
section of the DLL. This
description is different from the text inserted in the library by the Linker’s
/COMMENT option.
Now we are ready to build the new version of our DLL. But what happens now?!
We get an unresolved
external symbol error from the Linker! See Figure 10.

Figure 10 Unresolved External Symbol
I mentioned above that because we were exporting an entire class with
AFX_EXT_CLASS, the inline
methods are always expanded and exported just like any other class method whose
implementation is
provided in a CPP file. But now we have removed the AFX_EXT_CLASS macro from the
class
declaration. The inline method CMyClass::SAbout() will now remain treated as an
inline function. You
cannot export an inline function because there is nothing to export.
There are two solutions to this situation.
Option 1
We may remove the definition for CMyClass::SAbout() from the header file and
place it in the CPP file
MyFunctions.cpp. The new header file will then appear as shown in Figure 11.

Figure 11 MyFunctions.h Without Method Definition
When we do this we can successfully build the DLL and examine it in
Dependency Walker. You will
notice that the method SAbout() is listed as an exported function in the
"Export Function List View"
windowpane just as it was before.

Figure 12 ExampleDemoDLL2 With Non-Inlined Method Exported
Option 2
Or, we may simply remove the export line for the member function SAbout from
the DEF file. In this
case MyFunctions.h will remain as shown in Figure 8. The DEF file would then
appear as shown in
Figure 13. And since the header file MyFunctions.h will be included in source
code that references the
DLL, the method CMyClass::SAbout() will continue to be treated as an inline
function. But there is a
"gotcha" with this implementation so I recommend that you use the
first implementation. I will explain
why later when I discuss the pitfalls of using DEF Files.

Figure 13 DEF File Without Inlined Class Method
But for now, notice that in the "Ordinal" column of the
"Export Function List View" windowpane the
exported functions are assigned the ordinals I defined in the DEF file. Ordinal
numbers may be any
number between 1 and 65,535 inclusive. Ordinal numbers 4 and 5 are skipped in
the DEF file for the
example ExportDemoDLL2 so Dependency Walker displays them with no export entry.
As a matter
of good practice, you should number your exports sequentially.
Now, let us see what happens when we add the function Sub() to this new DLL
project like we did in
the example ExportDemoDLL1. I modified the header file to appear as shown in
Figure 14. Note that I
am going with the first option in the implementation of the method
CMyClass::SAbout().

Figure 14 New MyFunctions.h With Function Sub()
Then, I modified the DEF file as follows:

Figure 15 New DEF File With Export Entry For Function Sub()
After building the ExportDemoDLL2 again, we can re-examine the DLL using
Dependency Walker.

Figure 16 ExportDemoDLL2 In Dependency Walker With Function
Sub()
We now see the export for the function Sub() at the top of the "Export
Function List View" windowpane
of Dependency Walker with the assigned ordinal of 1. Notice that the ordinals
for the other exported
functions are unchanged from those shown in Figure 12 above.
If I were to now place this new DLL (with the added function Sub()) from the
example project
ExportDemoDLL2 with an application linked with the Import Library produced by
the version of
ExportDemoDLL2 created before function Sub() was added, the executable would
still run successfully.
The newly added function Sub() would simply be ignored by the application.
Exporting Global Variables
Exporting global variables is just as easy as exporting functions. They will
also be listed in the "Export
Function List View" windowpane of Dependency Walker along with the
functions and class methods
that are exported. The only thing to remember is not to define the global in a
header file that is included
in both the DLL and your client or you will get an error from the Linker that
the symbol is multiply |
defined.
As with the functions, you can export a global variable in two ways. The
first method of exporting a
global is to add a line to the header file as shown in MyFunctions.h in Figure
17. Then, in a CPP file of
your DLL project, define the global variable as you would any global variable.

Figure 17 DLL Global Variable Export/Import
When you use this header file to build the DLL and the client, it will make
the variable
"DLLGlobalVariable" global to both the DLL and the client. Or if you
wish to use a DEF file, you can
add an entry similar to the one shown in bold in Figure 18.

Figure 18 DEF File With Exported Global
You will still need to add an import statement in your client as shown in
Figure 19.

Figure 19 Import Statement When Accessing DLL Globals via DEF
File
You would not add anything to MyFunctions.h and you would define the global
in a CPP file of your
DLL as described above.
Mixing "dllexport" with DEF Files
It is perfectly acceptable to use both DEF files and __declspec(dllexport) to
define exports in a DLL
project. Just make sure that you do not attempt to export the same item both
ways or you will see a
warning from the Linker informing you that the export was specified multiple
times and that it is using
the first specification.
Nonames And Numbers
If you use a DEF file, you have the ability to perform some space
optimizations on your DLLs. The DEF
File allows you to specify the option "NONAME" as shown in Figure 20.

Figure 20 DEF File WIth NONAME Option
After building the DLL with this modification to the DEF file, we take
another look at the DLL in
Dependency Walker:

Figure 21 Exports With NONAME In Dependency Walker
Notice that the Decorated Names are no longer listed in the "Export
Function List View" windowpane
of Dependency Walker. This can be useful if you have a
large
DLL that exports
many
items. By not
recording the long Decorated Names, you can save a lot of space. However, if you
have any code that
utilizes explicit loading of the DLL, and thus calls GetProcAddress() with the
export function name, that
will no longer work. As a rule, you should not have to take advantage of this
feature unless you
absolutely must. Please refer to the MSDN documentation for details on the use
of GetProcAddress().
Export C++ Functions For Use By C Executables
If you are writing C++ functions to be exported and used by C executables,
you will need to export the
functions in the DLL with the extern "C" modifier. I have added a
function Div() to the header file
MyFunctions.h that I am exporting with the extern "C" modifier as
shown in Figure 22.

Figure 22 Export C++ Function For Use By C Executable
Again, since I defined the DECLSPEC preprocessor directive, I can use the
same header file to export
the function from the DLL and to import it into the client.
To do the same thing using a DEF file, you would add an export as shown in
Figure 23.

Figure 23 C Export in DEF File
Notice in Figure 23 that we did not specify a Decorated Name for the function
Div(). Decorated Names
are only used for C++ linkage specification. Since we are exporting with the
extern "C" linkage
specification, we do not enter a Decorated Name in the DEF file export entry.
You would then add an
import directive to your client as shown in Figure 24.

Figure 24 Client Import For Exported C Function
Exporting C Function For Use By C Or C++ Executables
This is a simple matter. All you have to do is define your imports in a
header file as shown in the following example
taken directly from the MSDN library:

Figure 25 Importing C Functions In C Or C++ Executables
Pitfalls of Using DEF Files
I stated earlier that there are some issues that you must consider before
implementing your DLLs using a DEF
file. I will outline these issues.
- The Decorated Names are version specific for the Visual C++ compiler. If
you install a new version
of Visual C++ or a Service Pack that delivers a new version of the Linker, the
Decorated Names may
change. Microsoft is free to change the decoration anytime they desire.
- Development must never change the ordinals once they are assigned to a
particular export. If you do,
clients built with previous versions of the Import Library will likely crash.
The results will be unpredictable.
- The Decorated Names are constructed using the exported symbol name and, in
the case of functions,
the parameter list and return type. Development must not change any of these
for exported items previously
delivered to a customer. If you do, the Decorated Names will change and create
an inconsistency with previous
versions of the Import Library.
- Avoid using inline functions including class methods that are fully defined
in the class declaration. An inline
function gets expanded into the application code when the application is
built; therefore, if you later rewrite
the function, it does not get updated unless the application itself is
recompiled. So if you change the
implementation of that function in a later version of the DLL, the application
that was linked with the original
version will still use the original implementation.
Obtaining And Converting Decorated Names
There are several ways in which you can obtain the decorated names for the
items you wish to export. One is to use
the dumpbin.exe utility.
- Open an MS-DOS window
- cd to the folder containing the object code for the DLL that you will
export.
- Enter the following on the DOS command line:
dumpbin –symbols *.obj > bindump.txt
This will dump tons of information about the object files in the current
folder into an ASCII text file.
- Search through the text file to locate the functions that you wish to
export. You will find the Decorated
Names for these along with the Undecorated Names scattered throughout the
ASCII dump text.
Another method for obtaining the Decorated Names is if you currently have a
DLL that exports items without the
use of the DEF file and you want to convert the DLL to use a DEF file. This
makes it easier. Build the DLL as it
currently is. Then follow the sameprocedure as outlined above but enter the
following at the DOS prompt instead
of the command shown for Step 3 above:
dumpbin –exports *.DLL > exports.txt
You can then easily cut-and-paste the exports into the DEF file for your DLL
project. Then remove all the existing
export directives and macros from the code as describe earlier in this
discussion.
If you ever need to convert a Decorated Name to its Undecorated equivalent,
Microsoft provides a utility to help
here as well. Visual Studio delivers a command-line application named
undname.exe. This application is provided
in the Common\Tools folder of your "Microsoft Visual Studio"
installation folder. To convert a Decorated Name to
an undecorated name follow these steps:
- Open an MS-DOS window.
- If you do not have the Common\Tools folder under "Microsoft Visual
Studio" in your system path, cd to
that folder.
- Enter the Decorated Name you wish to convert at the DOS prompt as shown in
the following example:
undname –f ?SHowdy@CMyClass@@QAEXXZ
The output of this example is shown in Figure 26. You can easily see that the
Decorated Name "?SHowdy@CMyClass@@QAEXXZ " is for the class method
"void CMyClass::SHowdy(void)."

Figure 26 Output of undname.exe
I hope that this discussion of exporting from DLLs has been informative. This
should get you started in creating
your own DLLs.
Constructive
comments? I would like to hear them.
|