AC2,由VS2005的ATL向导生成的默认COM对象代码分析ATL如何实现COM,第二部分。
OBJECT_ENTRY_AUTO相当不起眼的放在我们声明的类的头文件的最底行,但是位置不起眼作用却相当大,我给它的评价是ATL COM的基石。
以
XXX表示我们自己的com对象类,OBJECT_ENTRY_AUTO在我们的类名和iid上扩展为(我多贴了其他#pragma):
//为了比对,贴一下_ATL_OBJMAP_ENTRY声明
struct _ATL_OBJMAP_ENTRY30
{
/*1*/ const CLSID* pclsid;
/*2*/ HRESULT (WINAPI *pfnUpdateRegistry)(BOOL bRegister);
/*3*/ _ATL_CREATORFUNC* pfnGetClassObject;
/*4*/ _ATL_CREATORFUNC* pfnCreateInstance;
/*5*/ IUnknown* pCF;
/*6*/ DWORD dwRegister;
/*7*/ _ATL_DESCRIPTIONFUNC* pfnGetObjectDescription;
/*8*/ _ATL_CATMAPFUNC* pfnGetCategoryMap;
/*9*/ void (WINAPI *pfnObjectMain)(bool bStarting);
};
typedef _ATL_OBJMAP_ENTRY30 _ATL_OBJMAP_ENTRY;
//OBJECT_ENTRY_AUTO(__uuidof(XXX), XXX)的定义,在atlcom.h中
__declspec(selectany) _ATL_OBJMAP_ENTRY __objMap_XXX = //selectany声明了现在就赋值这个__objMap_XXX,尽管在头文件中
{
/*1*/ &clsid,
/*2*/ XXX::UpdateRegistry,
/*3*/ XXX::_ClassFactoryCreatorClass::CreateInstance,
/*4*/ XXX::_CreatorClass::CreateInstance
/*5*/ NULL,
/*6*/ 0,
/*7*/ XXX::GetObjectDescription,
/*8*/ XXX::GetCategoryMap,
/*9*/ XXX::ObjectMain
};
//我把代码里的美元符号换车¥了,因为只要使用美元符号blog显示就不正常
//这里不但告诉编译器现在就赋值,而且要把该变量写入符号表保存
extern "C" __declspec(allocate("ATL¥__m")) __declspec(selectany) _ATL_OBJMAP_ENTRY* const __pobjMap_XXX = &__objMap_XXX;
__pragma(comment(linker, "/include:___pobjMap_XXX")); //编译器要求保存入符号表的变量应该加_,所以加了一个
//为了方便查找,ATL在符号表里定义了另外两个ATL¥__分别做begin、end标志
//在atlbase.h中
#pragma section("ATL¥__a", read, shared)
#pragma section("ATL¥__z", read, shared)
#pragma section("ATL¥__m", read, shared)
extern "C"
{
__declspec(selectany) __declspec(allocate("ATL¥__a")) _ATL_OBJMAP_ENTRY* __pobjMapEntryFirst = NULL;
__declspec(selectany) __declspec(allocate("ATL¥__z")) _ATL_OBJMAP_ENTRY* __pobjMapEntryLast = NULL;
}
//当连接器布局代码时,它会根据字母排序组织段
通过OBJECT_ENTRY_AUTO宏,dll在编译阶段定位信息得以保存,因而dll启动的时候就有了它需要的信息(只要保存了)。对于com这种在启动的时候需要和外部库有太多交互的设计,保存这些信息使得交互变为可行,不然的话,com库找不到函数,dll本身还没有初始化,就变成鸡生蛋还是蛋生鸡的问题了。再从com的二进制设计思想来看,也必须要有这样的实现,dll通过使用这种规则使得任意com对象都可以在某个二进制位置上找到特定函数(通过符号表找到符号)。