Linking to Functions in Third-Party DLLs

by Jul 31, 1997

 Technical Information Database

TI2407C.txt   Linking to Functions in Third-Party DLLs
Category   :General
Platform    :All
Product    :Borland C++  All

Description:
Linking to Functions in Third-Party Dynamic Link Libraries
======= == ========= == =========== ======= ==== =========
The use of dynamic link libraries, or DLLs, is fundamental to
Windows programming.  Windows itself is a set of DLLs; when you
call a Windows API function in your program, Windows finds the
address of the function at run-time.  Dynamic linking is not
limited to Windows API calls;any library used for static linking
can be rebuilt as a DLL.  One advantage of DLLs is that they
share a common file format, which is not the case for static
libraries.  Frequently, third-party static libraries such as
Microsoft's use file formats that cannot be read by TLINK.  If
the same library is available in DLL form, linking will usually
be possible.
This document discusses two methods for linking to functions in a
DLL: 1) import libraries, and 2) module definition files.  It
also addresses "undefined symbol" messages when attempting to
link DLLs and gives methods for resolving these errors.
Import Libraries
------ ---------
An import library contains linker symbols for the function names
in a DLL and reference information which the linker uses to
construct a table in your .EXE file.  At run-time, Windows uses
the table to resolve function calls.  You already use an import
library whenever you write a Windows program: the library
IMPORT.LIB is linked into your application to resolve calls to
the Windows API functions.  (To see this in your project, set the
"Show run-time nodes" option under Options | Environment |
Project View).
To build an import library for a DLL, use the IMPLIB utility:
        IMPLIB LibName DLLName
where "LibName" is the name of the import library and "DLLName"
is the name of the DLL you wish to link to. Then add the import
library to your project to link to the DLL.
Undefined Symbols
--------- -------
Of course, just as with static linking, there is no guarantee
that the linker symbols generated by your compiler in your .OBJ
file will match the linker symbols in the import library.  The
most common cause of "undefined symbol" error messages during
linking is a language mismatch -- your program is written in C++,
and the DLL is written in C.
Extern C
------ -
For most compilers, linker symbols in C are generated by simply
prepending an underbar to the function name.  In C++, a more
sophisticated scheme must be used to generate a unique symbol for
each declaration of an overloaded function; this is called "name
mangling."
Unfortunately, name mangling is not standardized, but varies from
one compiler vendor to another.  DLL vendors, aware of this
problem, generally try to write their products in C to facilitate
linking. However, if you are writing a C++ program, the compiler
will automatically generate name-mangled symbols for all function
names unless you explicitly tell it otherwise.
To turn off name mangling for a function name, declare the
function as extern "C":
        extern "C" void YourFunctionName(int);
If you are linking with multiple functions in a header file, you
can wrap the include directive within an extern "C" declaration:
        extern "C" {
                #include "somedll.h"
                };
Calling Conventions
------- -----------
What if the extern "C" declaration still leaves you with an
undefined symbol?  The first thing to check is whether you are
using the correct calling convention in your function
declaration.  If you think you are not using a calling
convention, you're wrong -- the compiler is assuming
a default calling convention.  The calling convention tells the
compiler how parameters are pushed on the stack, and how the
stack is cleaned up upon returning from a function call.  It also
tells the compiler how to generate linker symbols.
In Borland C++, the default calling convention is cdecl, where
parameters are pushed on the stack from right to left and the
caller cleans up the stack.  If you're programming in 16 bits,
however, you will often attempt to link with functions that use
the same calling convention as the 16-bit Windows API -- pascal,
in which parameters are pushed on the stack from left to right
and the called function cleans up the stack.  The cdecl
convention produces symbols that have a leading underscore and
are case sensitive.  The pascal convention produces symbols that
have no leading underscore and are not case sensitive -- i.e.,
all letters are uppercase.
In the 32-bit world, a different kind of mismatch is common.
The stdcall calling convention, used by the 32-bit Windows API,
produces symbols that are case sensitive, but with no leading
underscore. It is sometimes possible to force a symbol match by
changing compiler and linker options, but you have to be careful:
you need to match calling conventions as well as symbols. 
Suppose that while you're compiling in 16 bits, you turn off the
compiler option to generate underbars (Options | Project |
Compiler | Compiler Output) and you find that the undefined
symbol messages for the functions in your DLL have disappeared. 
You're probably safe, because most likely the DLL functions use
the cdecl calling convention, and the DLL compiler did not
generate leading underbars.  But if you're compiling in
32 bits, you've probably got a mismatch in calling conventions --
most likely, the DLL functions use the stdcall convention.
Borland C++ provides keywords that enable you to inform the
compiler of the appropriate calling convention for a function. 
For example, a declaration for an externally defined pascal
function would look like this:
        void _far _pascal YourFunctionName(int);
(The _far modifier tells the compiler that the function
definition is in a far data segment, always the case with 16-bit
DLLs.)
For a stdcall function, the declaration would be
        void __stdcall YourFunctionName(int);
For portability, you may use the WINAPI macro for both
declarations:
        void WINAPI YourFunctionName(int);
WINAPI is defined as _far _pascal in 16 bits, and __stdcall in 32
bits.
Seeing What the Linker Sees
------ ---- --- ------ ----
 
If you have been unable to resolve an undefined symbol with the
methods discussed so far, it's time to look and see what linker
symbols are being generated by the compiler and compare them with
those in the DLL.  Borland C++ has two tools for this purpose:
TDUMP and IMPDEF.
Run TDUMP on your .OBJ file with the -m option to show C++ name
mangling and the -oiextdef option to show externally defined
symbols. You can also use -oipubdef to see what symbols are being
made public by an .obj file.
        tdump -m -oiextdef yourobj.obj > yourobj.lst
It should be an easy matter to identify the "undefined symbols"
the linker is complaining about.  With name mangling (-m option)
turned on, TDUMP shows a symbol as the linker sees it, with
additional characters appearing before and after the function
name.  For example, the function declaration
        extern void foo()
becomes
        @foo$qv
IMPDEF takes two parameters, the name of a module definition file
for the DLL, and the name of the DLL.
        impdef somedll.def somedll.dll
The output of IMPDEF is the module definition file somedll.def,
which contains an "EXPORTS" section listing the linker symbol for
each exported function, followed by an ordinal value of the
function. You'll probably recognize the function you're trying to
link to, and see why the linker is having trouble; the function
name within the symbol is the same, but the characters in which
it is embedded, if any, are different.
Resolving Undefined Symbols with Module Definition Files
--------- --------- ------- ---- ------ ---------- -----
Let's say the DLL's compiler uses a different name-mangling
convention, and has written the symbol for foo() as &foo%xy.  How
do you tell the linker that @foo$qv is the same as &foo%xy ?  It
can easily be done by aliasing the symbol in your program's
module definition file.  Edit the file to create an "IMPORTS"
section, and list each undefined symbol, followed by an equal
sign, the name of the DLL's module definition file without the
extension, and the symbol as it appears in that file.  For
example, if &foo%xy appears in the EXPORTS section of
SOMEDLL.DEF, you would alias @foo$qv as follows:
        @foo$qv=SOMEDLL.&foo%xy
References
----------
For further information, see
TI 1029, Resolving Undefined Linker Symbol Messages.


Reference:


7/2/98 10:40:50 AM
 

Article originally contributed by Borland Staff