The Ultimate (DLL) Header File |
|
Efficient and appropriate use of header files requires a few tricks. If you simply define a set of functions, everything works fine. No problems. If the file is included more than once because of a complex #include chain, you get redundant declarations. Most compilers, including the Microsoft C/C++ compilers, simply ignore these redundant function and variable declarations. However, if there are any class, typedef, struct, or some other declarations, the compiler will complain bitterly if they are seen twice, claiming they are duplication declarations. It does not choose to see if the structures actually are identical. So there is an idiom to avoid duplication declarations, which is to put the entire contents of the header file in an #ifndef, skipping the body if a symbol defined within the body is already defined. This works acceptably well for short files, but is a performance disaster for lengthy files, since each line of the file must be read. For standard header files, precompiled headers work well in eliminating this problem, but it is harder to justify putting your own headers into a precompiled header file since the effects of a simple change result in a massive recompilation (stdafx.h has to be recompiled). Microsoft has added a #pragma that means the header file is read only once. All of this is shown in detail below.
When constructing a DLL, you need a header file to interface to it. However, the obvious solutions do not work as expected. For example, if you declare a function in a fashion so as to export it, you need to add __declspec(dllexport) to its declaration, and if you don't want name mangling, particularly important if you expect to use the DLL with straight C code as well as C++, you need to define it in the .cpp file as
extern "C" __declspec(dllexport) function-header { // ... whatever }
The problem with this approach is that the obvious solutions to the header file don't work.
The first attempt you might make is to declare, in the .h file
extern "C" function-prototype; // incorrect
This doesn't work because the compiler will complain that the prototype is incompatible with the declaration. What causes this is the prototype lacks the __declspec(dllexport) declaration. So, you say, I'll just add that in. So you get
extern "C" __declspec(dllexport) function-prototype; // incorrect
This doesn't work for a different reason. Although you will no longer get a warning when you compile the DLL, you will get a warning when you compile the application, because the declaration __declspec(dllexport) is incompatible with the use of the function call.
At this point a couple workarounds present themselves. The first one you might consider is to use two header files, one of which has the __declspec(dllexport), and the other of which does not. This is a Bad Idea. The reason is that you lose the consistency checking you would get if you include the same header file into both the DLL compilation and the client compilation. For example, if you change the function to have a different parameter type, the compiler will complain unless you change the DLL's header file prototypes to correspond. But in this case, the DLL header file is completely useless, and might as well not exist, because there is a completely separate header file for the client. It remains uncorrected, and there is no cross-check against the actual module. You lose.
At this point, you could remove the __declspec(dllexport) declarations entirely. This means your one-and-only header file will be consistent with the source, but, whoops, the symbols are not exported. To get around this, you can add either the /exports:function command line switch to the linker command line, or use a .def file and add the function name to the EXPORTS section. This is a valid, correct solution, and it works, but you will find that it quickly becomes inconvenient, because you have to remember to add the function name declaration to the /exports switch or add the name to the .def file. Anyone who did DLLs or Windows programming in Win16 in the days before the __export keyword was added to that language will remember how bad this was. Those of you who did not, take our word for it, it was a real royal pain to deal with this. So I'm disinclined to use this solution unless there are other compelling reasons, which there rarely are.
This is a model which I have cut-and-pasted many times for every DLL I've built in recent years. I've added some comments to help explain and show visually what is going on, and you will probably want to remove these before actually using it.
The uniquename name is something of your own choice. It could be a simple text string, e.g., _DEFINED_MYDLL_HEADER, or you could use GUIDGEN to create a GUID. If you use GUIDGEN, create a "Registry Entry" format, which is
{EBE7C480-A601-11d4-BC36-006067709674}
Remove the { }s and replace the hyphens with underscores. This would give you a symbol like
_DEFINED_EBE7C480_A601_11d4_BC36_006067709674
which guarantees that you will never, ever have a conflict with any other header file.
#ifndef _DEFINED_uniqueheadername //---------------1---------------+ #define _DEFINED_uniqueheadername //---------------1---------------+ // | #if _MSC_VER > 1000 //--------------2---------------+ | #pragma once // | | #endif //----------------------------2---------------+ | // | #ifdef __cplusplus //---------------3---------------+ | extern "C" { // | | #endif // __cplusplus //------------3---------------+ | // | #ifdef _COMPILING_uniqueheadername //-----------4-----------+ | #define LIBSPEC __declspec(dllexport) // | | #else // | | #define LIBSPEC __declspec(dllimport) // | | #endif // _COMPILING_uniqueheadername //--------4-----------+ | // | | LIBSPEC linkagetype resulttype name(parameters); // | | // ... more declarations as needed // | | #undef LIBSPEC //------------------------------4-----------+ | // | #ifdef __cplusplus //----------------5---------------+ | } // | | #endif // __cplusplus //---------------5---------------+ | #endif // _DEFINED_uniqueheadername //-----------------1------------+
The explanation of the "contour lines" is as follows
Now, in the DLL itself, you have to do one thing: you must declare the _COMPILING_uniquename symbol before the #include. After that, you simply declare the function as shown.
#include "stdafx.h" // usually #define _COMPILING_uniquename #include "myDLL.h" extern "C" __declspec(dllexport) linkagetype resulttype name(parameters) { // ... whatever }
and you will not get any compiler conflicts. This will use the same header file for both the DLL and client, and all the Right Things will happen.
The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.