How does the C runtime know whether to use the static-linking or dynamic-linking version of the header file?


In response to a description of what happens when you get dll­import wrong, nksingh asks, "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?"

Let's start from the beginning.

Yes, this would be a problem for the CRT since it wouldn't know whether to declare the functions as normal static functions or as dllimport-style functions, and the headers have to make a choice which way to go.

And if you look at the headers, you can see that it is indeed done via conditional compilation.

...
_CRTIMP int __cdecl fflush(FILE * _File);
...

This magic _CRTIMP symbol is defined in crtdefs.h like so:

/* Define _CRTIMP */
#ifndef _CRTIMP
#ifdef _DLL
#define _CRTIMP __declspec(dllimport)
#else  /* _DLL */
#define _CRTIMP
#endif  /* _DLL */
#endif  /* _CRTIMP */

Conditional compilation decides whether _CRTIMP expands to __declspec(dllimport) or to nothing at all, depending on whether the _DLL symbol is defined.

And yet nobody bothers writing #define _DLL before they #include <stdio.h>. There must be something else going on.

In fact, we can run some experiments to see what's going on.

#ifdef _DLL
#error "_DLL is defined"
#else
#error "_DLL is not defined"
#endif

Save this as dummy.c and run a few tests.

C:\tests> cl /MT dummy.c
dummy.c
dummy.c(4) : fatal error C1189: #error :  "_DLL is not defined"

C:\tests> cl /MD dummy.c
dummy.c
dummy.c(2) : fatal error C1189: #error :  "_DLL is defined"

Well how's about that. The compiler uses the /MT and /MD flag to decide whether or not to define the preprocessor symbol _DLL, which is the secret signal it passes to the crtdef.h header file to control the conditional compilation.

The compiler has to use this technique instead of deferring the decision to link-time code generation because it cannot assume that everybody has enabled link-time code generation. (Indeed, we explicitly did not in our sample command lines.)

If link-time code generation were enabled, then is this something that could be deferred until that point?

In principle yes, because link-time code generation in theory could just make the .obj file a copy of the source file (and all the header files) and do all the actual compiling at link time. This is a sort of extreme way of doing it, but I guess it could've been done that way.

On the other hand, it also means that the compiler folks would have to come up with a new nonstandard extension that means "This function might be a normal static function or it might be a dll­import function. I haven't decided yet; I'll tell you later."

Seeing as how the CRT already has to solve the problem in the case where there is no link-time code generation, it doesn't seem worth the effort to add a feature to link-time-code generation that you don't actually need. It would be a feature for which the only client is the C runtime library itself, for which the C runtime library already requires a separate solution when link-time code generation is disabled, and for which that separate solution still works when link-time code generation is enabled.

No engineering purpose is served by writing code just for the sake of writing code.

Comments (14)
  1. Adam Rosenfield says:

    "And yet nobody bothers writing #define _DLL before they #include <stdio.h>."

    Because doing so would be undefined behavior.  The C language standard reserves all identifiers beginning with an underscore followed by either a capital letter or another underscore, and defining a reserved identifier as a macro is undefined behavior (C90 §4.1.2 or C99 §7.1.3).  That's why all compiler-defined macros begin with /[A-Z]/, so that they won't conflict with any macros user code might write.

  2. Rangoric says:

    @Adam exactly. That wasn't a question or statement saying that people SHOULD do it, it was saying nobody does that, let's find out why. The sentence right after clears this up.

    (I'm beginning to think people are trying to find something wrong with these posts to earn an award or something)

    [Exactly. It's "And yet this works even though nobody sets _DLL." -Raymond]
  3. Crescens2k says:

    @Adam

    Also remember, not everyone who uses C has read the standard. So would they know that _DLL is reserved by the compiler?

  4. Sunil Joshi says:

    @Crescens2k

    That is neither here nor there. If you don't know the standard's rules then you can't complain when unexpected things happen to you. Ignorance of the standard does not protect you.

  5. Joot says:

    Sigh. Language lawyers..

  6. Crescens2k says:

    @Adam

    It is very relevant. You quoted the standard yet it is very likely that most people who use VC, especially learners have never even read the standard. So quoting the standard in this kind of situation is pointless. Also, people tend to follow what they see, and there have been people who used uglified identifiers in classes after using the STL. So if people will use uglified names, is there any surprise if people end up using these definitions without reading the standard?

    @Sunil

    Well it is obvious that if you don't know the rules then you can't be surprised. But people like to stay ignorant. Also, no book/guide etc that I have read even mentions about any preprocessor directives begining with _ are reserved by the compiler. Also, if you look at the place that people are likely to hit first, wikipedia, then again, there is no mention of directives begining with _ is compiler reserved.

  7. waleri says:

    I always wondered why dllimport is needed at all. After all, in the end the things are linked against an import library. Is there any technical reason (other than "it is already done this way") that would require usage of dllimport?

    [Um, that was the topic of the original article that prompted today's question. -Raymond]
  8. KJK::Hyperion says:

    waleri: in short, mismatched dllimport is merely inefficient for functions (it costs a stub for statically linking an imported function, and a data indirection for importing a statically linked function), and completely broken for external data

  9. Adam Rosenfield says:

    @Rangoric: I wasn't trying to find fault with Raymond's post, I was providing additional background information on *why* nobody defines _DLL.

    @Crescens2k: That's irrelevant.  Nobody would go out of there way to explicitly define _DLL because there's no reason to.  It's a short enough (and odd-looking) macro name that hopefully someone would think twice before using it for their own purposes; even if someone did choose to use it, they would run into a macro redefinition error with MSVC with the default settings for new projects (/MD).

  10. Skyborne says:

    [Um, that was the topic of the original article that prompted today's question.]

    I was going to ask how to get that whole series, since that article isn't linked up, then it occurred to me… search for dllimport!  blogs.msdn.com/…/680250.aspx

  11. waleri says:

    Thanks for the clarification. Actually the answer was in one of the articles pointed by Skyborne – why cannot/should not fixup addresses in place. Now I know how it worked before dllimport was invented :)

  12. 640k says:

    Before dllimport/dllexport you used .def

    Actually still the easiest way to compile & use dlls.

  13. GregM says:

    "Actually still the easiest way to compile & use dlls."

    Seriously?  I assume you do not use C++ and work with DLLs with very few exports, because exporting classes or even just name-mangled functions through a .def file is a complete nightmare, especially when you get different name mangling for different configurations.  Having to keep a long list of exports in sync with the source, no thank you.

  14. GregM says:

    "Actually still the easiest way to compile & use dlls."

    Seriously?  I assume you do not use C++ and work with DLLs with very few exports, because exporting classes or even just name-mangled functions through a .def file is a complete nightmare, especially when you get different name mangling for different configurations.  Having to keep a long list of exports in sync with the source, no thank you.

Comments are closed.

Skip to main content