Dependency Analysis for Windows C++ Project
C++ Win32 Library中文在下面👇
Introduction
When deconstructing and porting complex C++ projects, managing and analyzing project file dependencies is crucial. Online game development involves a myriad of dependencies and library files: from engines to UI; from clients to rendering and media libraries to servers. Clarifying the dependency relationships within a project is a prerequisite for porting and refactoring. Trying to port or refactor without understanding these dependencies is like a blind person feeling an elephant — you don’t know where to start.
This article will introduce my experience in analyzing dependencies of large C++ projects on the Windows platform.
Visual Studio’s Project and Solution
Projects and Solutions are logical descriptions of C++ modules in Visual Studio (referred to as VS hereafter), where a Solution is a collection of Projects. Generally, without using CMake templates, compilation and linking are done at the Project level, producing either C++ executable files or C++ libraries (static or dynamic). Projects can depend on each other since the output objects can have dependencies; for example, an executable generated by Project A may depend on a static or dynamic library generated by Project B during compilation or runtime, or it may depend on an import library.
Usually, configuring a C++ project is done at the Project level. By right-clicking on a Project entry, developers can easily use the VS GUI to set build options for the Project. These parameters are persistently stored locally in configuration files. Compiler options, include file paths, linker search paths, target settings, etc., are key points to consider. It’s generally recommended to configure projects via the GUI rather than directly modifying the XML configuration files, which can be thousands of lines long. By the way, if you have installed LLVM-related development tools via the VS installer, you can find the clang-cl compiler in the configuration options, which uses Clang as the frontend and Windows MSVC platform as the backend compiler.
Classification of Library Files and Import Libraries
Before diving into dependency analysis, we need to understand how dependencies are manifested. Typically, in a C++ program, dependencies are exhibited in executable programs relying on libraries and libraries depending on other libraries. In Windows, static libraries end with .lib
, while dynamic libraries end with .dll
. Often, you’ll see files with the same name but different extensions (e.g., .lib
and .dll
) in a project; this is likely due to the import library mechanism on Windows. Simply put, during compilation, a program needs to link the import library corresponding to a dynamic library to use that dynamic library with the dynamic linker during execution. This is somewhat similar to how C++ compilation and linking phases require including library header files to obtain symbols from the library, allowing the compiler linker to generate executable files successfully.
Dependency Analysis Methods
How do we analyze dependencies? If you want to analyze dynamic library dependencies or executable files, you can use tools like Dependency Walker or Dependencies. As of 2024, Dependency Walker supports detecting dependency errors (such as checking for circular dependencies), claims to be able to find all dependencies, supports persistence by saving dependency relationships in a text format locally (though readability can be challenging). On the other hand, Dependencies doesn’t have the aforementioned features but is notably faster in parsing dependencies compared to Dependency Walker. One tricky aspect is that Windows’ library file matching rules are case-insensitive, so you may need to manually adjust the case when organizing or writing reports.
As for static library dependency analysis, we can refer to the toolchain configuration files for insights. In theory, static libraries cannot be analyzed externally by third-party software. This provides a natural security advantage for static libraries compared to dynamic libraries (of course, with clear disadvantages such as static size, memory usage during execution, incremental compilation speed, etc.). In the gaming industry, it’s quite common to distribute static libraries since developers prefer not to expose too much of their code implementation to hackers.
Runtime
During dependency analysis, you often encounter library names containing “RT,” which stands for runtime. Runtime generally refers to the environment required for a program to run. The kernel is also part of the runtime.
PlantUML
PlantUML is a software that helps programmers represent project dependencies. Its advantages include simplicity, extensibility, separate source code storage that can be merged, automatic formatting, among others.
Conclusion
Dependency analysis is a prerequisite step for porting and refactoring C++ projects. On the Windows platform, we can use tools like the VS GUI, Dependency Walker, Dependencies, inspect toolchain configuration files, PlantUML, etc., to help us perform dependency analysis.
Introduction
在解构,移植复杂C++工程时,对项目文件的依赖管理与分析至关重要。在线游戏开发涉及千奇百怪的依赖和库文件:下到引擎,上至UI;前有客户端,后有渲染与媒体库与服务器。理清项目的依赖关系是对项目进行移植,重构的先决步骤。搞不清楚依赖关系就进行移植和重构,好似盲人摸象,无从下手。
这篇文章将介绍我在Windows平台上分析大型C++工程依赖时的经验。
Visual Studio的Project与Solution
Project和Solution是Visual studio(以下简称VS)对C++模块的逻辑描述,Solution是Project的集合。一般来说,在不使用CMake模板的情况下,编译和链接以Project为单位进行,其输出可以为C++可执行文件,也可以是C++的库:静态库,动态链接库。Project之间可以互相依赖,因为输出的对象之间可以存在依赖关系:比如A项目生成的A可执行文件在编译阶段,或者运行阶段将依赖B项目生成的静态库,动态库,亦或是import library。
一般来说,对C++项目的配置也是以Project为最小单位进行的。右击Project条目,程序员可以方便地以VS的GUI界面对Project的Build选项进行设置。这些参数都会以配置文件的形式持久化地存储在本地。其中编译器选项,include file path,linker的搜索路径,target设置,都是需要关注的重点。一般还是建议通过GUI对工程配置进行设置,而不是直接打开配置文件修改动辄上千行的XML文件。顺便提一句,如果在VS installer中安装了LLVM相关的开发工具,你便能在配置选项中找到clang-cl这个以Clang为前端,Windows MSVC平台为编译器后端的编译器。
库文件的分类与import library
在进行依赖分析之前,我们要明白依赖的表现形式是什么。一般来说,C++程序的依赖关系表现在可执行程序依赖库以及库和库之间互相依赖。在Windows下,静态库以.lib
结尾,动态库以.dll
结尾。在很多时候,你能看见项目中存在同名的.lib
和.dll
文件,这大概率是因为Windows下的import library机制在作祟。简单来说程序需要在编译阶段将动态库对应的导入库链接,才可以顺利在执行阶段用动态链接器使用动态库。这有一点类似C++编译链接阶段需要include库的头文件,从而获得库里的符号才能让编译器链接器成功生成可执行文件。
依赖分析方法
如何分析依赖?如果希望解析动态库依赖或者可执行文件的话可以使用Dependency Walker或者Dependencies这两款工具。截止2024年,前者支持依赖error的检测(比如检测是否存在循环依赖),“号称”能搜到全部的依赖,支持持久化,能将依赖关系以文本格式保存在本地(可读性堪忧)。后者没有上述功能,但解析速度明显比前者快不少。比较棘手的是Windows下库文件的match规则是不区分大小写的,在写报告做整理的时候需要手工改大小写。
至于静态库的依赖分析,我们可以通过查看工具链配置文件来了解。静态库理论上是无法通过外部第三方解析软件进行解析的。这让静态库和动态库比有天然的安全优势(当然劣势也很明显,静态体积,执行时内存占用体积,增量编译速度etc。。。)。在游戏产业中,静态库发布是蛮常见的,毕竟开发者不希望代码的实现过多地暴露给黑客。
Runtime
在进行依赖分析的时候,经常能看见库的名字中含有RT。一般这是runtime的简称。Runtime一般指程序运行时所需要的环境。Kernel也是runtime的一部分。
PlantUML
PlantUML是一款帮助程序员表示项目依赖的软件。简洁,可扩展,源代码可以分开保存,一同merge,自动排版,这些都是他的优势。
Conclusion
依赖分析是C++工程移植,重构的先决步骤。在Windows平台上,我们可以使用VS的GUI界面,Dependency Walker,Dependencies,查看工具链配置文件,PlantUML等工具来帮助我们完成依赖分析。