Building the DLL Module

[Previous] [Next]

When you create a DLL, you create a set of functions that an executable module (or other DLLs) can call. A DLL can export variables, functions, or C++ classes to other modules. In real life, you should avoid exporting variables because this removes a level of abstraction in your code and makes it more difficult to maintain your DLL's code. In addition, C++ classes can be exported only if the modules importing the C++ class are compiled using a compiler from the same vendor. For this reason, you should also avoid exporting C++ classes unless you know that the executable module developers use the same tools as the DLL module developers.

When you create a DLL, you should first establish a header file that contains the variables (type and name) and functions (prototype and name) that you want to export. This header file must also define any symbols and data structures that are used with the exported functions and variables. All of your DLL's source code modules should include this header file. Also, you must distribute this header file so that it can be included in any source code that might import these functions or variables. Having a single header file used by the DLL builder and the executable builder makes maintenance much easier.

Here is how you should code the single header file to include in both the executable and the DLL's source code files:

 /*************************************************************************** Module: MyLib.h ***************************************************************************/ #ifdef MYLIBAPI // MYLIBAPI should be defined in all of the DLL's source // code modules before this header file is included. // All functions/variables are being exported. #else // This header file is included by an EXE source code module. // Indicate that all functions/variables are being imported. #define MYLIBAPI extern "C" _ _declspec(dllimport) #endif //////////////////////////////////////////////////////////////////////////// // Define any data structures and symbols here. //////////////////////////////////////////////////////////////////////////// // Define exported variables here. (NOTE: Avoid exporting variables.) MYLIBAPI int g_nResult; //////////////////////////////////////////////////////////////////////////// // Define exported function prototypes here. MYLIBAPI int Add(int nLeft, int nRight); ////////////////////////////// End of File ///////////////////////////////// 

In each of your DLL's source code files, you should include the header file as follows:

 /*************************************************************************** Module: MyLibFile1.cpp ***************************************************************************/ // Include the standard Windows and C-Runtime header files here. #include <windows.h> // This DLL source code file exports functions and variables. #define MYLIBAPI extern "C" _ _declspec(dllexport) // Include the exported data structures, symbols, functions, and variables. #include "MyLib.h" //////////////////////////////////////////////////////////////////////////// // Place the code for this DLL source code file here. int g_nResult; int Add(int nLeft, int nRight) { g_nResult = nLeft + nRight; return(g_nResult); } ////////////////////////////// End of File ///////////////////////////////// 

When the DLL source code file above is compiled, MYLIBAPI is defined using _ _declspec(dllexport) before the MyLib.h header file. When the compiler sees _ _declspec(dllexport) modifying a variable, function, or C++ class, it knows that this variable, function, or C++ class is to be exported from the resulting DLL module. Notice that the MYLIBAPI identifier is placed in the header file before the definition of the variable to export and before the function to export.

Also notice that inside the source code file (MyLibFile1.cpp), the MYLIBAPI identifier does not appear before the exported variable and function. The MYLIBAPI identifier is not necessary here because the compiler remembers which variables or functions to export when it parses the header file.

You'll notice that the MYLIBAPI symbol includes the extern "C" modifier. You should use this modifier only if you are writing C++ code, not straight C code. Normally, C++ compilers mangle function and variable names, which can lead to severe linker problems. For example, imagine writing a DLL in C++ and an executable in straight C. When you build the DLL, the function name is mangled, but when you build the executable, the function name is not mangled. When the linker attempts to link the executable, it will complain that the executable refers to a symbol that does not exist. Using extern "C" tells the compiler not to mangle the variable or function names and thereby make the variable or function accessible to executable modules written in C, C++, or any other programming language.

So now you see how the DLL's source code files use this header file. But what about the executable's source code files? Well, executable source code files should not define MYLIBAPI before this header file. Since MYLIBAPI is not defined, the header file defines MYLIBAPI as _ _declspec(dllimport). The compiler sees that the executable's source code imports variables and functions from the DLL module.

If you examine Microsoft's standard Windows header files, such as WinBase.h, you'll see that Microsoft uses basically the same technique that I've just described.

What Exporting Really Means

The only truly interesting thing I introduced in the previous section was the _ _declspec(dllexport) modifier. When Microsoft's C/C++ compiler sees this modifier before a variable, function prototype, or C++ class, it embeds some additional information in the resulting .obj file. The linker parses this information when all of the .obj files for the DLL are linked.

When the DLL is linked, the linker detects this embedded information about the exported variable, function, or class and automatically produces a .lib file. This .lib file contains the list of symbols exported by the DLL. This .lib file is, of course, required to link any executable module that references this DLL's exported symbols. In addition to creating the .lib file, the linker embeds a table of exported symbols in the resulting DLL file. This export section contains the list (in alphabetical order) of exported variables, functions, and class symbols. The linker also places the relative virtual address (RVA) indicating where each symbol can be found in the DLL module.

