0. Disclaimer
This question is only about Visual Studio C++ project/solution configuration and may involve subjectivity.
However, the idea behind this post is to share our approaches to configure a large Visual Studio solution.
I'm not considering any tool like CMake
/Premake
here.
1. Problem
- How do you handle a large scaled C++ application architecture and configuration using Visual Studio?
- What is, for you, the best way to setup a new Visual Studio solution composed of multiple projects?
- What Visual Studio project/solution configuration feature are you trying to avoid? (Ex: Filters instead of folders)
2. Personnal approach
2.1. Context
I'm a software developer for a video game company, so I will take a very simplified game engine architecture to illustrate my words:
2.2. File Structure
My Visual Studio solution would probably look something like this:
Where Application
is an executable and every other projects are libraries (Dynamically linked).
My approach would be to separate each project into two folders: include
, src
And the inner structure would be separated into folders following my namespaces:
2.3. Project Configuration
The following lines will assume there is only one $(Configuration)
and $(Platform)
available (Ex: Release-x64
) and that referencing .lib files into Linker/Input/Additional Dependencies
is done for each project.
I would here define a Bin
(Output), Bin-Int
(Intermediate output) and Build
(Organized output) folder, let's say they are located in $(SolutionDir)
:
$(SolutionDir)Bin\
$(SolutionDir)Bin-Int\
$(SolutionDir)Build\
The Bin
and Bin-Int
folders are playgrounds for the compiler, while the Build
folder is populated by each project post-build event:
$(SolutionDir)Build\$(ProjectName)\include\
(Project includes)$(SolutionDir)Build\$(ProjectName)\lib\
(.lib files)$(SolutionDir)Build\$(ProjectName)\bin\
(.dll files)
This way, each $(SolutionDir)Build\$(ProjectName)\
can be shared as an independent library.
Note: Following explainations may skip $(SolutionDir)
from the Build
folder path to simplify reading.
If B
is dependent of A
, Build\B\include\
will contain B
and A
includes. The same way, Build\B\bin\
will contain B
and A
binaries and Build\B\lib\
will contain B
and A
.lib files (If and only if B
is ok to expose A
to its user, otherwise, only B
.lib files will be added to Build\B\lib\
).
Projects reference themselves relatively to Build\
folders. Thus, if B
is dependent of A
, B
include path will reference $(SolutionDir)Build\A\include\
(And not $(SolutionDir)A\include\
), so any include used by A
will be available for B
without specifying it explicitly. (But result to sections 2.6.2.
, 2.6.3.
and 2.6.4.
technical limitations).
After that, I make sure that my solution has a proper Project Dependencies
configuration so the Build Order
, when building the whole solution, will consider and respect my project dependencies.
2.4. User Project Configuration
Our EngineSDK
user (Working onto Application
) will only have to setup Application
such as:
- Include Directories:
$(SolutionDir)Build\Engine\include\
- Library Directory:
$(SolutionDir)Build\Engine\lib\
- Post-build: Copy
$(SolutionDir)Build\Engine\bin\*
to$(OutDir)
- Additional Dependencies: Any .lib file upstream in the dependency hierarchy is listed here
This is the typical Visual Studio configuration flow of a lot of C++ library.
Common library folder architecture that I try to preserve:
lib\
include\
bin\
Here are some example of libraries using this folder architecture model (Do note that bin
is exclusively for dynamically linked libraries as statically linked libraries don't bother with DLLs):
- SFML: https://www.sfml-dev.org/
- SDL: https://www.libsdl.org/
2.5. Advantages
- Clear folder architecture
- Ability to export a library directly by copy-pasting or zipping a sub-folder of
$(SolutionDir)Build\
2.6. Technical limitations
The approach I wrote here has some drawbacks. These limitations are the reason of this post as I want to improve myself:
2.6.1. Tedious configuration
Dealing with 10 or less projects is fine, however, with bigger solutions (15+ projects), it can quickly become a mess. Project configurations are very rigid, and a small change in project architecture can result into hours of project configuration and debugging.
2.6.2. Post-build limitation
Let's consider a simple dependency case:
C
is dependent ofB
andB
is dependent ofA
.C
is an executable, andB
andA
are librariesB
andA
post-build events update theirBuild\$(ProjectName)\
directory
When changing the source code of A
, then compiling it, Build\A\
will get updated. However, as B
has been previously compiled (Before A
changes), its Build\B\
folder contains a copy of previous A
binaries and includes. Thus, executing C
(Which is only aware of B
as a dependency), will use old A
binaries/includes. A workaround I found for this problem is to manually trigger B
post-build event before executing C
. However, forgetting to trigger an intermediate project post-build can result into headaches during debugging (Not loaded symbols, wrong behaviour...).
2.6.3. Multiple times single header reference
Another limitation for this approach is "Multiple times single header reference".
This problem can be explained by considering the project dependency image at section 2.1..
Considering that Graphics
and Physics
are both including Maths
headers, and that Engine
is including Build\Graphics\include\
and Build\Physics\include\
, typing a header name will show multiple identical results:
2.6.4. De-synchronized symbol referencing
If B
is dependent of A
and any header changes in A
(for instance, we add a new function), Rescan File
/Rescan Solution
will be needed to access the new symbol from B
.
Also, navigating to files or symbol can make us move to the wrong header (Copied header instead of the original one).
3. Interrogations and learning perspectives
3.1. Project Reference
During my Visual Studio software developer journey, I came through the project Reference
concept, but I can't find how it can solve the technical limitations of my current approach, nor how it can helps me to re-think it.
3.2. Property sheets
As every project configuration of my solution is following the same principle but the content (Include dirs, library dirs...) for each one is different, I'm not sure how to make a great usage of property sheets.
3.3. Exploring GitHub
Currently I'm struggling finding some good project architecture references. I would be pleased finding some Visual Studio configured solution on GitHub or any code sharing platform. (I know that CMake
and Premake
are prefered in most case when sharing code, however, learning more about Visual Studio project configuration is my actual goal).
Thanks for reading my words, I hope that you are also interested into discussing about this subject and maybe we can share our approaches.