简体   繁体   中英

Shared libraries and c++20 modules

There is very little documentation online on the proper use of C++20 modules in shared libraries. Many folks are clearly interested , but I haven't been able to find a clear solution.

In MSVC, you need to use dllexport when compiling the library, and dllimport when consuming the symbols. This can be done using macros in "legacy C++", but this does not work with C++20 modules, since the code is only compiled once, regardless of preprocessor directives.

This post suggests that you only need to use dllexport now, and that dllimport will be taken care of automatically by the compiler. However, this comes from a comment which has now been deleted, and I couldn't find any reliable source on the topic.

How is one expected to create a shared library using C++20 modules?

Background

A translation unit which declares a module interface or a module partition will be treated as a module unit and will, when compiled, generate both an object file and a binary module interface (BMI). The BMI is a binary representation of an abstract syntax tree, that is a data structure representing the syntax and data types of the program. We have the traditional C++ compilation pipeline:

program -> precompiler -> lexer -> parser -> assembler -> linker

With GCC, we should add the compiler flag -c which tells the compiler to compile and assemble but not link.

But shared libraries are built by the linker by reading several compiled object files together and creating a shared object. So that happens after the BMI's have been built. And the BMI's may be built without linking them together as that is two different stages.

Module Visibility

In C# when building a DLL we have visibility attributes on class level, ie. public , private , internal . In C++ we can obtain the same functionality with module partitions.

A module partition, declared with module <module>: <partition>; will be entirely visible inside the compilation unit that declares export module <module>; , but not outside that module. This reminds me of internal mode from C#. But if we however export the partition with export module <module>: <partition>; then its declarations will be publicly visible. Read more on cppreference .

Example

I have solved that problem with GCC (g++-11), see here .

In essence, you don't need DLL import/export since there are (likely) no headers involved. I have tried inserting these visibility attributes but with complaints from my compiler, so I guess we might not need them after all. Other than that, it's standard procedure. I copy/paste my example here as well:

Main

import <iostream>;
import mathlib;


int main()
{
    int a = 5;
    int b = 6;
    std::cout << "a = " << a << ", b = " << b << '\n';

    std::cout << "a+b = " << mathlib::add(a, b) << '\n';
    std::cout << "a-b = " << mathlib::sub(a, b) << '\n';
    std::cout << "a*b = " << mathlib::mul(a, b) << '\n';
    std::cout << "a/b = " << mathlib::div(a, b) << '\n';

    return 0;
}

Library

export module mathlib;

export namespace mathlib
{
    int add(int a, int b)
    {
        return a + b;
    }

    int sub(int a, int b)
    {
        return a - b;
    }

    int mul(int a, int b)
    {
        return a * b;
    }

    int div(int a, int b)
    {
        return a / b;
    }
}

Makefile

GCC=g++-11 -std=c++20 -fmodules-ts
APP=app

build: std_headers mathlib main

std_headers:
    $(GCC) -xc++-system-header iostream

mathlib: mathlib.cpp
    $(GCC) -c $< -o $@.o
    $(GCC) -shared $@.o -o libmathlib.so

main: main.cpp
    $(GCC) $< -o $(APP) -Xlinker ./libmathlib.so

clean:
    @rm -rf gcm.cache/
    @rm -f *.o
    @rm -f $(APP)
    @rm -f *.so

Running

g++-11 -std=c++20 -fmodules-ts -xc++-system-header iostream
g++-11 -std=c++20 -fmodules-ts -c mathlib.cpp -o mathlib.o
g++-11 -std=c++20 -fmodules-ts -shared mathlib.o -o libmathlib.so
g++-11 -std=c++20 -fmodules-ts main.cpp -o app -Xlinker ./libmathlib.so
./app
a = 5, b = 6
a+b = 11
a-b = -1
a*b = 30
a/b = 0

Now this is clearly platform-specific, but the approach should work on other platforms. I have tested a similar thing with Clang as well (same repo as linked).

C++20 modules have no special relationship with shared libraries. They are primarily a replacement of header files.

This means that you would develop a shared library with C++20 modules in a similar fashion as you would with header files before C++20, at least with my current understanding. You design some API that is exported (unfortunately still using vendor-specific attributes like __declspec(dllexport) or __attribute__((visibility("default"))) ) and implement it. You build your shared library file (.dll/.so) and an import library for distribution, same way as before. However instead of distributing header files, you would distribute module interface units instead. Module interface units are files containing an export module ABC; declaration at the top.

And executables consuming that shared library would then import that module using import ABC; , instead of #include -ing a header file.

Edit: As was pointed out in the comments, it is seemingly still necessary on Windows to provide a macro switch inside the module interfaces that toggles between dllexport and dllimport attributes, similar to as it is done with headers. However, I have currently not experimented with this and can only defer to what @jeremyong has experimented with in What is the expected relation of C++ modules and dynamic linkage? .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM