Как исполняющая среда языка C определяет, когда использовать версию заголовочного файла для статического связывания, а когда — для динамического?

В комментариях к разъяснениям, что произойдет, если вы некорректно укажете опцию dllimport, участник nksingh спросил: «Похоже, что здесь возникнет проблема для исполняющей среды. Насколько я знаю, Visual C++ дает вам возможность подключать библиотеку времени исполнения статически или динамически. Но, похоже, что заголовочные файлы должны сделать выбор: что поддерживать лучше, а что хуже. Здесь помогла бы условная компиляция, но тогда разработчики должны не забывать добавлять куда-нибудь директиву #define. Выходит, что при использовании опции «Создание кода во время компоновки» компилятор должен сам определять, используется ли dllimport или статическое связывание?»

Давайте, начнем с самого начала.

Да, это будет проблемой для исполняющей среды, поскольку она не сможет узнать, как определять функции: как обычные статические функции или dllimport-функции, и этот выбор ложится на заголовочные файлы.

И если вы взглянете в заголовочные файлы, вы можете обнаружить, что, действительно, этот выбор производится при помощи условной компиляции.

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

Магический символ _CRTIMP определен в crtdefs.h примерно так:

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

Условная компиляция решает, преобразовывается ли _CRTIMP в __declspec(dllimport) или не преобразовывается ни во что, в зависимости от того, определен ли символ _DLL или нет.

Но, опять же, никто не утруждает себя написанием #define _DLL перед строкой #include . Здесь должно быть что-то еще.

Впрочем, мы можем провести несколько экспериментов, чтобы разобраться в том, как это все работает.

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

Сохраним этот код в файл dummy.c и запустим несколько тестов.

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"

Ну, вот и все понятно. Компилятор использует флаги /MT и /MD для определения, включать или не включать препроцессорный символ _DLL, который является секретным сигналом для управления условной компиляцией в заголовочном файле crtdef.h.

Компилятор использует эту технику вместо откладывания решения до момента генерации кода во время компоновки, потому что он не может предполагать, что все разработчики используют этот вид генерации кода. (Действительно, мы явно указали не использовать эту опцию в наших тестовых командах).

Если же генерация кода во время компоновки была включена, можно ли отложить это решение до момента компоновки?

В принципе, да, потому что генерация кода во время компоновки в теории может просто записать копию исходных файлов (и всех заголовочных файлов) в файл .obj и выполнить компиляцию непосредственно во время компоновки. Это несколько экстремальный способ, но, полагаю, он вполне осуществим.

С другой стороны, это также означает, что парни, разрабатывавшие компилятор, должны были придумать новое нестандартное расширение, которое означает «эта функция может быть как обычной статической функцией, так и dllimport-функцией, я еще не решил; я скажу тебе позже».

Поскольку исполняющей среде уже пришлось решить эту проблему в случае, когда генерация кода во время компоновки отключена, добавление соответствующей возможности, которая вам, собственно, и не нужна, в процесс генерации кода во время компоновки не будет стоить затраченных усилий. Она могла бы использоваться только в том случае, когда единственным клиентом является сама библиотека времени выполнения, когда этой библиотеке требуется отдельное решение, при котором генерация кода во время компоновки отключена, и когда это решение должно работать и в том случае, когда генерация кода во время компоновки включена.

Ни одна задача разработки не решается написанием кода лишь ради написания кода.