Ситуации, когда должны создаваться функции-заглушки.


Перевод с блога The Old New Thing. Оригинал здесь.

Я отмечал в прошлый раз, что вы можете создать ситуации, которые приведут к созданию заглушек для импортируемых функций. Например, если вы объявили глобальный указатель на функцию:

DWORD (WINAPI *g_pGetVersion)() = GetVersion;

то С компилятор вынужден сгенерировать функцию-заглушку и присвоить переменной g_pGetVersion ее адрес. Это лучшее, что он может сделать, поскольку загрузчик будет модифицировать только таблицу адресов импортируемых функций, не затрагивая сегмент данных.

С++ компилятор, с другой стороны, может использовать немного "магии С++" и тайно сгенерировать "псевдо-глобальный конструктор" (я сам выдумал этот термин, не рекомендую вам его использовать), который копирует значение из таблицы адресов импортируемых функций в переменную g_pGetVersion во время выполнения программы. Обратите, однако, внимание, что, так как это происходит во время работы программы, вперемешку с вызовами остальных глобальных  конструкторов, то переменная может быть неинициализирована, если вы обращаетесь к ней из конструкторов глобальных объектов.

Рассморим следующую программу, состоящую из двух файлов.

 // file1.cpp

#include <windows.h>

EXTERN_C DWORD (WINAPI *g_pGetVersion)();

class Oops {

  public: Oops() { g_pGetVersion(); }

} g_oops;

 

int __cdecl main(int argc, char **argv)

{

  return 0;

}

 

// file2.cpp

#include <windows.h>

EXTERN_C DWORD (WINAPI *g_pGetVersion)() = GetVersion;

Правила конструирования глобальных объектов в С++ таковы, что глобальные объекты в одной единице трансляции создаются в том порядке, в каком они объявлены (и разрушаются в обратном порядке), но не существует предопределенного порядка для глобальных объектов из разных единиц трансляции.

Но ведь есть еще и зависимости в порядке конструирования. Создание объекта g_oops требует, чтобы объект g_pGetVersion уже был сконструирован, потому он собирается выполнить вызов через этот указатель, во время  работы конструктора класса Oops.

Так оказалось, что компоновщик Microsoft создает глобальные объекты в том порядке, в каком соответствующие OBJ файлы перечислены в командной строке компоновщика. (Я не знаю, гарантировано ли такое поведение или это просто детали реализации, так что не буду утверждать наверняка.) Следовательно, если вы скажете компоновщику link file1.obj + file2.obj, вы получите свал, потому что компоновщик сгенерирует вызов Oops::Oops() до конструирования объекта g_pGetVersion. С другой стороны, если вы перечислите файлы в таком порядке file2.obj + file1.obj, все будет работать нормально.

Более удивительный момент: если переименовать file2.cpp в file2.c, то программа будет работать нормально вне зависимости от порядка, в котором obj файлы были переданы компоновщику, поскольку С компилятор использует заглушку вместо того, чтобы копировать адрес функции во время работы программы.

Но что произойдет, если вы перепутали и объявили функцию с директивой dllimport, когда она таковой не является или наоборот? Мы рассмотрим это в следующий раз.

Реклама
Запись опубликована в рубрике DLL. Добавьте в закладки постоянную ссылку.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s