- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我目前正在一个项目中,该项目具有许多用C++和ATL编写的COM对象。
当前,它们都在直接编译到COM DLL中的.cpp和.idl文件中定义。
为了使单元测试更容易编写,我计划将COM对象的实现移到一个单独的静态库中。然后可以将该库链接到主DLL和单独的单元测试项目中。
我假设ATL生成的代码没有什么特别的,并且在与静态库链接时,它的工作原理与所有其他C++代码一样。但是,我本人对ATL的了解并不多,所以不知道是否确实如此。
我会按预期工作吗?还是我应该注意的陷阱?
最佳答案
因为只有在引用了LIB的情况下才将它们引入,所以存在一些陷阱,这与明确包含的OBJ相反。
Larry Osterman discussed some of the subtleties a few years ago:
When I moved my code into a library, what happened to my ATL COM objects?
A caveat: This post discusses details of how ATL7 works. For other version of ATL, YMMV. The general principals apply for all versions, but the details are likely to be different.
My group’s recently been working on reducing the number of DLLs that make up the feature we’re working on (going from somewhere around 8 to 4). As a part of this, I’ve spent the past couple of weeks consolidating a bunch of ATL COM DLL’s.
To do this, I first changed the DLLs to build libraries, and then linked the libraries together with a dummy DllInit routine (which basically just called
CComDllModule::DllInit()
) to make the DLL.So far so good. Everything linked, and I got ready to test the new DLL.
For some reason, when I attempted to register the DLL, the registration didn’t actually register the COM objects. At that point, I started kicking my self for forgetting one of the fundamental differences between linking objects together to make an executable and linking libraries together to make an executable.
To explain, I’ve got to go into a bit of how the linker works. When you link an executable (of any kind), the linker loads all the sections in the object files that make up the executable. For each extdef symbol in the object files, it starts looking for a public symbol that matches the symbol.
Once all of the symbols are matched, the linker then makes a second pass combining all the .code sections that have identical contents (this has the effect of collapsing template methods that expand into the same code (this happens a lot with
CComPtr
)).Then a third pass is run. The third pass discards all of the sections that have not yet been referenced. Since the sections aren’t referenced, they’re not going to be used in the resulting executable, so to include them would just bloat the executable.
Ok, so why didn’t my ATL based COM objects get registered? Well, it’s time to play detective.
Well, it turns out that you’ve got to dig a bit into the ATL code to figure it out.
The ATL COM registration logic gets picked in the
CComModule
object. Within that object, there’s a methodRegisterClassObjects
, which redirects toAtlComModuleRegisterClassObjects
. This function walks a list of_ATL_OBJMAP_ENTRY
structures and calls theRegisterClassObject
on each structure. The list is retrieved from them_ppAutoObjMapFirst
member of theCComModule
(ok, it’s really a member of the_ATL_COM_MODULE70
, which is a base class for theCComModule
). So where did that field come from?It’s initialized in the constructor of the
CAtlComModule
, which gets it from the__pobjMapEntryFirst
global variable. So where’s__pobjMapEntryFirst
field come from?Well, there are actually two fields of relevance,
__pobjMapEntryFirst
and__pobjMapEntryLast
.Here’s the definition for the
__pobjMapEntryFirst
:__declspec(selectany) __declspec(allocate("ATL$__a")) _ATL_OBJMAP_ENTRY* __pobjMapEntryFirst = NULL;
And here’s the definition for
__pobjMapEntryLast
:__declspec(selectany) __declspec(allocate("ATL$__z")) _ATL_OBJMAP_ENTRY* __pobjMapEntryLast = NULL;
Let’s break this one down:
__declspec(selectany)
:__declspec(selectany)
is a directive to the linker to pick any of the similarly named items from the section – in other words, if a__declspec(selectany)
item is found in multiple object files, just pick one, don’t complain about it being multiply defined.
__declspec(allocate("ATL$__a"))
: This one’s the one that makes the magic work. This is a declaration to the compiler, it tells the compiler to put the variable in a section named"ATL$__a"
(or"ATL$__z"
).Ok, that’s nice, but how does it work?
Well, to get my ATL based COM object declared, I included the following line in my header file:
OBJECT_ENTRY_AUTO(<my classid>, <my class>)
OBJECT_ENTRY_AUTO
expands into:#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)Notice the declaration of
__pobjMap_##class
above – there’s thatdeclspec(allocate("ATL$__m"))
thingy again. And that’s where the magic lies. When the linker’s laying out the code, it sorts these sections alphabetically – so variables in theATL$__a
section will occur before the variables in theATL$__z
section. So what’s happening under the covers is that ATL’s asking the linker to place all the__pobjMap_<class name>
variables in the executable between__pobjMapEntryFirst
and__pobjMapEntryLast
.And that’s the crux of the problem. Remember my comment above about how the linker works resolving symbols? It first loads all the items (code and data) from the OBJ files passed in, and resolves all the external definitions for them. But none of the files in the wrapper directory (which are the ones that are explicitly linked) reference any of the code in the DLL (remember, the wrapper doesn’t do much more than simply calling into ATL’s wrapper functions – it doesn’t reference any of the code in the other files.
So how did I fix the problem? Simple. I knew that as soon as the linker pulled in the module that contained my COM class definition, it'd start resolving all the items in that module. Including the
__objMap_<class>
, which would then be added in the right location so that ATL would be able to pick it up. I put a dummy function call calledForceLoad<MyClass>
inside the module in the library, and then added a function calledCallForceLoad<MyClass>
to my DLL entry point file (note: I just added the function – I didn’t call it from any code).And voila, the code was loaded, and the class factories for my COM objects were now auto-registered.
What was even cooler about this was that since no live code called the two dummy functions that were used to pull in the library, pass three of the linker discarded the code!
关于c++ - 是否可以在静态库中编写COM代码,然后将其链接到DLL?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7935619/
我正在开发一个 voip 调用应用程序。我需要做的是在接到来电时将 Activity 带到前台。我在应用程序中使用 Twilio,并在收到推送消息时开始调用。 问题是我试图在接到任何电话时显示 Act
我是一名优秀的程序员,十分优秀!