Можно ли написать код COM в статической библиотеке, а затем связать его с DLL? - PullRequest
2 голосов
/ 29 октября 2011

В настоящее время я работаю над проектом, в котором имеется ряд объектов COM, написанных на C ++ с использованием ATL.

В настоящее время все они определены в файлах .cpp и .idl, которые непосредственно скомпилированы в COM DLL.

Чтобы упростить написание модульных тестов, я планирую перенести реализацию COM-объектов в отдельную статическую библиотеку.Затем эту библиотеку можно связать с основной библиотекой DLL и отдельным проектом модульного тестирования.

Я предполагаю, что в коде, сгенерированном ATL, нет ничего особенного, и это будет работать так же, как и все остальные C ++.код, когда дело доходит до связывания со статическими библиотеками.Тем не менее, я сам не очень хорошо знаю ATL, поэтому не знаю, так ли это на самом деле.

Будет ли это работать так, как я ожидал?Или есть подводные камни, на которые я должен обратить внимание?

1 Ответ

1 голос
/ 29 октября 2011

Есть ошибки, поскольку 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 объекты были теперь автоматически зарегистрированы.

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

...