In this three-part series we will explore how to build modules and header units from the command line as well as how to use/reference them.
The goal of this post is to serve as a brief tour of compiling and using primary module interfaces from the command line and the options we use.
Note: This tutorial will focus primarily on dealing with IFC and object file output. Other types of compiler output such as PDB info are not mentioned.
Overview
- Summary of C++ modules options.
- Basics of building a module interface.
- Modules with interface dependencies.
Summary of C++ modules options
Option | Brief Description |
---|---|
/interface |
Tells the compiler that the input file is a module interface unit. |
/internalPartition |
Tells the compiler that the input file is an internal partition unit. |
/reference |
Provides the compiler with an IFC file to reference for the nominated module interface name. |
/ifcSearchDir |
When the compiler falls back to implicit module interface search, directories specified by this option will be used. |
/ifcOutput |
Tells the compiler where the IFC resulting from compilation should go. If that destination is a directory the compiler will generate a name based on the interface name or the header unit name. |
/ifcOnly |
Instructs the compiler to only produce an IFC as the result of compilation. No other outputs will be produced as the result of compilation even if other options are specified. |
/exportHeader |
Instructs the compiler to create a header unit from the input. |
/headerName |
Tells the compiler that the input designates the name of a header. |
/translateInclude |
Instructs the compiler to perform #include -> import translation if the header-name nominates an importable header. |
/showResolvedHeader |
When building a header unit, show the fully resolved path to that header unit after compilation. |
/validateIfcChecksum[-] |
Off by default. Specifying this switch will enforce an extra security check using the stored content hash in the IFC. |
Basics of building a module interface
For the content in this section, we will assume that you have an appropriate compiler environment command prompt set up and that you have navigated to the directory with your test files.
Let’s look at the most basic scenario we can for starters:
m.ixx
:
export module MyModule; export void f() { }
main.cpp
:
import MyModule; int main() { f(); }
The simplest way to build this sample is the following:
$ cl /c /std:c++latest m.ixx $ cl /std:c++latest /reference MyModule=MyModule.ifc main.cpp m.obj $ .\main.exe
One quick note about the name of file m.ixx
above, the .ixx
extension is the default module interface extension for MSVC. If you wish to use a different extension then you must use /interface
along with /TP
in order to compile the input as both C++ and as a module interface. Here’s a quick example of compiling the module interface if the name were my-module.cppm
:
$ cl /c /std:c++latest /interface /TP my-module.cppm
In the first line we compile the module interface, and two things happen implicitly:
- The compiler will derive a name for the resulting object file based on the base name of the input file. The resulting object file in this case is derived from
m.ixx
transformed intom.obj
. - The compiler will derive a name for the resulting IFC file based on the module interface name. The resulting IFC in this case is derived from the module name
MyModule
transformed intoMyModule.ifc
. Note that the name of the input file has no bearing on the exported module interface name, they are completely orthogonal to each other so if this file were namedfoobar.ixx
the generated IFC name would still beMyModule.ifc
.
If we take away the two implicit points above, we will end up with a command line which looks like this:
$ cl /c /std:c++latest m.ixx /ifcOutput MyModule.ifc /Fom.obj
On the import side we could take advantage of the compiler’s implicit lookup behavior to find the module interface:
$ cl /std:c++latest main.cpp m.obj $ .\main.exe
Whoa! Hold on there! What happened? Well, in MSVC the compiler implements a well-coordinated lookup to find the module interface implicitly. Because the compiler generates a module interface IFC based on the module name it can safely be assumed that if there is no direct /reference
option on the command line then there could be an IFC somewhere on disk which is named after the module interface name. In the scenario above we are trying to import a module interface named MyModule
so there might be a MyModule.ifc
on disk, and indeed there is! It is worth pointing out that this implicit lookup behavior will search the current directory along with any directory added using /ifcSearchDir
.
Let’s consider a scenario where the destination for the resulting IFC is not in the immediate directory. Consider the following directory structure:
./ ├─ src/ │ ├─ m.ixx │ ├─ main.cpp ├─ bin/
And let’s assume that our compiler command prompt is rooted at ./
and that we want all output to go into the bin\
folder. Here’s what the fully explicit command lines look like:
$ cl /c /std:c++latest src\m.ixx /Fobin\m.obj /ifcOutput bin\MyModule.ifc $ cl /std:c++latest /reference MyModule=bin\MyModule.ifc src\main.cpp /Fobin\m.obj /Febin\main.exe bin\m.obj
There are a lot of things going on so let’s narrow the scope of noise to just the command line options required to compile main.cpp
and not link it.
$ cl /c /std:c++latest /reference MyModule=bin\MyModule.ifc src\main.cpp /Fobin\m.obj
Note: The /Fo
tells the compiler where to put the resulting object file. Further, in order to ensure that the compiler can properly detect that the destination is a directory, please append the trailing ‘\
‘ at the end of the argument.
If we wanted to take advantage of the compiler’s implicit naming mechanisms the command lines would be the following:
$ cl /c /std:c++latest src\m.ixx /Fobin\ /ifcOutput bin\ $ cl /std:c++latest /ifcSearchDir bin\ src\main.cpp /Fobin\ /Febin\ bin\m.obj
Notice that the difference here is we simply provide a directory as the argument to each of our command line options.
Modules with interface dependencies
Often, we don’t want to build a single module interface and call it a day, it is frequently the case that sufficiently large projects will be composed of many module interfaces which describe various parts of the system. In this section we’ll explore how to build translation units which depend on one or more interfaces.
Let’s consider a slightly more sophisticated directory layout:
./ ├─ src/ │ ├─ types/ │ │ ├─ types.ixx │ ├─ util/ │ │ ├─ util.ixx │ ├─ shop/ │ │ ├─ shop.ixx │ │ ├─ shop-unit.cpp │ ├─ main.cpp ├─ bin/
The code for these files can be found here.
As you explore the code you will find that many of these modules/source files contain references to module interfaces and those interfaces may reference yet another interface. At its core, the most basic dependency graph looks like the following:
types.ixx / \ util.ixx shop.ixx \ / shop-unit.cpp | main.cpp
Without further ado, here are the explicit command lines in all their glory:
$ cl /c /EHsc /std:c++latest src\types\types.ixx /Fobin\types.obj /ifcOutput bin\types.ifc $ cl /c /EHsc /std:c++latest /reference types=bin\types.ifc src\util\util.ixx /Fobin\util.obj /ifcOutput bin\util.ifc $ cl /c /EHsc /std:c++latest /reference types=bin\types.ifc src\shop\shop.ixx /Fobin\shop.obj /ifcOutput bin\shop.ifc $ cl /c /EHsc /std:c++latest /reference types=bin\types.ifc /reference util=bin\util.ifc /reference shop=bin\shop.ifc src\shop\shop-unit.cpp /Fobin\shop-unit.obj $ cl /EHsc /std:c++latest /reference shop=bin\shop.ifc /reference types=bin\types.ifc src\main.cpp /Fobin\main.obj /Febin\main.exe bin\types.obj bin\util.obj bin\shop.obj bin\shop-unit.obj
That is quite a mouthful. One thing you might notice is that when we built src\shop\shop-unit.cpp
we needed a reference to both types
and shop
even though there’s no explicit import of either interface. The reason for this is because util
has an implicit dependency on types
to resolve Product
properly and because it is a module unit the line module shop;
implicitly imports the module interface shop
, this behavior is defined by the C++ standard.
Applying some techniques learned above we can drastically reduce the noise by using implicit naming/lookup:
$ cl /c /EHsc /std:c++latest src\types\types.ixx /Fobin\ /ifcOutput bin\ $ cl /c /EHsc /std:c++latest /ifcSearchDir bin\ src\util\util.ixx /Fobin\ /ifcOutput bin\ $ cl /c /EHsc /std:c++latest /ifcSearchDir bin\ src\shop\shop.ixx /Fobin\ /ifcOutput bin\ $ cl /c /EHsc /std:c++latest /ifcSearchDir bin\ src\shop\shop-unit.cpp /Fobin\ $ cl /EHsc /std:c++latest /ifcSearchDir bin\ src\main.cpp /Fobin\ /Febin\ bin\types.obj bin\util.obj bin\shop.obj bin\shop-unit.obj
This is looking much better. We can take it a step further by taking advantage of the fact that cl.exe
will process each source file in a linear sequence:
$ cl /EHsc /std:c++latest /ifcSearchDir bin\ src\types\types.ixx src\util\util.ixx src\shop\shop.ixx src\shop\shop-unit.cpp src\main.cpp /Fobin\ /Febin\main.exe /ifcOutput bin\
The command above uses implicit naming/lookup along with cl.exe
‘s linear source processing behavior.
Note: the above command line will not work if the option /MP
is used (compiling multiple inputs in parallel).
Just to be complete, we could also use explicit naming for our module interfaces in the single command line above:
$ cl /EHsc /std:c++latest /reference shop=bin\shop.ifc /reference types=bin\types.ifc /reference util=bin\util.ifc src\types\types.ixx src\util\util.ixx src\shop\shop.ixx src\shop\shop-unit.cpp src\main.cpp /Fobin\ /Febin\main.exe /ifcOutput bin\
The reason either of these command lines work is that the compiler will not try to do anything special with a /reference
option unless the name designating the IFC is used and there is no extra cost to add /reference
options for a command line if you know the module will be generated at some point in the input sequence.
Closing
In part 2 we will cover how to handle module interface partitions. Finally, in part 3 we will cover how to handle header units.
We urge you to go out and try using Visual Studio 2019/2022 with Modules. Both Visual Studio 2019 and Visual Studio 2022 Preview are available through the Visual Studio downloads page!
As always, we welcome your feedback. Feel free to send any comments through e-mail at visualcpp@microsoft.com or through Twitter @visualc. Also, feel free to follow me on Twitter @starfreakclone.
If you encounter other problems with MSVC in VS 2019/2022 please let us know via the Report a Problem option, either from the installer or the Visual Studio IDE itself. For suggestions or bug reports, let us know through DevComm.
Can we get rid of the microsoft specific .ixx naming scheme? This really detered me from using modules. thank you.
Regarding the /interface switch, some places around Microsoft sites still refer to /module:interface, as answered by you.
https://developercommunity.visualstudio.com/t/c-modules-consider-supporting-custom-extensions-ot/978893
It would be nice if the 2019 written documentation also got a revamp for the current state of the compiler, and we get documentation about using modules in modern Windows development, like XAML C++/WinRT applications and UWP runtime components, or DirectX/GDK.
Looking forward to the series.
Thanks for the blog entry. Are C++ Modules supported in Visual Studio CMake yet? Or just with MSBuild? Thank you 🙂