Importing C++20 module as external DLL in msvc 2022

Hi,
I have a testing solution done with one cmake list that simulates the creation of a module as a shared library, installing it, and then an executable that will find and consume it. Before “file sets” it worked fine. I have just listed those .ixx as interface sources for install_interface.

Now with file sets, the build and install of the module dll works fine. The .ixx files are under /include/ as before but the importing part of the cmake results in an error:
CMake Error in CMakeLists.txt: Target "ExternalDllModule__ExternalDllModule@synth_435c1b58afb7" contains C++ modules intended for BMI-only compilation. This is not yet supported by the Visual Studio generator.
The target executable gets created anyway but can’t find the symbols in the modules. When I manually add the .ixx files from /include to the project, then it works. I can see the target_sources in the generated config file that should add those but it is adding them as interface instead of public.

Also, there was an error that wanted me to add target_compile_features(ExternalDllModule PUBLIC cxx_std_20) even when there already was set_property(TARGET ExternalDllModuleApp PROPERTY CXX_STANDARD 20). But that was a minor inconvenience.

So, what am I doing wrong?

The relevant part of a larger example project:

cmake_minimum_required(VERSION 3.28)
cmake_policy(VERSION 3.28)

project(Modules
    VERSION 0.0.0
    LANGUAGES CXX)

include(GnuInstallDirs)

add_compile_options(/experimental:module) # needed for STL modules
#
# 3rd party dll module creation
#

add_library(ExternalDllModule SHARED)
target_sources(ExternalDllModule
    PRIVATE 
        ExternalDllModule/mod.class.cpp
        ExternalDllModule/mod.func.cpp
    PUBLIC FILE_SET CXX_MODULES BASE_DIRS ExternalDllModule FILES
        ExternalDllModule/mod.ixx
        ExternalDllModule/mod.class.ixx
        ExternalDllModule/mod.func.ixx  
)
set_property(TARGET ExternalDllModule PROPERTY CXX_STANDARD 20)
target_compile_features(ExternalDllModule PUBLIC cxx_std_20)
target_compile_definitions(ExternalDllModule
        PRIVATE
            "DLLMODULE_EXPORT=__declspec(dllexport)"
        INTERFACE
            "DLLMODULE_EXPORT=__declspec(dllimport)"
    )
