Есть ошибки, поскольку LIB вытягиваются, только если на них ссылаются, в отличие от OBJ, которые явно включены.
Когда я переместил свой код в библиотеку, что случилось с моим ATL COM
объекты?
Предупреждение: В этом посте обсуждаются детали работы ATL7. Для других
версия ATL, YMMV. Общие принципы применяются ко всем
версии, но детали, вероятно, будут отличаться.
Моя группа недавно работала над сокращением количества DLL
которые составляют функцию, над которой мы работаем (откуда-то
около 8 до 4). Как часть этого, я провел последние пару
недели, объединяющие кучу ATL COM DLL.
Для этого я сначала изменил библиотеки DLL для создания библиотек, а затем
связал библиотеки вместе с фиктивной подпрограммой DllInit (которая
в основном просто называется CComDllModule::DllInit()
), чтобы сделать DLL.
Пока все хорошо. Все связано, и я приготовился протестировать новый
DLL.
По какой-то причине, когда я попытался зарегистрировать DLL,
регистрация фактически не регистрирует объекты COM. При этом
точка, я начал пинать себя за то, что забыл один из
фундаментальные различия между связыванием объектов вместе, чтобы сделать
исполняемый файл и связывание библиотек вместе для создания исполняемого файла.
Чтобы объяснить, мне нужно немного разобраться в том, как работает компоновщик. когда
Вы связываете исполняемый файл (любого типа), компоновщик загружает все
разделы в объектных файлах, которые составляют исполняемый файл. Для каждого
Символ extdef в объектных файлах, он начинает искать публичный
символ, соответствующий символу.
Как только все символы совпадают, компоновщик делает второй
передать объединение всех разделов .code, которые имеют идентичное содержимое
(это приводит к свертыванию методов шаблона, которые расширяются в
тот же код (это часто случается с CComPtr
)).
Затем запускается третий проход. Третий проход отбрасывает все
разделы, на которые еще не ссылались. Поскольку разделы
на них не ссылаются, они не будут использоваться в
исполняемый файл, поэтому его включение просто раздувает исполняемый файл.
Хорошо, так почему же мои COM-объекты на основе ATL не были зарегистрированы? Что ж,
пора играть в детектива.
Что ж, получается, что вам нужно немного покопаться в коде ATL, чтобы
разберись.
Логика регистрации ATL COM выбирается в CComModule
объект. Внутри этого объекта есть метод
RegisterClassObjects
, который перенаправляет на
AtlComModuleRegisterClassObjects
. Эта функция просматривает список
_ATL_OBJMAP_ENTRY
структуры и вызовы RegisterClassObject
на каждой структуре. Список извлекается из
m_ppAutoObjMapFirst
член CComModule
(хорошо, это действительно
член _ATL_COM_MODULE70
, который является базовым классом для
CComModule
). Так откуда взялась эта область?
Инициализируется в конструкторе CAtlComModule
, который
получает его из глобальной переменной __pobjMapEntryFirst
. Так где
__pobjMapEntryFirst
поля откуда?
Ну, на самом деле есть две области значимости,
__pobjMapEntryFirst
и __pobjMapEntryLast
.
Вот определение для __pobjMapEntryFirst
:
__declspec(selectany) __declspec(allocate("ATL$__a")) _ATL_OBJMAP_ENTRY* __pobjMapEntryFirst = NULL;
А вот определение для __pobjMapEntryLast
:
__declspec(selectany) __declspec(allocate("ATL$__z")) _ATL_OBJMAP_ENTRY* __pobjMapEntryLast = NULL;
Давайте разберем это:
__declspec(selectany)
: __declspec(selectany)
является директивой
компоновщик, чтобы выбрать любой из предметов с аналогичным именем из раздела
& Ndash; другими словами, если найден элемент __declspec(selectany)
в нескольких объектных файлах, просто выберите один, не жалуйтесь
быть многократно определенным.
__declspec(allocate("ATL$__a"))
: Это тот, который делает
волшебная работа. Это объявление компилятору, оно говорит
компилятор для помещения переменной в раздел с именем "ATL$__a"
(или
"ATL$__z"
).
Хорошо, это хорошо, но как это работает?
Ну, чтобы получить мой объект COM на основе ATLкт объявил, я включил
следующая строка в моем заголовочном файле:
OBJECT_ENTRY_AUTO(<my classid>, <my class>)
OBJECT_ENTRY_AUTO
расширяется до:
#define OBJECT_ENTRY_AUTO(clsid, class) \
__declspec(selectany) ATL::_ATL_OBJMAP_ENTRY __objMap_##class = {&clsid, class::UpdateRegistry, class::_ClassFactoryCreatorClass::CreateInstance, class::_CreatorClass::CreateInstance, NULL, 0, class::GetObjectDescription, class::GetCategoryMap, class::ObjectMain }; \
extern "C" __declspec(allocate("ATL$__m")) __declspec(selectany) ATL::_ATL_OBJMAP_ENTRY* const __pobjMap_##class = &__objMap_##class; \
OBJECT_ENTRY_PRAGMA(class)
Обратите внимание на объявление __pobjMap_##class
выше & ndash; есть
эта declspec(allocate("ATL$__m"))
снова чтоли И вот где
магия лжи Когда компоновщик выкладывает код, он сортирует
эти разделы в алфавитном порядке & ndash; поэтому переменные в ATL$__a
раздел появится перед переменными в разделе ATL$__z
.
Так что под прикрытием происходит то, что ATL спрашивает компоновщика
поместить все переменные __pobjMap_<class name>
в
исполняется от __pobjMapEntryFirst
до __pobjMapEntryLast
.
И в этом суть проблемы. Помните мой комментарий выше о
как работает компоновщик, разрешающий символы? Сначала загружаются все элементы
(код и данные) из файлов OBJ, переданных, и разрешает все
внешние определения для них. Но ни один из файлов в обертке
директория (это те, которые явно связаны)
любой код в DLL (помните, оболочка не делает больше
чем простой вызов функций-оболочек ATL & ndash; это не
ссылаться на любой код в других файлах.
Так как я решил проблему? Просто. Я знал это, как только
компоновщик вытащил модуль, который содержал мое определение класса COM,
он начал бы разрешать все элементы в этом модуле. В том числе
__objMap_<class>
, который затем будет добавлен в нужное место, чтобы ATL смог его забрать. Я поставил фиктивный вызов функции
вызывается ForceLoad<MyClass>
внутри модуля в библиотеке, и
затем добавил функцию под названием CallForceLoad<MyClass>
в мою DLL
файл точки входа (примечание: я только что добавил функцию - я не
звоните из любого кода).
И вуаля, код был загружен, и фабрики классов для моего COM
объекты были теперь автоматически зарегистрированы.
Что было еще круче, так это то, что ни один живой код не назывался
две фиктивные функции, которые использовались, чтобы тянуть в библиотеку, передать
три компоновщика отбросили код!