简体   繁体   English

共享库和 c++20 模块

[英]Shared libraries and c++20 modules

There is very little documentation online on the proper use of C++20 modules in shared libraries.关于在共享库中正确使用 C++20 模块的在线文档很少。 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.在 MSVC 中,编译库时需要使用dllexport ,使用符号时需要使用dllimport 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.这可以使用“遗留 C++”中的宏来完成,但这不适用于 C++20 模块,因为代码只编译一次,而不管预处理器指令如何。

This post suggests that you only need to use dllexport now, and that dllimport will be taken care of automatically by the compiler. 这篇文章建议您现在只需要使用dllexportdllimport将由编译器自动处理。 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?如何使用 C++20 模块创建共享库?

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).声明模块接口或模块分区的翻译单元将被视为模块单元,并且在编译时将生成 object 文件和二进制模块接口(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. BMI是抽象语法树的二进制表示,是表示程序语法和数据类型的数据结构。 We have the traditional C++ compilation pipeline:我们有传统的 C++ 编译管道:

program -> precompiler -> lexer -> parser -> assembler -> linker程序 -> 预编译器 -> 词法分析器 -> 解析器 -> 汇编器 -> linker

With GCC, we should add the compiler flag -c which tells the compiler to compile and assemble but not link.对于 GCC,我们应该添加编译器标志-c ,告诉编译器编译和汇编但不链接。

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.但是共享库是由 linker 通过一起读取几个已编译的 object 文件并创建一个共享的 object 来构建的。所以这发生在 BMI 构建之后。 And the BMI's may be built without linking them together as that is two different stages. BMI 可以在不将它们链接在一起的情况下构建,因为这是两个不同的阶段。

Module Visibility模块可见性

In C# when building a DLL we have visibility attributes on class level, ie.在 C# 构建 DLL 时,我们在 class 级别上具有可见性属性,即。 public , private , internal . public的, private的, internal的。 In C++ we can obtain the same functionality with module partitions.在 C++ 中,我们可以获得与模块分区相同的功能。

A module partition, declared with module <module>: <partition>;模块分区,声明为module <module>: <partition>; will be entirely visible inside the compilation unit that declares export module <module>;将在声明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>;这让我想起了 C# 的internal模式。但是如果我们使用export module <module>: <partition>; then its declarations will be publicly visible.然后它的声明将公开可见。 Read more on cppreference .阅读有关cppreference的更多信息。

Example例子

I have solved that problem with GCC (g++-11), see here .我已经用 GCC (g++-11) 解决了这个问题,请看这里

In essence, you don't need DLL import/export since there are (likely) no headers involved.本质上,您不需要 DLL 导入/导出,因为(可能)不涉及标头。 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 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).我也用 Clang 测试了类似的东西(与链接的回购相同)。

C++20 modules have no special relationship with shared libraries. C++20 模块与共享库没有特殊关系。 They are primarily a replacement of header files.它们主要是 header 文件的替换。

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.这意味着您将以与 C++20 之前的 header 文件类似的方式使用 C++20 模块开发共享库,至少以我目前的理解是这样。 You design some API that is exported (unfortunately still using vendor-specific attributes like __declspec(dllexport) or __attribute__((visibility("default"))) ) and implement it.您设计了一些导出的 API(遗憾的是仍然使用特定于供应商的属性,如__declspec(dllexport)__attribute__((visibility("default"))) )并实现它。 You build your shared library file (.dll/.so) and an import library for distribution, same way as before.您构建共享库文件 (.dll/.so) 和用于分发的导入库,方法与之前相同。 However instead of distributing header files, you would distribute module interface units instead.但是,您将分发模块接口单元,而不是分发 header 个文件。 Module interface units are files containing an export module ABC;模块接口单元是包含export module ABC; declaration at the top.声明在顶部。

And executables consuming that shared library would then import that module using import ABC;然后使用该共享库的可执行文件将使用import ABC; , instead of #include -ing a header file. ,而不是#include -ing header 文件。

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.编辑:正如评论中所指出的,似乎仍然有必要在 Windows 上提供模块接口内的宏开关,以在 dllexport 和 dllimport 属性之间切换,类似于对标头所做的那样。 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?但是,我目前还没有对此进行试验,只能参考@jeremyong 在C++ 模块和动态链接的预期关系是什么? . .

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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