Using __declspec(dllimport) and __declspec(dllexport)

OverviewHow Do IFAQDetailsSample

The 32-bit edition of Visual C++ uses __declspec(dllimport) and __declspec(dllexport) to replace the __export keyword previously used in 16-bit versions of Visual C++.

You do not need to use __declspec(dllimport) for your code to compile correctly, but doing so allows the compiler to generate better code. The compiler is able to generate better code because it knows for sure whether a function exists in a DLL or not, so the compiler can produce code that skips a level of indirection that would normally be present in a function call that crossed a DLL boundary. However, you must use __declspec(dllimport) in order to import variables used in a DLL.

With the proper .DEF file EXPORTS section, __declspec(dllexport) is not required. __declspec(dllexport) was added to provide an easy way to export functions from an .EXE or .DLL without using a .DEF file.

The Win32 Portable Executable format is designed to minimize the number of pages that must be touched to fix imports. To do this, it places all the import addresses for any program in one place called the Import Address Table. This allows the loader to modify only one or two pages when accessing these imports.

Using _declspec(dllimport) for Function Calls

In the following code example, assume func1 is a function that resides in a DLL separate from the .EXE file that contains the main function.

Without __declspec(dllimport), given this code:

   void main(void)
   {
       func1();
   }

the compiler generates code that looks like this:

    call func1

and the linker translates the call into something like this:

    call 0x4000000         ; The address of 'func1'.

If func1 exists in another DLL, the linker can't resolve this directly because it has no way of knowing what the address of func1 is. In 16-bit environments, the linker adds this code address to a list in the .EXE that the loader would patch at run time with the correct address. In 32-bit environments, the linker generates a thunk of which it does know the address. The thunk looks like:

   0x40000000:    jmp DWORD PTR __imp_func1

Here __imp_func1 is the address for func1's slot in the import address table of the .EXE file. All the addresses are thus known to the linker. The loader only has to update the .EXE file's import address table at load time for everything to work correctly.

Therefore, using __declspec(dllimport) is better because if the linker does not generate a thunk if it is not required. Thunks make the code larger (on RISC systems, it can be several instructions) and can degrade your cache performance. If you tell the compiler the function is in a DLL, it can generate an indirect call for you.

So now this code:

   __declspec(dllimport) void func1(void);
   void main(void)
   {
       func1();
   }

generates this instruction:

   call DWORD PTR __imp_func1

There is no thunk and no jmp instruction, so the code is smaller and faster.

On the other hand, for function calls inside a DLL, you don't want to have to use an indirect call. You already know a function's address. Time and space is required to load and store the address of the function before an indirect call, so a direct call is always faster and smaller. You only want to  use __declspec(dllimport) when calling DLL functions from the outside the DLL itself. Don't use __declspec(dllimport) on functions inside a DLL when building that DLL.

Using _declspec(dllexport)

Microsoft introduced __export in the 16-bit compiler version of Visual C++ to allow the compiler to generate the export names automatically and place them in a .LIB file. This .LIB file could then be used just like a static .LIB to link with a DLL.

Microsoft added __declspec(dllexport) to continue this convenience. Its purpose is to add the export directive to the object file so you don't need a .DEF file.

This convenience is most apparent when trying to export decorated C++ function names. There is no standard specification for name decoration, so the name of an exported function may change between compiler versions. If you use __declspec(dllexport), recompiling the DLL and dependent .EXE files is necessary only to account for any naming convention changes.

Many export directives, such as ordinals, NONAME, and PRIVATE, can be made only in a .DEF file, and there is no way to specify these attributes without a .DEF file. However, using __declspec(dllexport) in addition to using a .DEF file does not cause build errors.

As a reference, search through the Win32 WINBASE.H header file. It contains examples of __declspec(dllimport) usage.

Using __declspec(dllexport) and __declspec(dllimport) on Data

In the case of data, using __declspec(dllimport) is a convenience item that removes a layer of indirection. When you import data from a DLL, you still have to go through the import address table. In the Win32 days before __declspec(dllimport), this meant you had to remember to do an extra level of indirection when accessing data exported from the DLL:

   // project.h
   #ifdef _DLL     // If accessing the data from inside            //the DLL
      ULONG ulDataInDll;

   #else            // If accessing the data from                //outside the DLL
      ULONG *ulDataInDll;
   #endif

You would then export the data in your .DEF file:

   // project.def
   LIBRARY project
   EXPORTS
       ulDataInDll   CONSTANT

and access it outside the DLL:

   if (*ulDataInDll == 0L)
   {
      // Do stuff here
   }

When you mark the data as __declspec(dllimport), the compiler automatically generates the indirection code for you. You no longer have to worry about the steps above. As stated previously, do not use __declspec(dllimport) declaration on the data when building the DLL. Functions within the DLL will not use the import address table to access the data object; therefore, you will not have the extra level of indirection present.

To export the data automatically from the DLL, use this declaration:

   __declspec(dllexport) ULONG ulDataInDLL;

Using a .DEF File

If you choose to use __declspec(dllimport) along with a .DEF file, you should change the .DEF file to use DATA in place of CONSTANT to reduce the likelihood that incorrect coding will cause a problem:

   // project.def
   LIBRARY project
   EXPORTS
       ulDataInDll   DATA

The following table shows why:

Keyword Emits in the import library Exports
CONSTANT _imp_ulDataInDll
_ulDataInDll
_ulDataInDll
DATA _imp_ulDataInDll _ulDataInDll

Using __declspec(dllimport) and CONSTANT lists both the __imp_ version and the undecorated name in the .LIB DLL import library that is created to allow explicit linking. Using __declspec(dllimport) and DATA lists just the __imp_ version of the name.

If you use CONSTANT, either of the following code constructs could be used to access the ulDataInDll:

   __declspec(dllimport) ULONG ulDataInDll; /*prototype*/
   if (ulDataInDll == 0L)   /*sample code fragment*/

    -or-

   ULONG *ulDataInDll;      /*prototype*/
   if (*ulDataInDll == 0L)  /*sample code fragment*/

However, if you use DATA in your .DEF file, only code compiled with the following definition can access the variable ulDataInDll:

  __declspec(dllimport) ULONG ulDataInDll;

  if (ulDataInDll == 0L)   /*sample code fragment*/

Using CONSTANT is more risky because if you forget to use the extra level of indirection, you could potentially access the import address table's pointer to the variable — not the variable itself. This type of problem can often manifest as an access violation because the import address table is currently made read-only by the Microsoft compiler and linkers.

The Current Visual C++ linker issues a warning if it sees CONSTANT in the .DEF file to account for this case. The only real reason to use CONSTANT is if you can't recompile some object file where the header file didn't list __declspec(dllimport) on the prototype.

What do you want to do?