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.
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.
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.
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".
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 ;
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...