http://www.mingw.org/wiki/sampleDLL
HOWTO Create and Deploy a Sample DLL using MinGW
A sample DLL
The DLL we will build will consist of a single source file "example_dll.cpp":
#include
#include "example_dll.h"
__stdcall void hello(const char *s)
{
printf("Hello %s\n", s);
}
int Double(int x)
{
return 2 * x;
}
void CppFunc(void)
{
puts("CppFunc");
}
void MyClass::func(void)
{
puts("MyClass.func()");
}
The following header "example_dll.h" declares the interface of the
DLL, and is used both when building the DLL and when building an
executable that uses the DLL:
#ifndef EXAMPLE_DLL_H
#define EXAMPLE_DLL_H
#ifdef __cplusplus
extern "C" {
#endif
#ifdef BUILDING_EXAMPLE_DLL
#define EXAMPLE_DLL __declspec(dllexport)
#else
#define EXAMPLE_DLL __declspec(dllimport)
#endif
void __stdcall EXAMPLE_DLL hello(const char *s);
int EXAMPLE_DLL Double(int x);
#ifdef __cplusplus
}
#endif
// NOTE: this function is not declared extern "C"
void EXAMPLE_DLL CppFunc(void);
// NOTE: this class must not be declared extern "C"
class EXAMPLE_DLL MyClass
{
public:
MyClass() {};
virtual ~MyClass() {};
void func(void);
};
#endif // EXAMPLE_DLL_H
Note that the three functions defined by the DLL have been declared slightly differently (for illustration purposes).
Building the DLL
To build the DLL use the following commands:
g++ -c -DBUILDING_EXAMPLE_DLL example_dll.cpp
g++ -shared -o example_dll.dll example_dll.o -Wl,--out-implib,libexample_dll.a
The -DBUILDING_EXAMPLE_DLL compiler option causes the DLL's functions
to be declared as "dllexport", meaning that they will be "exported"
from the DLL and available to client applications. The "-shared" option
tells the linker to create a DLL instead of an .exe, and the
"--out-implib" linker option causes an import library to be created,
which is used later on.
Note:
The import library created by the "--out-implib" linker option is
required iff (==if and only if) the DLL shall be interfaced from some
C/C++ compiler other than the MinGW toolchain. The MinGW toolchain is
perfectly happy to directly link against the created DLL. More details
can be found in the ld.exe info files that are part of the binutils
package (which is a part of the toolchain).
Building a client executable
The following source code "example_exe.cpp" demonstrates calling the DLL's functions:
#include
#include "example_dll.h"
int main(void)
{
hello("World");
printf("%d\n", Double(333));
CppFunc();
MyClass a;
a.func();
return 0;
}
To build the above example program, use the following commands:
g++ -c example_exe.cpp
g++ -o example_exe.exe example_exe.o -L. -lexample_dll
The option -lexample_dll specifies that the executable be linked with
the library libexample_dll.a. The option -L. instructs the linker to
search for libraries in the current directory, which is necessary
because "." is not in the default search path (this requirement can be
avoided by placing libraries in the MinGW lib directory).
It is worth mentioning that the same executable may be built without an import library using the following command:
g++ -o example_exe.exe example_exe.o example_dll.dll
If this method works for your application then there is usually no need for an import library.
Building and using a DLL without the dllexport/dllimport attibutes
If you pass the -no-undefined and --enable-runtime-pseudo-reloc
options to the linker, you don't have to add dllimport or dllexport
attributes to the source code that the DLL is made with ; all functions
are imported/exported automatically by default, just like in unices.
Is dllhelpers
http://cygutils.fruitbat.org/dll-stuff/index.html still a useful extra reference, or is it just too outdated? -- GregChicares?
Caveat
There is an important thing to note with the above example functions:
Both hello(const char *) and Double(int) are surrounded by
#ifdef __cplusplus
extern "C" {
#endif
and
#ifdef __cplusplus
}
#endif
in the header file. This has the rather important consequence that
their exported names use C-style name mangling (i.e. their names are "as
is").
The function CppFunc( void ) is not inside an extern "C" {...} block
and thus uses C++-style name mangling. This has the consequence that the
exported name depends on the compiler used to generate the DLL. It is
by design that such generated names are different from compiler to
compiler.
The important and often overlooked consequence of this is that from
the above DLL only the functions hello(const char *) and Double(int) are
seamlessly callable from e.g. MS VC++ while CppFunc(void) is not
(assuming the DLL is created by MinGW).
A similar statement goes for C++ classes exported in a DLL, i.e. for
the class MyClass. For further reading search the Web for the keyword
"ABI" and possible in conjunction with "C++". See also MixObjects.
Note: Someone else might want to provide more links here
A way to circumvent this problem is either using COM or create C-style wrapper functions to encapsulate the C++ ABI.
http://www.mingw.org/wiki/CreateImportLibraries
HOWTO Create an Import Library for a DLL using MinGW
This page is about how to "call into" an existing DLL, using your
mingw program. for information on creating your own DLL library, see
DLL.
Usually (read: for all DLLs created with MinGW and also a few others)
MinGW links fine against a DLL. No special import library is necessary
(see
sampleDLL).
However there are situations when it won't. Then one needs a
so-called import library to help the linker. The rest of this page gives
hints as to how to create such import libraries when there is only the
DLL available.
MinGW comes with a handy tool which does most of the work for you:
dlltool.exe (or i586-mingw32-dlltool or something similar in linux
cross compile). Issue 'dlltool --help' to get a brief description of the
available options.
Sometimes
dlltool
can create an import library from the DLL. But then you wouldn't need
an import library since the linker wouldn't need one. So you probably
are reading this because you've got a DLL against which MinGW can't link
without further help. You will provide this help by creating a custom
def-File. There are several ways to create such a file. In the following
some of these will be described:
run 'dlltool -z somedll.def --export-all-symbol somedll.dll' (without 's)
chances are high that you'll get something like 'dlltool:
somedll.dll: no symbols'. However, the resulting def-File is a good
starting point and probably will need only little additional tweaking.
use your favorite texteditor and create a file somedll.def with the following lines:
LIBRARY SOMEDLL.DLL
EXPORT
Here
is at least the list of
unsatisfied external references the linker did complain about and that
are supposed to be in somedll.dll. The list is given one name per line.
Once you've created the def-File (see above) you'll issue
dlltool -d somedll.def -l libsomedll.a
Depending on the DLL you may want to try one or more of the following:
add option -U
This will prefix all symbols in the import library with an underscore '_'
add option -k
This removes @
from the exported names but their calling convention remains stdcall.
add option -A
This adds an alias for all stdcall entries but without the
@
, i.e. you get both the undecorated names (as with -k) and the
decorated names.
For a detailed description of these options and dlltool take a look at the corresponding documentation.
That's it. The resulting file libsomedll.a should satisfy the linker.
Some Examples
def-file for an external lib created with Delphi
LIBRARY Diverg01.dll
EXPORTS
Divergences
LTDivergences
STDivergences
Excerpt from a def-file for IBM's db2cli.dll
LIBRARY DB2CLI.dll
EXPORTS
SQLAllocConnect@8
SQLAllocEnv@4
SQLAllocHandle@12
SQLAllocStmt@8
SQLBindCol@24
SQLBindFileToCol@32
SQLBindFileToParam@32
SQLBindParam@32
SQLBindParameter@40
SQLBrowseConnect@24
SQLBrowseConnectW@24
SQLBuildDataLink@40
SQLBulkOperations@8
...
Note that you can also use the pexports command to help (see the Python example in the
FAQ).
Note also that some dll's may just *not be compatible* -- for example
if size differences occur between long long in the different compilers,
and you attempt to pass out structs that have long longs. MSVC is
known to have different lengths than gcc for long double, some of its
versions of it have a difference for size_t, as well (other
discrepancies may be lurking, as well).
http://www.mingw.org/wiki/MSVC_and_MinGW_DLLs
MSVC and MinGW DLLs
Assume we have a testdll.h, testdll.c, and
testmain.c. In the first case, we will compile testdll.c with MinGW, and
let the MSVC-compiled testmain call it. You should use
gcc -shared -o testdll.dll testdll.c -Wl,--output-def,testdll.def,--out-implib,libtestdll.a
to produce the DLL and DEF files. MSVC cannot use the MinGW library,
but since you have already the DEF file you may easily produce one by
the Microsoft LIB tool:
lib /machine:i386 /def:testdll.def
Once you have testdll.lib, it is trivial to produce the executable with MSVC:
cl testmain.c testdll.lib
Now for MinGW programs calling an MSVC DLL. We have two methods. One
way is to specify the LIB files directly on the command line after the
main program. For example, after
cl /LD testdll.c
use
gcc -o testmain testmain.c testdll.lib
The other way is to produce the .a files for GCC. For __cdecl
functions (in most cases), it is simple: you only need to apply the
reimp tool from Anders Norlander (since his web site is no longer
available, you may choose to download
[this version|
http://wyw.dcweb.cn/download.asp?path=&file=reimp_new.zip]
enhanced by Jose Fonseca):
reimp testdll.lib
gcc -o testmain testmain.c -L. -ltestdll
However, for __stdcall functions, the above method does not work. For
MSVC will prefix an underscore to __stdcall functions while MinGW will
not. The right way is to produce the DEF file using the pexports tool
included in the mingw-utils package and filter off the first underscore
by sed:
pexports testdll.dll | sed "s/^_//" > testdll.def
Then, when using dlltool to produce the import library, add `-U' to the command line:
dlltool -U -d testdll.def -l libtestdll.a
And now, you can proceed in the usual way:
gcc -o testmain testmain.c -L. -ltestdll
Hooray, we got it.
This guide may also be helpful.
Here is an example of creating a .lib file from a mingw dll, in order to be able to use it from MSVC.
http://mirrors.zoreil.com/webclub.kcom.ne.jp/ma/colinp/win32/dll/make.html
Creating Dynamic Link Libraries
.def Files and Export Lists
When you want to create a DLL you must generate an export
list, which lists all of the functions which you will allow
programs to call directly. It is a good idea to keep the export
list for any one DLL to a managable size. You create an export
list by writing a
.def file, which you can use
as input to
the dlltool program
to create import libraries for your DLL and to create the special
export table that needs to be
linked
into your DLL.
A
.def file looks like this:
EXPORTS
Foo
Bar
That simply says that there are two functions, Foo and Bar,
which are exported by the DLL in question. Generally you won't
have to get much more sophisticated than that.
Generating an Import Library
For other programs to be able to use your DLL you need to
provide an import library which can be linked to those programs
(or other DLLs for that matter). The tool for generating import
libraries from
.def files is called dlltool and
is discussed in
its own section
in
the introduction to GNU
programming tools.
DLL Initialization in DllMain
When using Mingw32 to build DLLs you have the option to
include a function called DllMain to perform initialization or
cleanup. In general there will be an
entry
point function of some sort for any DLL (whether you use Mingw32
or not). In Mingw32 this entry point function is supplied by the
DLL startup code automatically linked to a DLL, and that startup
code calls DllMain. There is a default DllMain which will be
called if you don't supply one in your own code.
DllMain might look something like this:
BOOL WINAPI
DllMain (HANDLE hDll, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
// Code to run when the DLL is loaded
break;
case DLL_PROCESS_DETACH:
// Code to run when the DLL is freed
break;
case DLL_THREAD_ATTACH:
// Code to run when a thread is created during the DLL's lifetime
break;
case DLL_THREAD_DETACH:
// Code to run when a thread ends normally.
break;
}
return TRUE;
}
You put your code where the comments are. Notice that the
function always returns TRUE. This is really only important when
dwReason is DLL_PROCESS_ATTACH. If your code returns FALSE here
that means that the DLL failed to initialize properly (for
example, it couldn't allocate some memory it needed). The DLL
will not be loaded and DllMain will be called again with
DLL_PROCESS_DETACH immediately. If this DLL is being loaded
automatically (instead of with the LoadLibrary
function) that will make the loading of
the entire program fail.
DllMain in C++ Modules
If you are writing code in C++ you should note that DllMain
must
not be "name-mangled" like normal C++
functions are (name mangling is a process that makes it possible
to have two functions with the same name in C++ as long as they
have different arguments). You can prevent the mangling of the
DllMain function by adding
extern "C"
Before the name DllMain in the code above. You could also put
DllMain in a separate .c source code file (but then you would
have to worry about any calls made from inside DllMain to C++
code).
If you do not force DllMain to be a C function it will not be
found by the call in the Mingw32 start up code, and the default
DllMain in libmingw32.a will get called instead. If your
initialization code doesn't seem to be called, and you are
programming in C++, check that your DllMain function is either in
a separate C source file or is forced to be a C function with
extern "C".
Linking a DLL
Now, if you have created your .def file and have your object
files ready, you can create a DLL. I have to warn you though,
it's not pretty:
gcc -mdll -o junk.tmp -Wl,--base-file,base.tmp bar.o
del junk.tmp
dlltool --dllname bar.dll --base-file base.tmp --output-exp temp.exp --def bar.def
del base.tmp
gcc -mdll -o bar.dll bar.o -Wl,temp.exp
del temp.exp
This creates a relocatable DLL called
bar.dll
from the object file
bar.o and the
.def
file
bar.def. Relocatable DLLs are better,
because they can be placed anywhere in memory and you don't have
to worry about whether two DLLs in your program will be fixed at
the same base address (see below).
The first line generates an output file which we will discard,
called
junk.tmp, as well as a base file called
base.tmp.
A base file gives the information necessary to generate
relocation information for the DLL. This step would not be
strictly necessary if you didn't want a relocatable DLL, but it
is better this way, really.
After removing the
junk.tmp file the third
line invokes dlltool to build the
temp.exp file,
which contains the export table to be linked with the DLL, as
well as putting the relocation information in a form which can be
linked in and used by the operating system to relocate the DLL
(and also putting it in the
temp.exp file). If
you wanted to be really ambitious I think you could output the
import library at the same time, but why make things more complex
then they already are?
If you are exporting PASCAL functions without the @nn
extensions remember that
-k doesn't seem to work
in that line. See the below section for what you have to put in
the
.def file to get this to work.
Since the base file is no longer needed the fourth line
deletes it, and then the fifth line performs the final linking of
the object files containing the functions in the DLL and the
export table into the form of a complete DLL file. Then we delete
the export table file since we no longer need it. If all goes
well you should have a DLL file ready for use.
Probably you will want to automate this process using a make
file or some such method. With the Mingw32 version of Jam the
above process could be done from compiling
bar.c
to finished DLL and import library with the following line in a
Jamfile:
Dll bar.dll : bar.c : bar.def ;
Standard Call or PASCAL Functions
I talked a little about standard call or PASCAL calling
convention functions on the last page, but now I'll talk about
them a bit more. A calling convention is a way of arranging
information in memory, that is, on the "stack", before
calling a function, as well as a standard for who gets rid of the
information on the stack after it has been used. The C language
uses a convention where after a function returns the caller
cleans up the mess.
The PASCAL convention works sort of the other way around. The
function called cleans up the mess before it returns. The C
convention has the advantage that the called function doesn't
need to know how many arguments were passed (which makes the
magic of printf, and other variable argument functions,
possible). The PASCAL convention makes for smaller code, because
the code to clean up the arguments is only in one place (the
function), instead of in every function that calls the given
function.
GCC can generate function calls, and functions, that use the
PASCAL calling convention using the special
__attribute__((stdcall))
keyword sequence. In fact, the GNU Win32 API headers define
PASCAL, WINAPI and STDCALL constants to all be that magic keyword
sequence.
The problem for DLL writers is that internally GCC mangles
PASCAL functions with an at symbol (@) plus a number indicating
the number of bytes in the argument list. This prevents you from
making a call to such a function with the wrong number of
arguments, which would have that function destroying part of your
stack, causing crashes and mysterious errors. Instead you get a
link time error if you don't use the right prototype. However,
many times DLLs will export names without that @nn addition, and
there is no way to figure out what the number should be without
looking at the prototype of the function and counting bytes, or
trying to link a program which uses the function and seeing the
number in the linker error. Both of these methods are tedious if
you have a lot of functions to deal with.
If you want to create an import library where the DLL gives
names without the @nn but those functions are PASCAL then you
need to add the @nn to the function names in the .def file. For
example if the above Foo and Bar functions were PASCAL functions
that took single integer arguments the .def file might look like
this:
EXPORTS
Foo@4
Bar@4
Then you need to use the
-k option for
dlltool telling it to
kill the @nn for the exported
function names (but keep it for internal use). E.g.:
dlltool --dllname bar.dll --def bar.def --output-lib libbar.a -k
That takes care of the
program's side of the problem.
The program linked with the produced library will look for the
names without the associated @nn extension, and if the library
exports the names without @nn they will be found.
Unfortunately, when
building a DLL the
-k
option doesn't work! This line (see the section on linking a DLL
above)
dlltool --dllname bar.dll --base-file base.tmp --output-exp temp.exp --def bar.def -k
doesn't remove the @nn from the exported names in the created
DLL (this is a bug in my opinion). Fortunately you can use the
aliasing
feature of dlltool to get this to work. Change your
.def
file to look like this:
EXPORTS
Foo@4
Foo=Foo@4
Bar@4
Bar=Bar@4
What the extra lines say are "export a symbol Foo (or
Bar) pointing at the internal symbol Foo@4 (or Bar@4)." The
import library produced by this .def file will have four internal
symbols (available for use by your programs): Foo, Foo@4, Bar and
Bar@4, but if -k is used Foo and Foo@4 both link to the exported
symbol Foo, and similarly for Bar and Bar@4. The DLL produced
using this .def file will export four names, Foo, Foo@4, Bar and
Bar@4. The exported symbols all point to the internal @nn names.
The program will generate internal calls to Foo@4 and Bar@4,
which will be resolved in the import library. Those symbols in
the import library are, in turn, linked to exported names Foo and
Bar. The DLL exports Foo and Bar symbols which point to the
internal symbols Foo@4 and Bar@4, so everything works out in the
end.
If you've really been paying attention closely you may notice
that the above process is a little risky. If your program does
not properly prototype the functions as STDCALL (or PASCAL) then
it will try to link with Foo and Bar, and it will find them!
However, they will be connected to the same STDCALL functions in
the DLL as Foo@4 and Bar@4. Your program will corrupt the stack
every time it calls Foo or Bar, and probably crash shortly
afterwards.
To be completely correct and safe you have to use two
.def
files. One
.def file for creating the import
library and one for building the DLL. The import library
.def
file (call it
bar_i.def) looks like this:
EXPORTS
Foo@4
Bar@4
same as before, and the DLL building
.def
file (call it
bar_e.def) looks like this:
EXPORTS
Foo=Foo@4
Bar=Bar@4
Now when you build the import library using the first
.def
file like so:
dlltool --dllname bar.dll --def bar_i.def --output-lib libbar.a -k
it will contain only the symbols Foo@4 and Bar@4, which are
linked to the external names Foo and Bar.
Then when you build the DLL with
bar_e.def
like this:
dlltool --dllname bar.dll --base-file base.tmp --output-exp temp.exp --def bar_e.def
the DLL exports the internal symbols Foo@4 and Bar@4 with only
the names Foo and Bar. The DLL and the import library now match
up exactly, so the only errors you should get will be linking
errors... unless you forget the
-k when building
an import library, or use the wrong
.def file
somewhere...
http://mirrors.zoreil.com/webclub.kcom.ne.jp/ma/colinp/win32/dll/use.html
Using Dynamic Link Libraries
Using DLLs In Your Programs
In order to use a DLL you must have the appropriate header
files and/or prototypes for the functions you want to call, plus
the DLL of course, and a special library called an import
library. An import library is a lot like a normal library except
that instead of normal functions it contains an import table like
I described
in the introduction, and a
short function with the name of the function you really want to
call which looks up the correct address in the import table and
calls the real DLL function. By linking your program with an
import library you gain access to the functions exported by the
DLL for which the import library was created.
Using an import library is just like
linking with an ordinary library.
I talk about creating import libraries for your own DLLs in
the section on creating DLLs. But sometimes
you don't have an import library for a DLL that someone else gave
you, which is what the next section is about.
Using Other People's DLLs
What if you have a DLL but you don't have an import library
for it? At the moment this is the case for a lot of commercial
DLL libraries which provide import libraries for Microsoft
compatible compilers. At this time gcc does not understand
Microsoft .lib files correctly (although that will be fixed in
the future). To use the DLL you have to generate an import
library that GCC will understand. You do that
using dlltool in the same way as you
generate import libraries for your own programs. But before
you can do that you need a
.def file.
Fortunately there is a tool for automatically generating
.def
files called
impdef.exe which is discussed
in the section for DLL import
library tools in
the
introduction to GNU programming tools.
Note, however, that impdef isn't perfect. Some newer DLLs
confuse it so that it may not be possible to automatically
generate the
.def file, or the file won't work
without changes. Also, gcc requires that functions with PASCAL
calling convention (also called STDCALL or WINAPI functions) to
have @nn added to the end of the function name, where nn is the
number of bytes passed on the stack to the function in question
(if you didn't understand that don't worry, just think of it as a
magic number). Sometimes the functions will be exported with such
"mangled" names, in which case things should be fine
(as long as you don't use the -k option for dlltool). But in most
cases, including almot all of the Win32 API functions, the
mangled version is not exported. In this case you have to add the
@nn on to each function name manually. You can generally
determine the correct number by writing a program that uses the
function and trying to link it. gcc will report the correct
number in its linker error message (if the prototype is done
correctly). You also have to use the -k option when creating the
import library with dlltool, which I talk about a bit more in
the section on creating DLLs.
There are other tools that will show you the exports for a
DLL, including TDUMP from Borland and DUMPBIN (I think) from
Microsoft. Also, using Explorer in Windows 95 you can
Quick
View a DLL, and that will also give you a list of its
exports. Unfortunately you can't save it to a file or copy it to
the clipboard, so it's very inconvenient for creating import
libraries.
Global Variables in DLLs
With Win32 Microsoft introduced the idea that DLLs could
contain global variables which you could access the same way you
can access a function. The variables are also listed in the
import/export tables with the functions, but instead of pointing
to a function's code the variable entries point to the actual
spot in memory where the variable is stored.
Microsoft made a special keyword for Visual C++ which makes
the compiler generate the code to automatically follow the
pointer in the import table and get or set the real value of such
a variable whenever you manipulate it in your program. EGCS 1.1
and above should allow you to use the
__declspec
(dllexport) and
__declspec (dllimport)
syntax to use such variables. You can also access such variables
by performing a little magic in your header files, if you are
using another version of gcc for example. Say there was a
variable foobar exported by a DLL you use. Here's how you could
use it in your programs (assuming foobar is an int):
extern int* _imp__foobar;
#define foobar (*_imp__foobar)
Then you can go ahead and use foobar just like it was the same
variable all through your program, and the DLL will see the
changes you make in the variable as well. But, before you start
doing this all over the place a few words of warning:
- Older versions of dlltool used __imp_ as the prefix
instead of _imp__. The new prefix is more compatible with
Microsoft, and dlltool includes both for backward
compatibility, but these type of changes show just how
dangerous such a kludge can be.
- Using defines like that can cause strange errors, because
define has no concept of scope. So, for example, a C++
member function called foobar might get inappropriately
mangled by the define.
- You have to include the header file to access the
variable, unlike normal global variables which you can
declare inside individual source files or even functions.
- You can't use the same header file for making the DLL
itself, since it must deal directly with the foobar
variable.
All things considered, global variables should be avoided in
any case, and even more so when you are dealing with dynamic
linking. Still, it is possible to work with global variables in a
DLL, but just because you
can do something doesn't mean
you
should do it.
http://www.willus.com/mingw/yongweiwu_stdcall.html
Stdcall and DLL tools of MSVC and MinGW
The
__stdcall calling convention has been there for a very long
time. When the older calling conventions like
__pascal fell into
oblivion,
__stdcall became the standard calling conventions of Win32
API functions. Unlike
__cdecl (the
native calling convention
of C/C++), it is supported in C/C++, Visual Basic, Java, and other languages
alike, which makes it the first choice when building a DLL for cross-language
use.
The internal representations of both
__cdecl and
__stdcall
functions have
decorations. In MSVC (Microsoft Visual C++) and MinGW
(Minimalistic GNU for Windows) GCC,
__cdecl function will be prefixed
an underscore, and
__stdcall functions will have the beginning underscore
as well as be appended by the at-sign (
@) followed by the number
of bytes in the argument list. So,
double __cdecl sin(double)
will be decorated as
_sin, and
double __stdcall sin(double)
will be decorated as
_sin@8.
But things are not that simple. The decorations could change when they
appear in DLLs or when they are produced by different compilers. The following
table lists the names as are produced by MSVC, MinGW, Digital Mars C/C++
Compiler (DMC), Borland C++ Compiler/C++ Builder (BCC):
Calling Convention |
Internal* |
MSVC DLL (w/ DEF) |
MSVC DLL (dllexport) |
DMC DLL |
MinGW DLL |
BCC DLL |
__stdcall |
_Function@n |
Function |
_Function@n |
_Function@n |
Function@n |
Function |
__cdecl |
_Function |
Function |
Function |
Function |
Function |
_Function |
* For all but BCC, which has the same naming
convention for symbols in code and exported name in DLL.
What a mess (especially when you notice that, for MSVC, whether a
name is exported by a DEF file or by the
__declspec(dllexport)
attribute affects its
naming decoration)! And although the ending decoration clearly shows how
many bytes the called function pops up from stack beforereturning, it is
not necessarily the most frequently used form. E.g., the Win32 API
functions in the system DLLs have no decoration at all, as in the case
ones uses a DEF file when exporting functions with MSVC. Many DLLs
intended for multi-language usage also follow this practice and use no
decoration with
__stdcall functions (although the Java Native
Interface, on Win32 platforms, will accept functions either undecorated
or decorated in the Microsoft way, the latter being the preferred
form). The remaining part of this article is thus devoted to the
creation and use of such DLLs with MSVC and MinGW, as well as the
introduction and comparison of related tools (there are good articles at
http://www.bcbdev.com/articles.htm
explaining the complexities of DLLs with Borland C++ Builder, so I need
not bother to say anything more about it).
Tools working with DEF files
First, I will talk about the DEF file format and the relevant tools used
with MSVC and MinGW. Many intricacies lie here.
DEF file format
We care about only two sections of the DEF file: the LIBRARY section and
the EXPORTS section. The LIBRARY section specifies the internal name of
the DLL; and the EXPORTS section specifies the function or data items to
export. A short example follows:
LIBRARY testdll.dll
EXPORTS
cdeclFunction @1
_stdcallFunction@8 @2
aliasName = cdeclFunction @3
This DEF file defines three exports for a
testdll.dll:
the first one is a
__cdecl function, the second one a
__stdcall
function, and the third one an alias of the first function. The three functions
are also assigned
ordinals. A function can be called by its name
or its ordinal.
CL
CL can accept a DEF file on the command line, and it simply passes
the file name to
LINK. E.g.,
cl /LD testdll.obj testdll.def
will become
link /out:testdll.dll /dll /implib:testdll.lib /def:testdll.def testdll.obj
LINK
LINK is our most important tool when treating DLL and DEF files
with MSVC. The command line mentioned in
CL already shows the options
commonly used when creating a DLL with a DEF file. The main point is: if
we do not use a DEF file when creating a DLL, the exported name of a
__stdcall
function will be
_Function@n; but if we use a DEF
file, the exported name could be either
Function or
_Function@n;
if both names appear, only the undecorated form is used. But it is possible
to get both forms of exports (similar to the
--add-stdcall-alias
option of GNU
ld and
dllwrap), when we have the following
line in the EXPORTS section (notice that the order cannot be reversed):
TestFunction = _TestFunction@4
Another thing to notice when we use the above line:
LINK will export
both
TestFunction (or any alias name) and
_TestFunction@4
from the DLL, but
only _TestFunction@4 (the internal name)
will be output to the import library: in no case will a program linked
with the import library call the function by
TestFunction!
LIB
If we have the DLL from somebody else (no source available), and we have
the DEF file, the easiest way to create an import library is to use the
LIB
tool. The following syntax is often enough (check
MSDN
for more details):
lib /def:DEF_file
Nota bene: 1) it seems
LIB does not accept aliased forms
(it will simply ignore the part after the equal-sign); 2) it assumes all
functions in the DEF file
__cdecl. The second point lies in the
fact that the import library it produces will map each symbol in the DLL
to an internal name with an underscore prefixed, i.e., the linker using
the import library will try to resolve an undefined symbol
_Function
to the symbol
Function in the DLL. It takes no special care of
the
__stdcall calling convention. With some techniques we could
use
LIB to produce import libraries for
__stdcall functions,
but the caller could only call them by ordinal, not by name. The details
are left as an exercise
:-).
gcc
Here we use
gcc to call
ld. The reason why we do not use
ld
directly is that using
gcc is generally more convenient. The
-shared
option is specially designed to produce DLLs. We could also use the
-Wl
option to pass special link options.
ld
GNU
ld has many options regarding DLLs, but we shall only focus
on four (help information follows):
--add-stdcall-alias Export symbols with and without @nn
--kill-at Remove @nn from exported symbols
--out-implib Generate import library
--output-def Generate a .DEF file for the built DLL
Either
gcc or
ld can accept a DEF file directly on the command
line. When we have the following line in the EXPORTS section,
TestFunction = TestFunction@4
both symbols will be exported to the DLL. This behaviour is different from
dllwrap,
which we shall talk of immediately.
dllwrap
GNU
dllwrap could produce a DLL by a DEF file. We generally use
dllwrap
in the following syntax,
dllwrap --def DEF_file -o DLL_file OBJ_files [--output-lib LIB_file]
and
dllwrap will transparently call
gcc,
ld, and
dlltool
to fulfil its task. If
dllwrap is asked to produce an import library
(
--output-lib), it will let
dlltool do it.
Unlike
LINK or
ld, when
dllwrap encounters a line
in the EXPORTS section like
TestFunction = TestFunction@4
it will export
TestFunction but not
TestFunction@4, and
only
TestFunction is output to the import library (somehow similar
to
LIB).
dlltool
GNU
dlltool may be used to create the files needed to build and
use dynamic link libraries (DLLs). The following options are of interest
to us currently:
-l --output-lib Generate an interface library.
-D --dllname Name of input dll to put into interface lib.
-d --input-def Name of .def file to be read in.
-U --add-underscore Add underscores to symbols in interface library.
-k --kill-at Kill @ from exported names.
dlltool works like
LIB, but it has its special feature: the
-U
option makes the items in the DEF file map to symbols prefixed with an
underscore in the DLL, and the
-k option makes the
items in the DEF file map to symbols stripped of
@n in
the DLL.
pexports
This is a stand-alone open-source tool to produce a DEF file from a given
DLL. It is not distributed with MSVC or MinGW, and you may choose to download
here
if you do not find it elsewhere.
The __stdcall DLL and the import library
Having learnt so much about the tools, now we are ready to do what we wanted.
We still need
sed (search on the Internet if you do not already
have this useful tool), and a knowledge of regular expression is required
to understand thoroughly how it works.
Microsoft Visual C++
The simplest way to produce a DLL is to use the
/LD command-line
option of
CL:
cl /LD OBJ_files
The resulting DLL will have exported names like
_MyFunction@8,
as is shown in the `MSVC DLL (no DEF)' column above. To create symbols
with no decoration, we must use a DEF file. The following is an automatic
way to create a DEF file from the DLL if
__declspec(dllexport) is
used to indicate which functions to export:
link /out:DLL_file /dll OBJ_files
pexports DLL_file | sed "s/^_\([[:alnum:]_]\+\)@[[:digit:]]\+/\1/" > DEF_file
Once you have the object files and the DEF file, creating the DLL and the
import library can be done in one step:
link /out:DLL_file /dll /def:DEF_file /implib:LIB_file OBJ_files
And you are free to use the DLL and the import library now as you wish.
MinGW GCC
If we do not need to control which functions to export except by
__declspec(dllexport),
we can type:
gcc -shared -o DLL_file OBJ_files -Wl,--output-def,DEF_file
gcc -shared -o DLL_file OBJ_files -Wl,--kill-at
dlltool -d DEF_file --dllname DLL_file --output-lib LIB_file --kill-at
If we want to use a DEF file to control which functions to export, we can
start by (assuming
__declspec(dllexport) is used to indicate which
functions to export)
gcc -shared -o DLL_file OBJ_files -Wl,--kill-at,--output-def,DEF_file
to produce a DEF file with exports like "
Function =
Function@n
@Ordinal". After editing it to our will, the following commands
will finish the job:
dllwrap --def DEF_file -o DLL_file OBJ_files
sed "s/[[:alnum:]_]\+ *= *//" DEF_file > New_DEF_file
dlltool -d New_DEF_file --dllname DLL_file --output-lib LIB_file --kill-at
And the import library is now at your hand to use.
I am not sure whether I have stated clearly, but I have listed all my
findings when I struggled to find out how to use the DLL tools properly
and how to deal with
__stdcall functions in DLLs. Hope you find
it useful.
ACKNOWLEDGEMENT: The MinGW mailing list provided much useful
information; Luke Dunstan provided important suggestions and corrections.
http://www.transmissionzero.co.uk/computing/building-dlls-with-mingw/
Building Windows DLLs with Mingw
Introduction
One question I often get asked is how to create Windows DLLs using MinGW. Under MSVC it’s pretty simple, as Visual Studio will create a barebones project with everything you need. With MinGW a bit of manual work is needed, but it’s not that difficult when you know how it’s done.
This article will show you the basics of creating DLLs with MinGW, and then linking to them in your applications. The code we produce here will be usable in MSVC as well, so you or your end users can target multiple compilers.
DLL Basics
A DLL is a type of shared library used on Microsoft Windows and OS/2, containing functions which can be reused in any application which wants to make use of them (actually, DLLs can contain a lot more than just functions, but this article is about creating libraries of functions). If you have a library foo.dll which contains a function DoWork(), the DLL must export that function in order for it to be used by applications. Likewise, if an application bar.exe wants to make use of the function DoWork(), it must import that function from foo.dll (it is also possible to write code which loads a DLL once an application has started running, calls functions from that DLL, and then unloads the DLL when it’s no longer useful—this is how plugin / addon systems usually work). The exporting and importing of functions is fairly straightforward, and just involves a little bit of code accompanied by the correct linker command line.
The Inflexible and Inelegant Way
First we will create a DLL exporting only a very basic function which adds two integers and returns the result. Notice the “__declspec(dllexport)” attribute in the code below—this is the key to exporting functions from the DLL, and every function you would like exported from the DLL should be marked with this (any internal functions which don’t make up part of your API should be left as they are). Also notice the “__cdecl” before the function name, which declares the calling convention for the function (see the Wikipedia article on x86 calling conventions if you’re not familiar with them). The default calling convention for C functions in MinGW is cdecl, but it’s a good idea to always explicitly state the calling convention in case some other compiler has a different default—if this happens, your application will likely misbehave or crash as a result of calling one of these functions. Note that this first example is deliberately inflexible and inelegant in order to show exactly what’s going on behind the scenes. In the next section we will be hiding these details using preprocessor definitions.
/* add_basic.c
Demonstrates creating a DLL with an exported function, the inflexible way.
*/
__declspec(dllexport) int __cdecl Add(int a, int b)
{
return (a + b);
}
To compile this, it’s just necessary to pass the option “-shared” to the usual linker command line:
z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o add_basic.o add_basic.c
z:\Users\mpayne\Documents\MinGWDLL>gcc -o add_basic.dll -s -shared add_basic.o -Wl,--subsystem,windows
No errors should be reported, and you should now have a usable DLL. I’ve separated the compiler and link step in this example, although for such a small project it could have been done simply with “gcc -o add_basic.dll -s -shared add_basic.c -Wl,--subsystem,windows”. The “-Wl,--subsystem,windows” isn’t really necessary, but it’s just conventional that DLLs have the Windows GUI subsystem specified in their PE header. Also note the “-s” switch is used to strip symbols from the DLL—you’ll probably want to do this only for release builds.
You can now build an application which uses this DLL. All that is needed in order to use the function Add(a, b) is to declare the function with the “__declspec(dllimport)” attribute before using it (note that it’s dllimport for the client application, as opposed to the dllexport used in our DLL).
/* addtest_basic.c
Demonstrates using the function imported from the DLL, the inelegant way.
*/
#include
#include
/* Declare imported function so that we can actually use it. */
__declspec(dllimport) int __cdecl Add(int a, int b);
int main(int argc, char** argv)
{
printf("%d\n", Add(6, 23));
return EXIT_SUCCESS;
}
You can now compile and link this application, simply by referencing our DLL on the linker command line.
z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o addtest_basic.o addtest_basic.c
z:\Users\mpayne\Documents\MinGWDLL>gcc -o addtest_basic.exe -s addtest_basic.o -L. -ladd_basic
z:\Users\mpayne\Documents\MinGWDLL>addtest_basic.exe
29
The “-L” option specifies an additional folder that the linker should search for the DLL. In this case we want the linker to search the current directory, so we can just use a period, but for a real DLL which we have deployed to a system we would use the full path to its directory. The “-l” option specifies a DLL we want to import functions from—this should be the DLL filename without the file extension. Again, for such a small project the application could have been compiled and linked by doing a “gcc -o addtest_basic.exe -s addtest_basic.c -L. -ladd_basic”
The Flexible and Elegant Way
The above demonstrates very well what’s going on, but it’s less than ideal—in a real application you wouldn’t want to declare every single imported function in every single source code file where it’s used. Instead you would place the declarations in a header file and #include the header where needed. The only problem with this is that the client application requires the functions be declared “__declspec(dllimport)”, whereas when building the DLL you must declare them “__declspec(dllexport)”. Although you could use two separate headers, this can be a bit of a maintenance headache, so instead we use some preprocessor definitions.
/* add.h
Declares the functions to be imported by our application, and exported by our
DLL, in a flexible and elegant way.
*/
/* You should define ADD_EXPORTS *only* when building the DLL. */
#ifdef ADD_EXPORTS
#define ADDAPI __declspec(dllexport)
#else
#define ADDAPI __declspec(dllimport)
#endif
/* Define calling convention in one place, for convenience. */
#define ADDCALL __cdecl
/* Make sure functions are exported with C linkage under C++ compilers. */
#ifdef __cplusplus
extern "C"
{
#endif
/* Declare our Add function using the above definitions. */
ADDAPI int ADDCALL Add(int a, int b);
#ifdef __cplusplus
} // __cplusplus defined.
#endif
Notice that we have defined “ADDAPI” as “__declspec(dllexport)” if “ADD_EXPORTS” is defined, or “__declspec(dllimport)” otherwise. This is what allows us to use the same header for the applications and the DLL. Also notice we have defined “ADDCALL” as “__cdecl”, which allows easy redefinition of the calling convention we are using for our API. Now, anywhere in our code where we need to export a function from our DLL, we specify “ADDAPI” instead of a “__declspec(dllexport)” attribute, and “ADDCALL” instead of specifying, i.e. “__cdecl”. It’s conventional to name these preprocessor definitions “[library name]_EXPORTS”, “[library name]API”, and “[library name]CALL”, so it’s a good idea to stick to this so your code is readable by yourself and others.
Finally, note that the imported / exported functions should all be wrapped in an “#ifdef __cplusplus” block containing an “extern "C"” statement. This allows the header to be used by both C and C++ applications—without it a C++ compiler will use C++ function name mangling, which would cause the linker step to fail.
Bear in mind that this code isn’t portable, because it’s using Microsoft specific attributes in the code. That’s ok here because this is a tutorial about building Windows DLLs, but if you need cross-platform compatibility then ADDAPI and ADDCALL can be defined conditionally. Typically it would be done like this:
#ifdef _WIN32
/* You should define ADD_EXPORTS *only* when building the DLL. */
#ifdef ADD_EXPORTS
#define ADDAPI __declspec(dllexport)
#else
#define ADDAPI __declspec(dllimport)
#endif
/* Define calling convention in one place, for convenience. */
#define ADDCALL __cdecl
#else /* _WIN32 not defined. */
/* Define with no value on non-Windows OSes. */
#define ADDAPI
#define ADDCALL
#endif
The code for the DLL can now include the header we just created, and the only change we need to make is to include “ADDCALL” before the function name (it’s necessary to specify the calling convention here, to prevent possible conflicts between the declaration and definition of the function).
/* add.c
Demonstrates creating a DLL with an exported function in a flexible and
elegant way.
*/
#include "add.h"
int ADDCALL Add(int a, int b)
{
return (a + b);
}
To compile this DLL, it’s necessary to define “ADD_EXPORTS” when compiling the object code, to ensure “ADDAPI” is correctly defined in the header. This is done most easily by passing a “-D ADD_EXPORTS” on the command line:
z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o add.o add.c -D ADD_EXPORTS
z:\Users\mpayne\Documents\MinGWDLL>gcc -o add.dll add.o -s -shared -Wl,--subsystem,windows
The client application code is much the same as before, except that we now #include the header file we created rather than declare the function in the source file.
/* addtest.c
Demonstrates using the function imported from the DLL, in a flexible and
elegant way.
*/
#include
#include
#include
int main(int argc, char** argv)
{
printf("%d\n", Add(6, 23));
return EXIT_SUCCESS;
}
To compile the application, no change is needed to the command line we use:
z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o addtest.o addtest.c
z:\Users\mpayne\Documents\MinGWDLL>gcc -o addtest.exe -s addtest.o -L. -ladd
z:\Users\mpayne\Documents\MinGWDLL>addtest.exe
29
This is the build process that many real world libraries use. If it’s not clear what’s going on with the preprocessor definitions, pass “-save-temps” on the gcc command line during the compilation stage and look at the files with a “.i” extension—you’ll notice that ultimately the generated code in both elegant and inelegant examples is almost identical.
Exporting and Importing Variables
In addition to functions, it’s also possible to export and import variables. These variables must be declared “extern __declspec(dllexport)” or “extern __declspec(dllimport)”, depending on whether we are building the DLL or a client application using these variables. Similarly to functions, we can use our preprocessor definitions and simply delcare the variables “extern ADDAPI”. A calling convention should not be specified for variables.
Here we have added two exported variables to our header file, foo and bar.
/* add_var.h
Declares a function and variables to be imported by our application, and
exported by our DLL.
*/
/* You should define ADD_EXPORTS *only* when building the DLL. */
#ifdef ADD_EXPORTS
#define ADDAPI __declspec(dllexport)
#else
#define ADDAPI __declspec(dllimport)
#endif
/* Define calling convention in one place, for convenience. */
#define ADDCALL __cdecl
/* Make sure functions are exported with C linkage under C++ compilers. */
#ifdef __cplusplus
extern "C"
{
#endif
/* Declare our Add function using the above definitions. */
ADDAPI int ADDCALL Add(int a, int b);
/* Exported variables. */
extern ADDAPI int foo;
extern ADDAPI int bar;
#ifdef __cplusplus
} // __cplusplus defined.
#endif
The code for the DLL now includes the assignment of values to our exported variables:
/* add_var.c
Demonstrates creating a DLL with an exported function and imported variables.
*/
#include "add_var.h"
int ADDCALL Add(int a, int b)
{
return (a + b);
}
/* Assign value to exported variables. */
int foo = 7;
int bar = 41;
The application has been modified so that now it adds the imported foo and bar variables together, printing the result:
/* add_vartest.c
Demonstrates using the function and variables exported by our DLL.
*/
#include
#include
/* Don't forget to change this to #include for real applications where
the header has been deployed to a standard include folder!
*/
#include "add_var.h"
int main(int argc, char** argv)
{
/* foo + bar = Add(foo, bar) */
printf("%d + %d = %d\n", foo, bar, Add(foo, bar));
return EXIT_SUCCESS;
}
Compilation is the same as before:
z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o add_var.o add_var.c -D ADD_EXPORTS
z:\Users\mpayne\Documents\MinGWDLL>gcc -o add_var.dll add_var.o -s -shared -Wl,--subsystem,windows
z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o add_vartest.o add_vartest.c
z:\Users\mpayne\Documents\MinGWDLL>gcc -o add_vartest.exe -s add_vartest.o -L. -ladd_var
z:\Users\mpayne\Documents\MinGWDLL>add_vartest.exe
7 + 41 = 48
If you change the values of foo and bar, recompile the DLL, and run the application again, you can see that the variables are indeed imported from the DLL.
Import Libraries
Although it’s usually possible to link to a DLL simply by having the DLL present on your system and adding some linker command line options, it may not always be desirable. In this case, an import library should be used instead. An import library contains no code, but does contain all of the necessary information needed for your application to locate the functions exported by the DLL. When creating libraries for third parties to use, I would recommend always creating an import library and distributing it with your DLL and header files, as your users may have a requirement to build their applications using import libraries.
If you have modified the function names that the DLL exports (see “Warning About Exporting stdcall Functions” below), an import library is the only option. This is the case for the Windows API, where you must link your applications using import libraries, i.e. passing “-lgdi32 -luser32” etc. on the linker command line when compiling Windows GUI programs.
Creating and Using an Import Library
If you haven’t modified any of the function names exported by the DLL, then creating an import library is just a case of passing an extra parameter “-Wl,--out-implib,lib[library name].a” to the linker command line.
z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o add.o add.c -D ADD_EXPORTS
z:\Users\mpayne\Documents\MinGWDLL>gcc -o add.dll add.o -s -shared -Wl,--subsystem,windows,--out-implib,libadd.a
Creating library file: libadd.a
It’s conventional to use the library name in the import library name. It’s mandatory that the filename begins with “lib” and ends with a “.a” extension in order for it to be usable (actually there are a few supported naming conventions you can use which are covered in the manual, but anything else won’t work).
Building the application is the same as before, but instead of passing the directory containing the DLL to the linker, you pass the directory containing the import library instead (actually we’re still doing everything from the current directory, but this likely wouldn’t be the case for a real application). To link to the DLL, you pass the name of the import library without the lib prefix and file extension.
z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o addtest.o addtest.c
z:\Users\mpayne\Documents\MinGWDLL>gcc -o addtest.exe -s addtest.o -L. -ladd
z:\Users\mpayne\Documents\MinGWDLL>addtest.exe
29
Typically DLLs go in a folder named “bin”, and import libraries go in a folder named “lib”, for example “C:\Program Files\Add\bin\” and “C:\Program Files\Add\lib\”.
Warning About Exporting stdcall Functions
One thing you should be aware of when declaring functions “__stdcall”, is that MinGW and MSVC export their functions with slightly different names (function decorations). For our add example above, MSVC would export the “Add(int a, int b)” function with the name “_Add@8” (underscore, function name, at sign, size of arguments in bytes), whereas MinGW will export it as “Add@8” (same as MSVC, but without the underscore). Therefore, if you intend to have binary compatibility between MinGW and MSVC builds of your DLL, it’s necessary to change the names of the exported functions. This is a bit more advanced than I intend to cover in this article, but I have written an Advanced MinGW DLL Topics article which explains how to do this, along with some of the pitfalls you should watch out for.
Adding Version Information and Comments to your DLL
If you view the properties for a DLL using Windows explorer and go to the “details” tab, you’ll likely see information about the DLL such as the version, author, copyright, and a description of the library. It’s a nice touch to add to a DLL (particularly helpful when you want to know which version of a DLL you have installed on your PC), and is fairly straightforward to add to your DLL. All you need to do is create a version resource such as the following:
#include
// DLL version information.
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG | VS_FF_PRERELEASE
#else
FILEFLAGS 0
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "080904b0"
BEGIN
VALUE "CompanyName", "Transmission Zero"
VALUE "FileDescription", "A library to perform addition."
VALUE "FileVersion", "1.0.0.0"
VALUE "InternalName", "AddLib"
VALUE "LegalCopyright", "©2013 Transmission Zero"
VALUE "OriginalFilename", "AddLib.dll"
VALUE "ProductName", "Addition Library"
VALUE "ProductVersion", "1.0.0.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x809, 1200
END
END
I won’t go into detail about what each of these means, as it’s covered very well in the MSDN, but most of it is self-explanatory (I’d suggest reading the MSDN article anyway, particularly if the language is American English or anything other than British English).
The idea is to compile the resource script using windres.exe, and then pass it to the linker when linking your DLL. For example if your resource script is named “resource.rc”:
z:\Users\mpayne\Documents\MinGWDLL>windres -i resource.rc -o resource.o
z:\Users\mpayne\Documents\MinGWDLL>gcc -o add.dll add.o resource.o -s -shared -Wl,--subsystem,windows,--out-implib,libadd.a
That’s all there is to adding version information to a DLL. It’s worth taking the time to do this for any DLLs you release, as it’s both helpful and gives your application a professional look and feel.
Putting It All Together
Using the information in this article, you should now be able to put it all together and create DLLs using MinGW, as well as executables which use the functions exported from the DLLs. To help you out, I’ve created a small project which builds a DLL with the following functionality:
Exported “int Add(int a, int b);” function, using cdecl calling convention.
Exported variables “foo” and “bar”.
Version Information resource embedded into the DLL.
Additionally an executable will be built which prints the result of an addition, using the exported function and exported variables. The project can be built with the mingw32-make utility.
Download the MinGW DLL Example
Things To Be Aware Of
There are a few things to be aware of when exposing a C API from a DLL. Firstly, your code can’t throw any C++ exceptions inside the DLL and expect the client application to catch these exceptions. This shouldn’t be a problem if you are using pure C code because C doesn’t support C++ exceptions, but if you’re coding in C++ and are specifying C linkage for your exported functions, it’s something you need to be careful about. It’s best to catch all exceptions where there is a possibility of an exception being thrown, and return some error code to the caller of the API you’re exposing. If you do throw exceptions across a DLL boundary, you might get lucky and it will work for you, but it will more than likely fail if someone is using your library with a different programming language, a different compiler, or even a different version of the compiler you are using (just because you wrote the library in C, doesn’t mean that someone won’t use it, for example, in an application written in Visual Basic, assembly, D, or C#).
Another thing to be aware of is that if you’re passing structs into a function exported by a DLL, or returning a struct from that function, you can’t later change the definition of that struct. Doing such changes would likely result in older applications either crashing or behaving in unintended ways, because the memory layout of the struct has changed since the application was compiled. It’s not impossible to change the definition of a struct without breaking backwards compatibility, but it’s not elegant either. For example, it’s common in the Windows API that structs have a (seemingly pointless) member which indicates the size of the struct, have new members added at the end of the struct definition, and are passed into functions by address and not value (for example the OPENFILENAME struct). These are the kind of things that allow the Windows API to change between releases of Windows without breaking backwards compatibility (I know people might say that each version of Windows causes many backwards compatibility issues, but the fact is that a well written application created for Windows 95 has a good chance of running on recent versions of Windows, which in my opinion is pretty impressive).
Although it’s possible to export C++ variables and C++ functions from a DLL, you should avoid doing so unless you really understand what you are doing. C++ does not have the well defined ABI that C does, and you’ll likely find that compiled C++ functions created with a compiler from one vendor are not compatible with those from another vendor. In fact, you may find that compatibility is broken between different versions of a C++ compiler from the same vendor. For this reason, each compiler has it’s own way of mangling the names of C++ functions, which prevents functions created with different C++ compilers from being linked together. As disappointing as this is, it prevents potential mayhem which could occur as a result of differences in ABI. I cover C++ DLLs in my Advanced MinGW DLL Topics article.
http://www.transmissionzero.co.uk/computing/advanced-mingw-dll-topics/
Advanced MinGW DLL Topics
Introduction
This article is a follow-on from my Building Windows DLLs with MinGW article, which gave an introduction into building DLLs with MinGW and using them within an executable. In this article I will cover some more advanced topics which weren’t appropriate for the introductory article:
Displaying functions exported from a DLL.
The DllMain function.
Using a module definition file.
Exporting undecorated stdcall functions.
Exporting C++ functions and variables from a DLL.
Creating JNI DLLs.
P/Invoking MinGW DLLs in .NET
Setting the DLL base address.
Loading and unloading DLLs at runtime.
Displaying Functions Exported From A DLL
If you’d like to view the functions exported by a DLL, you can use the GNU binutils objdump tool. This is useful if you want to see the exact symbols which are being exported from a DLL, including any name mangling. It is also useful if you have a third party DLL and would like to see exactly what it’s exporting. The following is an example of dumping the exports of the “AddLib.dll” from my previous article:
z:\Users\mpayne\Documents\MinGWDLL>objdump -p AddLib.dll
The output will contain something like the following:
There is an export table in .edata at 0x6da46000
The Export Tables (interpreted .edata section contents)
Export Flags 0
Time/Date stamp 4da9a500
Major/Minor 0/0
Name 00006046 AddLib.dll
Ordinal Base 1
Number in:
Export Address Table 00000003
[Name Pointer/Ordinal] Table 00000003
Table Addresses
Export Address Table 00006028
Name Pointer Table 00006034
Ordinal Table 00006040
Export Address Table -- Ordinal Base 1
[ 0] +base[ 1] 1280 Export RVA
[ 1] +base[ 2] 2004 Export RVA
[ 2] +base[ 3] 2000 Export RVA
[Ordinal/Name Pointer] Table
[ 0] Add
[ 1] bar
[ 2] foo
You can see the function “Add” is being exported, along with the variables “foo” and “bar”. You don’t get any more information than this, for example the number of parameters and their types. In fact, you don’t even know if a name represents a function or variable. That’s because each name represents an address in memory, which could be a pointer to anything.
If you have the Microsoft Windows SDK, you can also use the dumpbin utility to display exported functions:
z:\Users\mpayne\Documents\MinGWDLL>dumpbin -exports AddLib.dll
Microsoft (R) COFF/PE Dumper Version 9.00.30729.01
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file AddLib.dll
File Type: DLL
Section contains the following exports for AddLib.dll
00000000 characteristics
4DA9A500 time date stamp Sat Apr 16 15:17:36 2011
0.00 version
1 ordinal base
3 number of functions
3 number of names
ordinal hint RVA name
1 0 00001280 Add
2 1 00002004 bar
3 2 00002000 foo
Summary
1000 .CRT
1000 .bss
1000 .data
1000 .edata
1000 .eh_fram
1000 .idata
1000 .rdata
1000 .reloc
1000 .rsrc
1000 .text
1000 .tls
As you can see, it’s the same information about the exported functions, but presented in a different format.
The DllMain Function
DLLs can have an entry point named “DllMain”, which is called by the operating system when the DLL is loaded into or unloaded from memory, or when a thread is created or has exited. The format of the functions is as follows:
#include
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
/* Code path executed when DLL is loaded into a process's address space. */
break;
case DLL_THREAD_ATTACH:
/* Code path executed when a new thread is created within the process. */
break;
case DLL_THREAD_DETACH:
/* Code path executed when a thread within the process has exited *cleanly*. */
break;
case DLL_PROCESS_DETACH:
/* Code path executed when DLL is unloaded from a process's address space. */
break;
}
return TRUE;
}
Note that the function signature should match exactly, as getting the function name or case incorrect will result in the function never being called, and getting the calling convention incorrect can corrupt the stack—no matter what calling convention your other functions are using, always mark the DllMain function WINAPI, which maps to stdcall on Win32. The return value is always required (as per the C standard), but is ignored for all messages except for “DLL_PROCESS_ATTACH”. When handling the process attach message, you should return TRUE if your initialisation was successful, or FALSE if initialisation was unsuccessful. In the case that initialisation failed, the application will fail to start up and issue an error if the DLL is statically loaded, and in the case of dynamic loading, it will cause “LoadLibrary” to return a NULL value, and the DLL will not be mapped into the address space of the process.
The DLL entry point should perform only basic initialisation procedures for your DLL. If you need to perform other tasks, this should be handled elsewhere with a function called from the user code. This is because there is no guarantee about the order in which DLLs are loaded, so if your entry point for “Foo.dll” calls a function in “Bar.dll”, it’s quite possible that Bar.dll has not yet been loaded into your process’s address space, and would result in a general protection fault. The only functions which are safe to call are those located in “kernel32.dll”, which is guaranteed to be loaded before your DLL is loaded. See the DllMain Entry Point MSDN article for full details of how it works, and what you should and shouldn’t do in this function.
Using A Module Definition File
An alternative to using the “__declspec(dllexport)” attribute on functions you want exported from a DLL, is to use a module definition file. This is simply a file which defines (among other things), the functions and variables you want exported from a DLL. An example for our “AddLib.dll” looks like the following:
LIBRARY AddLib.dll
EXPORTS
Add
foo
bar
This specifies that we want function “Add” being exported from the DLL, along with variables “foo” and “bar”. Our C header file looks like the following:
/* Define calling convention in one place, for convenience. */
#define ADDCALL __cdecl
/* Make sure functions are exported with C linkage under C++ compilers. */
#ifdef __cplusplus
extern "C"
{
#endif
/* Declare our Add function using the above definitions. */
int ADDCALL Add(int a, int b);
/* Exported variables. */
extern int foo;
extern int bar;
#ifdef __cplusplus
} // __cplusplus defined.
#endif
You can see that we have not exported the function or the variables. However, we still need to specify the calling convention, and the variables still to be marked “extern” to ensure they are linked correctly. The function body is implemented as follows:
#include "add.h"
int ADDCALL Add(int a, int b)
{
return (a + b);
}
/* Assign value to exported variables. */
int foo = 7;
int bar = 41;
To use the module definition file, you just have to pass the filename during the link step:
z:\Users\mpayne\Documents\MinGWDLL>gcc -O3 -std=c99 -Wall -c add.c -o add.o
z:\Users\mpayne\Documents\MinGWDLL>gcc -o AddLib.dll add.o AddLib.def -shared -s -Wl,--subsystem,windows,--out-implib,libaddlib.a
One thing to be aware of, is that the header file above does not declare the functions “__declspec(dllimport)” when they are being imported. Whilst this does still work, it results in a slight overhead when calling these functions, as the compiler is not able to make an optimisation that it would otherwise be able to make if it knew the function was coming from a DLL. You could add a conditionally defined “__declspec(dllimport)” to the header file for the exported functions, but this adds another layer of complexity.
Exporting Undecorated stdcall Functions
If you are exporting functions from a DLL and you’re using the stdcall calling convention, you’ll find that MinGW decorates the functions exported from the DLL. For example if you have a function with the signature “int Add(int, int)”, the exported name will be “Add@8”. The linker has added an ‘@’ sign after the function name, followed by the size of the function arguments in bytes. However, if you’re using Microsoft’s Visual C++ you’ll find that it additionally prefixes the name with an underscore, for example “_Add@8”. This may not seem to be a cause for concern, but consider the following scenario. You create an image handling library, and distribute it as source code only. Company A builds an application using your image library, and builds them using MSVC. It deploys the DLL to the “system32” folder of your system, and all works fine. Company B then builds an application using your image library, and builds them using MinGW. It also deploys the DLL to the “system32” folder, overwriting the version of the DLL that Company A deployed. Company B’s application works fine, but Company A’s application is now broken, because the MSVC and MinGW DLLs have differently named exports which are incompatible with each other. The solution is to export the functions without decorations.
To export functions without decorations, it’s simply necessary to pass “--kill-at” to the linker. You must also create an import library when modifying the names exported by a DLL, otherwise the link step will fail when building applications using your DLL. Unfortunately, passing “--out-implib” to the linker will result in an unusable import library, due to the way that the functions are exported. To create the import library, you will need to use dlltool.exe along with a module definition file which contains the decorated versions of all of the exported functions. If this all sounds like a real pain, the following is a much simpler (if slightly inelegant) way of doing this:
z:\Users\mpayne\Documents\MinGWDLL>gcc -o AddLib.dll add.o -shared -s -Wl,--subsystem,windows,--output-def,AddLib.def
z:\Users\mpayne\Documents\MinGWDLL>gcc -o AddLib.dll add.o -shared -s -Wl,--subsystem,windows,--kill-at
z:\Users\mpayne\Documents\MinGWDLL>dlltool --kill-at -d AddLib.def -D AddLib.dll -l libaddlib.a
The first step creates the DLL with decorated functions. Although this is not what we want, passing “--output-def,AddLib.def” to the linker generates a module definition file for us which contains decorated function names. The second step creates the DLL again, but this time we pass “--kill-at” to export the functions without decorations. Note that we can’t create the module definition file in this step, because it would contain undecorated function names. The third and final step creates the import library, based on the module definition file and the DLL name. A little more complicated, but worth it if you need consistency between compilers. In fact, the Win32 API functions are exported this way, without any decorations.
Exporting C++ Functions & Variables
Exporting C++ functions and variables from a DLL is not particularly difficult, but there is one thing which can catch you out if you’re not aware. If you build a C++ DLL with one compiler, it probably won’t work with another compiler. In fact, C++ DLLs built with a certain version of a compiler may not be compatible with DLLs built with another version of the same compiler. This is because C++ is a complex language, and doesn’t have the well defined ABI which C has. For example, different compilers might have different ways of handling C++ exceptions, different ways of implementing virtual functions, or different memory layouts for STL classes. To avoid the issues which would result from this, C++ implementations are intentionally incompatible, and use different methods of name mangling for their functions.
Exporting global variables and global functions from a DLL is done the same way in C++ as it is done in C. The only difference from C, is that global variables can be instances of C++ objects, and the functions can be overloaded if needed. You can also export a C++ class, which will export all of its static and instance member functions which have implementations, whether they are public, protected, or private. The following is an example of a (very minimal) class representing a 2D point:
#ifndef POINT_HPP
#define POINT_HPP
#ifdef POINT_EXPORTS
#define POINTAPI __declspec(dllexport)
#else
#define POINTAPI __declspec(dllimport)
#endif
#include
using std::ostream;
class POINTAPI Point
{
public:
// Constructors.
Point();
Point(int x, int y);
// Getters and setters.
int getX() const;
int getY() const;
void setX(int x);
void setY(int y);
// Friend the overloaded operators, so they can access private Point data.
friend Point operator+(const Point& lhs, const Point& rhs);
friend ostream& operator<<(ostream& os, const Point& pt);
private:
int x, y;
};
// Overloaded operators.
POINTAPI Point operator+(const Point& lhs, const Point& rhs);
POINTAPI ostream& operator<<(ostream& os, const Point& pt);
extern POINTAPI Point foo;
extern POINTAPI Point bar;
#endif
The point class has been exported, which means that the two constructors, the getters, and the setters are exported. Additionally, the global function to add two points together, the global function to write the point to a stream, and two instances of points have been exported from the DLL. The following is the implementation of these functions:
#include "point.hpp"
Point::Point()
: x(0), y(0)
{ }
Point::Point(int x, int y)
: x(x), y(y)
{ }
int Point::getX() const { return this->x; }
int Point::getY() const { return this->y; }
void Point::setX(int x) { this->x = x; }
void Point::setY(int y) { this->y = y; }
Point operator+(const Point& lhs, const Point& rhs)
{
return Point(lhs.x + rhs.x, lhs.y + rhs.y);
}
ostream& operator<<(ostream& os, const Point& pt)
{
return os << "(" << pt.x << ", " << pt.y << ")";
}
Point foo(9, 6);
Point bar(3, 19);
Typically such simple functions would be declared inline, but we haven’t done so here because it would defeat the purpose of housing the functions in a DLL. Building the DLL is much the same as building a C DLL, except that we are using the C++ compiler in this case:
z:\Users\mpayne\Documents\MinGWDLL>g++ -c -o point.o point.cpp -D POINT_EXPORTS
z:\Users\mpayne\Documents\MinGWDLL>g++ -o point.dll point.o -s -shared -Wl,--subsystem,windows,--out-implib,libpoint.a
Creating library file: libpoint.a
Creating the import library is optional, as MinGW can alternatively use the DLL in the link step instead of the import library. If you perform an “objdump -p” on the DLL, you will notice that the functions have names such as “_ZN5Point4setXEi” and “_ZlsRSoRK5Point”. If it isn’t clear what these mangled functions refer to, you can use the “c++filt” tool to unmangle them. If you pass a function name on the command line, it will unmangle the name, and if you pass nothing on the command line, it will wait for names to be written to the standard input:
z:\Users\mpayne\Documents\MinGWDLL>c++filt -n _ZlsRSoRK5Point
operator<<(std::basic_ostream >&, Point const&)
z:\Users\mpayne\Documents\MinGWDLL>c++filt -n
_ZN5Point4setXEi
Point::setX(int)
_ZN5Point4setYEi
Point::setY(int)
_ZN5PointC1Eii
Point::Point(int, int)
_ZN5PointC1Ev
Point::Point()
_ZN5PointC2Eii
Point::Point(int, int)
_ZN5PointC2Ev
Point::Point()
_ZNK5Point4getXEv
Point::getX() const
_ZNK5Point4getYEv
Point::getY() const
_ZlsRSoRK5Point
operator<<(std::basic_ostream >&, Point const&)
_ZplRK5PointS1_
operator+(Point const&, Point const&)
Now that the DLL is created, you can write an application which makes use of the DLL. The following example makes use of all of the exported functions, and makes use of the overloaded addition operator along with the stream operator to add two points, and write them to the screen:
#include
#include "point.hpp"
using namespace std;
int main(int argc, char** argv)
{
Point a;
Point b(2, 7);
Point c;
c.setX(85);
c.setY(24);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = (" << c.getX() << ", " << c.getY() << ")\n";
cout << "foo + bar = " << foo << " + " << bar << " = " << (foo + bar) << endl;
return 0;
}
The application can then be compiled, linked, and executed:
z:\Users\mpayne\Documents\MinGWDLL>g++ -c -o pointtest.o pointtest.cpp
z:\Users\mpayne\Documents\MinGWDLL>g++ -o pointtest.exe -s pointtest.o -L. -lpoint
z:\Users\mpayne\Documents\MinGWDLL>pointtest.exe
a = (0, 0)
b = (2, 7)
c = (85, 24)
foo + bar = (9, 6) + (3, 19) = (12, 25)
In this case we have exported all of the members from a class, but you don’t have to export them all if you don’t want to. Instead, you can mark individual member functions as being exported. Again, it doesn’t matter whether they are static functions or instance member functions, and it doesn’t matter whether they are public, protected, or private. The only thing you cannot export is a pure virtual function, which by definition has no implementation, so there is nothing to export. Regular virtual functions can be exported, but it’s only necessary to export them if you need to call them for stack based variables which are not accessed through a pointer or reference—for variables which are accessed via a pointer or reference, the functions are instead looked up in the object’s vtable, so a DLL import is not used.
When deploying C++ DLLs, you need to be very careful to avoid breaking other applications by putting an incompatible DLL in another application’s path. It’s best to either place the DLL in the same directory as the application you are deploying, or if you need to put the DLL in a shared folder, give the DLL a name which is unique to the compiler and version. An example would be “point-mingw-4.5.2.dll", or “point-msvc-2010.dll”.
Creating JNI DLLs
Creating MinGW DLLs for use with the Java Native Interface is quite easy, except for one tiny gotcha. The JNI calling convention on Win32 is stdcall, and the JVM expects the exported functions to either have undecorated names, or have names decorated in the format “_[function name]@[size of arguments]”. MinGW does not prefix the names with an underscore, so trying to call MinGW DLLs from Java might result in a stack trace like the following:
z:\Users\mpayne\Documents\MinGWDLL>java Hello
Exception in thread "main" java.lang.UnsatisfiedLinkError: Hello.add(II)I
at Hello.add(Native Method)
at Hello.main(Hello.java:5)
The solution is to ensure that the functions are exported with undecorated names. As we only require a DLL to be created in this case (no import library is needed), you can simply pass “--kill-at” to the linker and you’re done. Here is a quick example of using JNI with MinGW. Firstly, create the Java file with a native function declaration:
public class Hello
{
public static void main(String[] args)
{
System.out.println("8 + 5 = " + Hello.add(8, 5));
}
static
{
System.loadLibrary("Hello");
}
public static native int add(int a, int b);
}
Remember to call “System.loadLibrary()” for the DLL, as I have done in the static constructor above. You can then compile the Java source code and create the C / C++ header file:
z:\Users\mpayne\Documents\MinGWDLL>javac Hello.java
z:\Users\mpayne\Documents\MinGWDLL>javah Hello
The second command will create a C / C++ header file which looks like the following:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class Hello */
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_Hello_add
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
Next, create a C file which contains the body of this function:
#include "Hello.h"
jint JNICALL Java_Hello_add(JNIEnv* env, jclass clazz, jint a, jint b)
{
return (a + b);
}
Now you can compile the DLL, remembering to pass “--kill-at” to the linker, and run the application:
z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o Hello.o Hello.c -I "c:\Program Files (x86)\Java\jdk\include\win32" ^
-I "c:\Program Files (x86)\Java\jdk\include"
z:\Users\mpayne\Documents\MinGWDLL>gcc -o Hello.dll -s -shared Hello.o -Wl,--subsystem,windows,--kill-at
z:\Users\mpayne\Documents\MinGWDLL>java Hello
8 + 5 = 13
Notice that I have passed two include paths to the compiler, as they contain headers which contain data types and function prototypes needed by JNI. You might need to adjust the paths to match those of your Java installation. Also be aware that like any Windows process, a 32 bit JVM can only load 32 bit DLLs, and a 64 bit JVM can only load 64 bit DLLs. If you try to load a 32 bit DLL in a 64 bit JVM, you’ll get an error such as “Can't load IA 32-bit .dll on a AMD 64-bit platform”.
P/Invoking MinGW DLLs in .NET
Calling functions from a MinGW DLL from .NET code is even easier than working with JNI. This is because in JNI the DLL has to be designed to work with JNI, and must follow certain rules. With P/Invoke, it doesn’t really matter how the DLL has been designed, as you can adjust the DLL import attribute in your .NET code to match the DLL.
To import a function from a DLL, you must declare the function as “extern”, and mark it with the “DllImport” attribute. Here is an example of importing a cdecl function:
using System;
using System.Runtime.InteropServices;
public class Hello
{
public static void Main(string[] args)
{
Console.WriteLine("8 + 5 = {0}", Hello.Add(8, 5));
}
[DllImport("AddLib.dll", CallingConvention = CallingConvention.Cdecl)]
extern public static int Add(int a, int b);
}
You can also import stdcall functions, by changing the DLL import attribute to specify “CallingConvention = CallingConvention.StdCall”. The .NET CLR will attempt to import the function without decorations, and if you have not specified “ExactSpelling = true”, it will also attempt to import the function decorated in the MSVC way, i.e. “_Add@8”. If you have a stdcall function decorated in the MinGW way, i.e. “Add@8”, it won’t work. However, you can still import the function by explicitly specifying the entry point:
using System;
using System.Runtime.InteropServices;
public class Hello
{
public static void Main(string[] args)
{
Console.WriteLine("8 + 5 = {0}", Hello.Add(8, 5));
}
[DllImport("AddLib.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "Add@8", ExactSpelling = true)]
extern public static int Add(int a, int b);
}
Remember that 64 bit Windows processes can only load 64 bit DLLs, and 32 bit Windows processes can only load 32 bit DLLs. Therefore if your DLL is a 32 bit DLL, and your end user is running the .NET application on a 64 bit version of Windows, your application will fail with an error similar to the following:
z:\Users\mpayne\Documents\MinGWDLL>Hello.exe
Unhandled Exception: System.BadImageFormatException: An attempt was made to load a program with an incorrect format.
(Exception from HRESULT: 0x8007000B)
at Hello.Add(Int32 a, Int32 b)
at Hello.Main(String[] args)
To work around this, you can specify the platform the application will run on by passing the “platform” linker option:
z:\Users\mpayne\Documents\MinGWDLL>csc /platform:x86 /out:Hello.exe Hello.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.4927
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
The platform target option can also be set in Visual Studio, in the “build” section of the project’s properties. By setting this option, your application will run correctly on both 32 bit versions of Windows and 64 bit versions. The same can be done for 64 bit DLLs, by specifying the platform as “x64”, but of course your application won’t run on 32 bit version of Windows in that case.
Using MinGW DLLs with VB6 and VBA
It’s fairly straightforward to use MinGW DLLs with Visual Basic 6 and VBA. The only requirement is that the calling convention for exported functions is stdcall (cdecl and other calling conventions are not supported), and the functions are exported without decorations by using the “--kill-at” command line option:
z:\Users\mpayne\Documents\MinGWDLL>gcc -o AddLib.dll add.o -shared -s -Wl,--subsystem,windows,--kill-at
The function needs to be declared in your code before you are able to call it:
Private Declare Function MyAddFunction Lib "AddLib.dll" Alias "Add" (ByVal a As Long, ByVal b As Long) As Long
Sub Test()
Call MsgBox(MyAddFunction(4, 5))
End Sub
I have used the “Alias” keyword here to import the function with a different name. I have done this just to demonstrate its use, and normally you would just leave this keyword out completely and use the name exported from the DLL. It does come in useful either if you would end up with name clashes in your code, or if you are calling a Windows API function which comes in Unicode and ANSI variants, and you don’t want to suffix the ‘A’ on every single function call (note that Visual Basic only supports ANSI strings, not Unicode).
If you are using VBA, you should additionally mark your function with the “PtrSafe” keyword. This will ensure you are able to run the code on 64 bit versions of Microsoft Office. However, this is not backwards compatible with versions before Office 2010, so you will need to do it conditionally:
#If VBA7 Then
Private Declare PtrSafe Function MyAddFunction Lib "AddLib.dll" Alias "Add" (ByVal a As Long, ByVal b As Long) As Long
#Else
Private Declare Function MyAddFunction Lib "AddLib.dll" Alias "Add" (ByVal a As Long, ByVal b As Long) As Long
#End If
Sub Test()
Call MsgBox(MyAddFunction(4, 5))
End Sub
It’s a bit of a pain, but well worth doing to ensure your code works across many versions of Office on different architectures.
Setting The DLL Base Address
The base address of a DLL is the preferred location within a process’s virtual address space where the DLL should be loaded. If two DLLs which are loaded by a process have the same base address, or if the regions of memory that the DLLs should be mapped into overlap, it is not possible load both DLLs at their preferred load address. Therefore, one of the DLLs needs to be relocated and loaded at a different memory address. This involves the loader patching hard coded memory addresses within the application’s address space, which can involve significant overhead. By default, the MinGW linker chooses a base address based on a hash of the DLL name, which seems to work very well. However, if you do experience issues with DLLs being unable to be loaded at their preferred address, you can specify the base address of a DLL manually with the “--image-base” linker option. For example, to set the base address to “0x10000000” you pass this option as follows:
z:\Users\mpayne\Documents\MinGWDLL>gcc -o AddLib.dll obj/add.o -shared -s ^
-Wl,--subsystem,windows,--out-implib,libaddlib.a,--image-base,0x10000000
You can verify the base address using “objdump -p AddLib.dll”, which should include the “ImageBase” in an output similar to the following:
AddLib.dll: file format pei-i386
Characteristics 0x230e
executable
line numbers stripped
symbols stripped
32 bit words
debugging information removed
DLL
Time/Date Tue Apr 19 16:32:45 2011
Magic 010b (PE32)
MajorLinkerVersion 2
MinorLinkerVersion 21
SizeOfCode 00000c00
SizeOfInitializedData 00002200
SizeOfUninitializedData 00000200
AddressOfEntryPoint 000010c0
BaseOfCode 00001000
BaseOfData 00002000
ImageBase 10000000
SectionAlignment 00001000
FileAlignment 00000200
MajorOSystemVersion 4
MinorOSystemVersion 0
MajorImageVersion 1
MinorImageVersion 0
MajorSubsystemVersion 4
MinorSubsystemVersion 0
Win32Version 00000000
SizeOfImage 0000c000
SizeOfHeaders 00000400
CheckSum 0000383c
Subsystem 00000002 (Windows GUI)
DllCharacteristics 00000000
SizeOfStackReserve 00200000
SizeOfStackCommit 00001000
SizeOfHeapReserve 00100000
SizeOfHeapCommit 00001000
LoaderFlags 00000000
NumberOfRvaAndSizes 00000010
Loading / Unloading DLLs At Runtime
Loading a DLL at runtime is useful when you have a plugin architecture where developers can extend the functionality of your application. You would likely provide some header files which define the data structures the application uses, some prototypes for supporting functions, and some prototypes for function(s) which the plugin must implement. To take a very simple example, you might require a developer to provide a DLL with a single exported function, with a signature “void __cdecl DoPlugin();”. Your application then just needs to call the “DoPlugin” function exported by that DLL, in order to expose its functionality.
Of course, you don’t know the name of this DLL in advance, so you need to load the DLL at runtime. This is done using the “LoadLibrary” Windows API function, located in “kernel32.dll”. Calling LoadLibrary loads the DLL into the process’s address space (if it is not already present), and increments the usage count for this DLL. If the DLL is successfully loaded, the function returns an HMODULE whose value is the address in memory where the DLL was loaded (ideally the DLL base address). Once the DLL is loaded into memory, you can call the “GetProcAddress” function to get the address of the function you’d like to call. The following example attempts to load the “AddLib.dll” library from my previous article, and call the “Add” function which the DLL exports:
#include
#include
#include
/* Function signature of the function exported from the DLL. */
typedef int (__cdecl *AddFunc)(int a, int b);
int main(int argc, char** argv)
{
HMODULE hAddLib;
AddFunc Add;
/* Attempt to load the DLL into the process's address space. */
if (! (hAddLib = LoadLibrary(TEXT("AddLib.dll"))))
{
fprintf(stderr, "Error loading \"AddLib.dll\".\n");
return EXIT_FAILURE;
}
/* Print the address that the DLL was loaded at. */
printf("Library is loaded at address %p.\n", hAddLib);
/* Attempt to get the memory address of the "Add()" function. */
if (! (Add = (AddFunc) GetProcAddress(hAddLib, "Add")))
{
fprintf(stderr, "Error locating \"Add\" function.\n");
return EXIT_FAILURE;
}
/* Print the address of the "Add()" function. */
printf("Add function is located at address %p.\n", Add);
/* Call the function and display the results. */
printf("7 + 41 = %d\n", Add(7, 41));
/* Unload the DLL. */
FreeLibrary(hAddLib);
return EXIT_SUCCESS;
}
There are a few things here which should be noted. Firstly, the “LoadLibrary” and “GetProcAddress” functions return a NULL pointer on failure, and you should always check for a failure in either of these function calls. Secondly, the “LoadLibrary” function has both ANSI and Unicode versions, whereas “GetProcAddress” always takes an ANSI string for the function name—don’t let this catch you out in Unicode builds. Thirdly, we are using a C “typedef” to declare a function pointer type matching the signature of the function exported from the DLL. This signature should match exactly, including the calling convention, otherwise you can corrupt the stack when calling the function. Finally, the “FreeLibrary” function should be called when you are finished using functions exported from the DLL. The “FreeLibrary” function decrements the usage count of the DLL, and additionally unmaps the DLL from the process’s address space if the usage count reaches zero.
Running the application should result in an output such as the following:
z:\Users\mpayne\Documents\MinGWDLL>DynamicLoad.exe
Library is loaded at address 6DA40000.
Add function is located at address 6DA41280.
7 + 41 = 48
Of course in a real example you would not hard code the name of the DLL into the application. Instead, you would probably expose a user interface within the application which allows managing of plugins—exactly how this is done is left as an exercise for the reader.
http://c.biancheng.net/cpp/html/2847.html
__stdcall,__cdecl,__pascal,__fastcall的区别
__cdecl
__cdecl 是 C Declaration 的缩写,表示 C
语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数不会要求调用者传递多少参数,调用者传递过多或者过
少的参数,甚至完全不同的参数都不会产生编译阶段的错误。
__stdcall
__stdcall 是 Standard Call 的缩写,是 C++
的标准调用方式:所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是 this
指针。这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是 retnX,X 表示参数占用的字节数,CPU 在 ret 之后自动弹出 X
个字节的堆栈空间,称为自动清栈。函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。
__pascal
__pascal 是 Pascal 语言(Delphi)的函数调用方式,也可以在 C/C++ 中使用,参数压栈顺序与前两者相反。返回时的清栈方式与 __stdcall 相同。
__fastcall
__fastcall 是编译器指定的快速调用方式。由于大多数的函数参数个数很少,使用堆栈传递比较费时。因此 __fastcall
通常规定将前两个(或若干个)参数由寄存器传递,其余参数还是通过堆栈传递。不同编译器编译的程序规定的寄存器不同,返回方式和 __stdcall
相当。
__thiscall
__thiscall 是为了解决类成员调用中 this 指针传递而规定的。__thiscall 要求把 this
指针放在特定寄存器中,该寄存器由编译器决定。VC 使用 ecx,Borland 的 C++ 编译器使用 eax。返回方式和 __stdcall
相当。
__fastcall 和 __thiscall 涉及的寄存器由编译器决定,因此不能用作跨编译器的接口。所以 Windows 上的 COM 对象接口都定义为 __stdcall 调用方式。
C 语言中不加说明默认函数为 __cdecl 方式(C中也只能用这种方式),C++ 也一样,但是默认的调用方式可以在 IDE 环境中设置。
例子:test1(par1, par2, par3);
__cdecl调用约定 |
PASCAL调用约定 |
__stdcall调用约定 |
push par3; 参数按右到左传递
push par2
push par1
call test1
add esp, OC; 平衡堆找 |
push par1; 参数按左到右传递
push par2
push par3
call test1; 函数内平衡堆找 |
push par3; 参数按右到左传递
push par2
push par1
call testl; 函数内平衡堆栈 |