1.引入问题
公司产品有大量模块(vcproj)需要在日常和出货时构建,有一个专门的批处理(BuildOrder_XXX.bat)去管理这些模块的构建顺序,每个开发者在写新模块后,都需要将它加入批处理文件内某一行,这没问题。但后续某个模块增加了对其它模块的隐式依赖(一般是使用了其它模块的导出函数)后,如果忘记也修改 vcproj 在批处理里的顺序,就会导致编译报错,需要跑几遍批处理,而且顺序也不太好调整。受困于此,我们可以引入构建顺序管理工具,专门用来分析和管理各模块的构建顺序。
2.分析问题
如何知道模块之间的隐式依赖关系呢?
首先想到的是 solution 文件内的项目依赖,但是如果开发者没有设置依赖关系怎么办?或者两个vcproj根本没有在同一个solution内怎么办?所以此办法无法解决问题。
接着想到的是 Dependency Walker,这个软件用来查看模块间的隐式依赖关系,是通过分析 PE 文件的导入表得来,需要先有编好的可执行文件,这个可以满足。网上查找到怎么获取PE导入表的代码如下:
HRESULT ParseFilePEDependency(LPCSTR szPEPath)
{
HMODULE hMod = LoadLibraryExA(szPEPath, NULL, DONT_RESOLVE_DLL_REFERENCES);
if( !hMod )
return S_OK; //输出文件不存在
std::string sModDepend;
do{
// Import table
IMAGE_DOS_HEADER* IDH = (IMAGE_DOS_HEADER*)hMod;
if( !IDH )
break;
IMAGE_OPTIONAL_HEADER* IOH = (IMAGE_OPTIONAL_HEADER*)((BYTE*)hMod + IDH->e_lfanew + 24);
if( !IOH )
break;
IMAGE_IMPORT_DESCRIPTOR* IID = (IMAGE_IMPORT_DESCRIPTOR*)((BYTE*)hMod + IOH->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
if( !IID )
break;
while(IID->FirstThunk)
{
IMAGE_THUNK_DATA* ITD =(IMAGE_THUNK_DATA*)((BYTE*)hMod+ IID->OriginalFirstThunk);
if( IsBadReadPtr(ITD, sizeof(IMAGE_THUNK_DATA)) )
break;
sModDepend = ((char const *)hMod + IID->Name);//这个 sModDepend 就是每一个被 szPEPath 所隐式依赖的 DLL 名字了
++IID;
}
}while(0);
FreeLibrary(hMod);
return S_OK;
}
实际跑起来后,发现另一个问题,sModDepend 会有许多 windows 自己的 DLL,例如 ntdll.dll, kernel32.dll 等,还有其它的我们不关心的无源码的第三方库,这些都可以排除。实际做法有两个:
第一个是从配置文件内读一个排除列表,这个方法比较麻烦,而且维护不易,因为外部DLL太多了,维护不易,废弃。
第二个是只记在批处理内的模块输出的可执行文件,这个方法比较好,但需要保证所有模块不能有相同的可执行模块名(相信每个大型软件都可以做到)。
想实现第二个办法,需要解析批处理文件、.sln文件和.vcproj文件。