install(TARGETS ExternalDllModule EXPORT ExternalDllModule_export FILE_SET CXX_MODULES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(EXPORT ExternalDllModule_export NAMESPACE ExternalDllModule::  DESTINATION lib/cmake/ExternalDllModule FILE ExternalDllModuleConfig.cmake)

#
# 3rd party dll module consumption
# You need to install the ExternalDllModule first
#

find_package(ExternalDllModule QUIET)
if(ExternalDllModule_FOUND)
    add_executable(ExternalDllModuleApp)
    target_sources(ExternalDllModuleApp
        PRIVATE 
            DllModule/main.cpp
    )

    target_link_libraries(ExternalDllModuleApp PRIVATE ExternalDllModule::ExternalDllModule)
    set_property(TARGET ExternalDllModuleApp PROPERTY CXX_STANDARD 20)
endif()

The old version with the source files but without the “file sets” changes is here GitHub - forry/examples-cppmodules: C++20 modules examples.

I have prepared a MR that should work:

I see a different problem on my CI:

[1/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\SingleFileModule\main.cpp for CXX dependencies
[2/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\TwoFileModule\main.cpp for CXX dependencies
[3/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\TwoFileModule\mod.cpp for CXX dependencies
[4/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\SingleFileModule\mod.ixx for CXX dependencies
mod.ixx
[5/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\TwoFileModule\mod.ixx for CXX dependencies
mod.ixx
[6/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\PartitionModule\mod.func.cpp for CXX dependencies
[7/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\PartitionModule\main.cpp for CXX dependencies
[8/82] Generating CXX dyndep file CMakeFiles\SingleFileModule.dir\CXX.dd
[9/82] Generating CXX dyndep file CMakeFiles\TwoFileModule.dir\CXX.dd
[10/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\PartitionModule\mod.ixx for CXX dependencies
mod.ixx
[11/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\PartitionModule\mod.class.ixx for CXX dependencies
mod.class.ixx
[12/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\PartitionModule\mod.func.ixx for CXX dependencies
mod.func.ixx
[13/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\Conflicts\main.cpp for CXX dependencies
[14/82] Building CXX object CMakeFiles\TwoFileModule.dir\TwoFileModule\mod.ixx.obj
mod.ixx
[15/82] Building CXX object CMakeFiles\SingleFileModule.dir\SingleFileModule\mod.ixx.obj
mod.ixx
[16/82] Building CXX object CMakeFiles\TwoFileModule.dir\TwoFileModule\main.cpp.obj
FAILED: CMakeFiles/TwoFileModule.dir/TwoFileModule/main.cpp.obj 
C:\PROGRA~1\MICROS~2\2022\ENTERP~1\VC\Tools\MSVC\1438~1.331\bin\Hostx64\x64\cl.exe  /nologo /TP  -external:I"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.38.33130\ATLMFC\include" -external:I"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.38.33130\include" -external:I"C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\ucrt" -external:I"C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\shared" -external:I"C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\um" -external:I"C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\winrt" -external:I"C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\cppwinrt" -external:W0 /DWIN32 /D_WINDOWS /EHsc /O2 /Ob2 /DNDEBUG -std:c++20 -MD /experimental:module /showIncludes @CMakeFiles\TwoFileModule.dir\TwoFileModule\main.cpp.obj.modmap /FoCMakeFiles\TwoFileModule.dir\TwoFileModule\main.cpp.obj /FdCMakeFiles\TwoFileModule.dir\ /FS -c D:\a\examples-cppmodules\examples-cppmodules\TwoFileModule\main.cpp
D:\a\examples-cppmodules\examples-cppmodules\TwoFileModule\main.cpp(2): fatal error C1011: cannot locate standard module interface. Did you install the library part of the C++ modules feature in VS setup?
[17/82] Building CXX object CMakeFiles\SingleFileModule.dir\SingleFileModule\main.cpp.obj
FAILED: CMakeFiles/SingleFileModule.dir/SingleFileModule/main.cpp.obj 
C:\PROGRA~1\MICROS~2\2022\ENTERP~1\VC\Tools\MSVC\1438~1.331\bin\Hostx64\x64\cl.exe  /nologo /TP  -external:I"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.38.33130\ATLMFC\include" -external:I"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.38.33130\include" -external:I"C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\ucrt" -external:I"C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\shared" -external:I"C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\um" -external:I"C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\winrt" -external:I"C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\cppwinrt" -external:W0 /DWIN32 /D_WINDOWS /EHsc /O2 /Ob2 /DNDEBUG -std:c++20 -MD /experimental:module /showIncludes @CMakeFiles\SingleFileModule.dir\SingleFileModule\main.cpp.obj.modmap /FoCMakeFiles\SingleFileModule.dir\SingleFileModule\main.cpp.obj /FdCMakeFiles\SingleFileModule.dir\ /FS -c D:\a\examples-cppmodules\examples-cppmodules\SingleFileModule\main.cpp
D:\a\examples-cppmodules\examples-cppmodules\SingleFileModule\main.cpp(2): fatal error C1011: cannot locate standard module interface. Did you install the library part of the C++ modules feature in VS setup?
[18/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\Conflicts\mod.ixx for CXX dependencies
mod.ixx
[19/82] Scanning D:\a\examples-cppmodules\examples-cppmodules\PartitionModule\mod.class.cpp for CXX dependencies
ninja: build stopped: subcommand failed.

Hi,
thank you. It is very educative, I’ve picked up a few tricks. But I couldn’t understand what would make the difference. There are just so many changes :slight_smile: . So I have cloned it, and added the target_compile_features(ExternalDllModule PUBLIC cxx_std_20) and it is still behaving the same in the end.

To be clear. I’m configuring it without the “test”, then installing it, and then configuring it with the “test” checked, but the .ixx files don’t get added to the sources of the test app resulting in compile time errors. While the cmake reports the same error in the generate phase as before.

I would say this is a bug since now, I wouldn’t use the FILE_SET CXX_MODULES since it doesn’t help in this case. And this case is when it is only ever helpful.

The WindowsToolchain is just an smart way used on the CI action, I like to build fast with ninja. :wink:

Here, you may found working examples, not on CI, but on OSX with clang++ v17.0.7:

Thanks,
but as I’ve said in the OP I’m interested in the MSVC. And I can’t even compile your huge repo there – 1>D:\projects\claus\cppstd20-code\modules\mod3\mod3order.cppm(17,18): error C7621: module partition 'Order' for module unit 'Mod3' was not found
But anyway, I’m not seeing the find_package directive for modules anywhere, so this is not what I want. I guess that feature is not yet supported with CXX_MODULES as the above cmake error states and you need to just do it by hand.

Sorry for your problems, but it builds with the Windows toolchain file on my CI:

Using external modules from Visual Studio is not yet supported. I hope to get back to this MR but I don’t have a timeline right now.

I don’t know if that MR is about what I need. I don’t want to import .ifc. I install/export a shared library (dll) but instead of public header files, I have .ixx module interface files. I then want the consuming cmake, after calling the find_package and target_link_library, to have them as files to compile. I guess that it is not as easy when using other filename extensions or generators.

But other than that I don’t know why I would use the file set for modules instead of just:

target_sources(tgt PUBLIC some.ixx)

Which have worked fine in that scenario. So if that is not supported and without a clear timeline it’s fine, I just want to be sure.

Modules must belong to a single target and making them “compiled” by putting them into multiple targets as plain SOURCES is a recipe for violating the ODR (one definition rule). CMake needs to know which files provide modules in order to properly account for usage by other targets (the visibility) as well as the “home” target (for anchoring into a single place for its .obj (compiled) part while also having usage semantics for other targets for its .ifc (BMI) part). Only Visual Studio has the magic that would allow for target_sources(PUBLIC) to work because MSBuild itself understands C++ modules; other generators do not. CMake tends to only provides generator-specific behaviors with generator-specific specification (VS_USER_PROPS, XCODE_ATTRIBUTE_) or with extra information that is conditionally used (e.g., BYPRODUCTS, OPTIMIZE_DEPENDENCIES) but doesn’t fundamentally change the semantics CMake guarantees (i.e., other generators can implement this but the build still works without it).

I kind of understand the second part. It may be a misunderstanding on my side, I also look at c++20 module interface units as a replacement for header files thus when making a package/module/library I assumed that instead of interface headers that are included and thus compiled by the consumer I would use module interface for the same reason and the distribution of the BMI files is somewhat possible but not the main goal.
It might be I’m missing something, but I think when a source code has an import module; statement it needs to know where the BMI for that module is or perhaps know where the module interfaces are and compile them beforehand. These module interfaces (partitions and all) will just say that these symbols are dll imported and that’s it. But even if the primary module interface has an implementation in its private part I can’t imagine how you can get an ODR.
What would then be a good solution for making a shared library as a c++20 module and distributing it as pre-compiled binary using cmake as we do with dlls?

That is the file that import foo; ends up reading for MSVC. You want this to import it. The object file is compiled with the original target and ships in the associated library. The consuming project also needs to create a .ifc file for the module in order to import it. Shipping BMI files is not portable because one made with, say, -std=c++20 is not compatible with consuming code that uses -std=c++23 (maybe this particular flag is fine with MSVC, but it is not true in general).

Use FILE_SET to store your module files and let the above MR make it work for Visual Studio; it works for Ninja generators already.