What happens when you get dllimport wrong?

Now that we've learned what the dllimport declaration specifier does, what if you get it wrong?

If you forget to declare a function as dllimport, then you're basically making the compiler act like a naive compiler that doesn't understand dllimport. When the linker goes to resolve the external reference for the function, it will use the stub from the import library, and everything will work as before. You do miss out on the optimization that dllimport enables, but the code will still run. You're just running in naive mode.

(There are still some header files in the Platform SDK that neglect to use the dllimport declaration specifier. As a result, anybody who uses those header files to import functions from the corresponding DLL will be operating in "naive mode". Hopefully the people responsible for those header files will recognize themselves in this parenthetical and fix the problem for a future release of the Platform SDK.)

Now, what about the reverse problem? What if you declare a function as dllimport when it really isn't? The linker detects this since it sees an attempt to import a __imp__FunctionName symbol and can't find one, though it can find the normal FunctionName symbol. When this happens, the linker raises warning LNK4217. It recovers from this error by simply manufacturing a fake __imp__FunctionName variable and initializing it with the address of the FunctionName function. In effect, you've imported the function from yourself. Your code now goes through all the gyrations associated with calling an imported function unnecessarily; it could have just called FunctionName directly.

(There are cases where the linker can be a little smarter. For example, if it sees a call [__imp__FunctionName], it can change it to call FunctionName + nop. The nop is necessary because the call [__imp__FunctionName] instruction is six bytes long, whereas call FunctionName is only five. The extra nop gets everything back in sync.)

Thus, in both cases where you mess up the dllimport declaration specifier, the linker manages to recover from your mistake, and your program does run fine, though the patching up did cost you in code size and efficiency.

(All this discussion is for x86, by the way. Other architectures have different quirks.)

Next time, more on import libraries, and exposing some "little white lies" I've been telling.

Comments (12)
  1. nksingh says:

    This seems like a problem for the CRT.  As far as I know, VC gives you the option of statically or dynamically linking the CRT.  But it seems like the headers will have to make a choice to support one thing better than the other.  Conditional compilation would work, but then people would have to remember to include a #define somewhere.  Is this dllimport vs. static linking thing something the compiler could figure out on its own if you’re doing Link-time codegen?

  2. Adam says:

    “All this discussion is for x86, by the way. Other architectures have different quirks.”

    Huh? Surely you mean this is for PE/x86. Other formats (e.g. ELF/x86) will naturally have a different shared library ABI (with related quirks). But given that you’re talking about PE/x86, what other architectures does PE run on? (Aside from PE/x86-64, which will be mostly running 32-bit userspace apps anyway)

    [Ah, the nitpick police are out early today. This is part nine in a series on how Windows imports and exports functions from DLLs. I’d’ve though the context was well-established by now. -Raymond]
  3. nksingh says:

    Adam:  IA-64?  PowerPC (XBox)?

  4. Reinder says:

    My guess would be PE/PPC, on the XBOX 360.

  5. Tyler Reddun says:

    Don’t forget the DEC Alpha, and every other processor that NT used to support. Each one has there own quirks.

  6. glonq says:

    …and don’t forget the bunch of CPU’s that WinCE runs on.

  7. doug says:


    You bet this is an issue for the CRT.  The CRT headers use a lot of conditional compilation.  Look at the file "crtdefs.h".  This defines "_CRTIMP" as either "__declspec(dllimport)" or nothing, depending on whether or not you’re using the DLL version of the CRT.  Every CRT export has "_CRTIMP" (or some variation) in front of it.  Luckily, most people don’t have to remember how this works, so most people don’t have to worry about forgetting to put the _CRTIMP prefix on the functions.

    You do have to do something so the compiler knows what you want, though.  The VC 8.0 CRT uses the _DLL symbol to decide whether or not to import the functions.  You can get this symbol defined (or not) based on a #define in a common header (probably not the best way), by passing -D_DLL or some other symbol as a command-line argument, or by using one of the -M?? switches (which is like passing -D_DLL plus some other stuff).

  8. Adam says:

    Right – the context *is* well-established now.

    *That’s what confused me!*

    I’m trying to figure out why the clarification was necessary, or what other information it gave us than that which was already implied by the *last couple of weeks* posts (which have been excellent). I couldn’t figure out was the reason for the clarification was, so assumed I must have been missing something. (Happens a lot :)

    Sorry to come across as one of the Nitpick Police.

    /me tiptoes away, trying to not draw any more attention…

  9. Mike says:

    Raymond, you mentioned in a previous entry in this series “Next time, we’ll look at the dllexport declaration specifier…”. I have yet to find a mention of it. Did you decide to save it for later, did it somehow got lost, or am I just blind?

    (Me, I think dllexport simply tags the name for publication, much like a .def file would do, but without the power of the .def file to rename exported symbols and so on)

    On the subject of __imp prepended function names – is that the only thing __dllimport does – renaming the entry in the import-lib (actually, more like providing the mapping __imp_X -> X) to be externally visible as __imp__sym for the symbol sym in the DLL the import library is generated for?

    [Or (3) I mean dllimport, not dllexport. Typo. dllimport influences the codegen of the DLL’s clients. It control how the DLL is consumed not how it is produced. -Raymond]
  10. Mike says:

    How appropriate that I wrote "__dllimport" when I intended __dllexport to generate the names. :-)

    Still, does __dllexport do anything else but create the name __imp__X for X in the import library, even if subtle things like "slightly" modify optimizations or anything else? Even if not, how exactly does that work (on a COFF level if you don’t mind)? There must be a "mapping" stored in the COFF object file for the linker to pick it up, no? Is it simply that addr X get referred by the name real_fn as tatic, and __imp__real_fn as public?

  11. Tom says:

    Does anyone have any idea of how much the __dllimport optimisation is worth these days? I mean how much faster is the optimised case than the "naive mode" case on modern CPUs?

  12. performance says:

    Is the optimized call always slower than a local call? File size increases one extra byte, why?

    [Sigh. I already answered the extra byte question in the fifth paragraph. Please read the article before asking questions. I’m not going to talk about performance issues because performance is complicated. Different CPUs behave differently. -Raymond]

Comments are closed.