Using Microsoft Visual Studio's DumpBin.exe utility (with the -exports switch), you can see what a DLL's export section looks like. The following is a fragment of Kernel32.dll's export section. (I've removed some of DUMPBIN's output so that it won't occupy too many pages in this book.)

 C:\WINNT\SYSTEM32>DUMPBIN -exports Kernel32.DLL Microsoft (R) COFF Binary File Dumper Version 6.00.8168 Copyright (C) Microsoft Corp 1992-1998. All rights reserved. Dump of file kernel32.dll File Type: DLL Section contains the following exports for KERNEL32.dll 0 characteristics 36DB3213 time date stamp Mon Mar 01 16:34:27 1999 0.00 version 1 ordinal base 829 number of functions 829 number of names ordinal hint RVA name 1 0 0001A3C6 AddAtomA 2 1 0001A367 AddAtomW 3 2 0003F7C4 AddConsoleAliasA 4 3 0003F78D AddConsoleAliasW 5 4 0004085C AllocConsole 6 5 0002C91D AllocateUserPhysicalPages 7 6 00005953 AreFileApisANSI 8 7 0003F1A0 AssignProcessToJobObject 9 8 00021372 BackupRead 10 9 000215CE BackupSeek 11 A 00021F21 BackupWrite          828 33B 00003200 lstrlenA 829 33C 000040D5 lstrlenW Summary 3000 .data 4000 .reloc 4D000 .rsrc 59000 .text 

As you can see, the symbols are in alphabetical order and the numbers under the RVA column identify the offset in the DLL file image where the exported symbol can be found. The ordinal column is for backward compatibility with 16-bit Windows source code and should not be used in modern-day applications. The hint column is used by the system to improve performance and is not important for our discussion.

NOTE
Many developers are used to exporting DLL functions by assigning functions an ordinal value. This is especially true of those who come from a 16-bit Windows background. However, Microsoft does not publish ordinal values for the system DLLs. When your executable or DLL links to any Windows function, Microsoft wants you to link using the symbol's name. If you link by ordinal, you run the risk that your application will not run on other or future Windows platforms.

In fact, this has happened to me. I published a sample application that used ordinal numbers in the Microsoft Systems Journal. My application ran fine on Windows NT 3.1, but when Windows NT 3.5 came out, my application did not run correctly. To fix the problem, I had to replace the ordinal numbers with function names. Now the application runs on both Windows NT 3.1 and all later versions.

I asked Microsoft why it is getting away from ordinals and got this response: "We feel that the Portable Executable file format provides the benefit of ordinals (fast lookup) with the flexibility of import by name. We can add functions at any time. Ordinals are very hard to manage in a large project with multiple implementations."

You can use ordinals for any DLLs that you create and have your executable files link to these DLLs by ordinal. Microsoft guarantees that this method will work even in future versions of the operating system. However, I am avoiding the use of ordinals in my own work and will link by name only from now on.

Creating DLLs for Use with Non-Visual C++ Tools

If you are using Microsoft Visual C++ to build both a DLL and an executable that will link to the DLL, you can safely skip this entire section. However, if you are building a DLL with Visual C++ that is to be linked with an executable file built using any vendor's tools, you must perform some additional work.

I already mentioned the issue of using the extern "C" modifier when you mix C and C++ programming. I also mentioned the issue of C++ classes and how because of name mangling you must use the same compiler vendor's tools. Another issue comes up even when you use straight C programming with multiple tool vendors. The problem is that Microsoft's C compiler mangles C functions even if you're not using C++ at all. This happens only if your function uses the _ _stdcall (WINAPI) calling convention. Unfortunately, this calling convention is the most popular type. When C functions are exported using _ _stdcall, Microsoft's compiler mangles the function names by prepending a leading underscore and adding a suffix of an @ sign followed by a number that indicates the count of bytes that are passed to the function as parameters. For example, this function is exported as _MyFunc@8 in the DLL's export section.

 _ _declspec(dllexport) LONG _ _stdcall MyFunc(int a, int b); 

If you build an executable using another vendor's tools, it will attempt to link to a function named MyFunc—a function that does not exist in the Microsoft compiler_built DLL—and the link will fail.

To build a DLL with Microsoft's tools that is to be linked with other compiler vendor's tools, you must tell Microsoft's compiler to export the function names without mangling. You can do this in two ways. The first way is to create a .def file for your project and include in the .def file an EXPORTS section like this:

 EXPORTS MyFunc 

When Microsoft's linker parses this .def file, it sees that both _MyFunc@8 and MyFunc are being exported. Because these two function names match (except for the mangling), the linker exports the function using the .def file name of MyFunc and does not export a function with the name of _MyFunc@8 at all.

Now, you might think that if you build an executable with Microsoft's tools and attempt to link to the DLL containing the unmangled name, the linker will fail because it will try to link to a function called _MyFunc@8. Well, you'll be pleased to know that Microsoft's linker does the right thing and links the executable to the function named MyFunc.

If you want to avoid using a .def file, you can use the second way of exporting an unmangled version of the function. Inside one of the DLL's source code modules, you add a line like this:

 #pragma comment(linker, "/export:MyFunc=_MyFunc@8") 

This line causes the compiler to emit a linker directive telling the linker that a function called MyFunc is to be exported with the same entry point as a function called _MyFunc@8. This second technique is a bit less convenient than the first because you must mangle the function name yourself to construct the line. Also, when you use this second technique, the DLL actually exports two symbols identifying a single function—MyFunc and _MyFunc@8—whereas the first technique exports only the MyFunc symbol. The second technique doesn't buy you much—it just lets you avoid using a .def file.



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net