C++ ModulesとDLLの関係 (MSVCの場合)

C++20で導入が決まったモジュールですが、「リンクのことは処理系依存」というわけで規格上DLLとは何の関係もありません。

ただし、DLLとモジュールを連動させるような実装はありえるし、実現すればうれしいところだと思います。
今回は規格の外側、DLLとモジュールの関係をコンパイラの挙動から探ってみました。

処理系は、一番早くからモジュールを実装していた（要出典）MSVCを使用しました。

* cl.exe バージョン 19.20.27508.1

# ライブラリを分けない場合

まずは普通にモジュールを使うコードを[Microsoftのブログ](https://msdn.microsoft.com/ja-jp/magazine/mt694085.aspx)を参考に書きました。

```cpp:calc.ixx
export module calc;

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

```cpp:main.cpp
import calc;
//import <iostream>; // => Header units; まだ実装していなかった
#include <iostream>

int main()
{
    int x,y;
    std::cin >> x >> y;
    std::cout << calc::add(x, y) << std::endl;
    return 0;
}
```

* モジュールを宣言しても名前空間は作られません
* [Header Units](https://qiita.com/tetsurom/items/e25b2683cb7e7c0fa91c)は未実装

ビルドはとりあえずMakefileで。

```makefile:Makefile
CXX = cl /nologo /EHsc /std:c++latest /experimental:module

main.exe: main.cpp calc.ifc calc.obj
	$(CXX) main.cpp calc.obj

calc.ifc: calc.ixx
	$(CXX) /c calc.ixx

calc.obj: calc.ifc

clean:
	del main.exe
	del main.obj
	del calc.ifc
	del calc.obj

check: main.exe
	echo 1 2 | main.exe
```

* モジュールは依存順にビルドする必要があります(ここでは、calc→mainの順)
* MSVCではビルド済みモジュールの拡張子は`.ifc`のようです

```
> nmake /nologo check
        echo 1 2 | main.exe
3
```

無事に動きました。

# ライブラリを分ける場合

問題はライブラリを分ける場合です。DLLとモジュールは実装上何らかの関係があるのでしょうか。

## レガシーライブラリ

初めにモジュール以前の方法でDLLを作ってみます。

```cpp:foo.h
#pragma once
#ifdef FOO
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __declspec(dllimport)
#endif

struct EXPORT foo
{
    static int foovar;
    static int getvar();
};
```

```cpp:foo.cpp
#define FOO
#include "foo.h"

int foo::foovar;
int foo::getvar(){ return foovar; }
```

```cpp:main.cpp
#include <iostream>
#include "foo.h"

int main()
{
    foo::foovar = 99;
    std::cout << foo::getvar() << std::endl;
    return 0;
}
```

```make:Makefile
CXX = cl /nologo /EHsc /std:c++latest

main.exe: main.cpp foo.lib
	$(CXX) main.cpp foo.lib

foo.lib: foo.cpp foo.h
	$(CXX) /DFOO /LD foo.cpp
```

## モジュールへ移行

前節のレガシーライブラリをモジュールへ移行するには、[Header Units](https://qiita.com/tetsurom/items/e25b2683cb7e7c0fa91c)で次のようにラップすれば良いです。

```cpp:ifoo.ixx
export module foo;
export import "foo.h";
```

しかし、現時点では実装されていません。代わりに次のようにします。

```cpp:ifoo.ixx
export module foo;
#include "foo.h"
export struct foo;
```

利用側も修正します。

```cpp:main.cpp
import foo;
#include <iostream>

int main()
{
    foo::foovar = 99;
    std::cout << foo::getvar() << std::endl;
    return 0;
}
```

```make:Makefile
CXX = cl /nologo /EHsc /std:c++latest /experimental:module

main.exe: main.cpp foo.ifc foo.lib
	$(CXX) main.cpp foo.lib

foo.lib: foo.cpp ifoo.ixx
	$(CXX) /LD /DFOO foo.cpp ifoo.ixx

foo.ifc: foo.lib
```

これはコンパイルでき、動作は前節と同じでした。

# モジュールと `dllexport`

しかし、以下の分岐はどうなるのでしょうか？

```cpp
#ifdef FOO
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __declspec(dllimport)
#endif
```

モジュールは利用するより前にビルドしています。プリプロセッサはモジュールのビルド時には使えますが、ヘッダーファイルとは違って利用側ではもはや実行されません。

※ 関数に対する `__declspec(dllimport)` の使用は任意なので注意しましょう。`foo` に変数があるのはそのためです。~~モジュールだと省略できるのかと思って時間を失いました。~~

一見すると利用側でも`__declspec(dllexport)` になりそうですが、コンパイルできるという事実から次の**予測**が成り立ちます。

* モジュールユニットで `__declspec(dllexport)` を記述するとインポート側では `__declspec(dllimport)` に見える

ヘッダーファイルと違って、モジュール自身をコンパイルしているのか、コンパイル済みのモジュールをインポートしているのかは、コンパイラからすれば簡単にわかるはずなので十分あり得えそうです。

このことはエラーメッセージを使って確認できます。
変数 `foo::foovar` を再定義してコンパイルしてみましょう。

```cpp:main.cpp
import foo;
#include <iostream>

int foo::foovar = 0;

int main()
{
    foo::foovar = 99;
    std::cout << foo::getvar() << std::endl;
    return 0;
}
```

```
main.cpp(5): error C2491: 'foo::foovar': dllimport スタティック データ メンバー の定義は許されません。
```

当たりのような気がします。

# まとめ

* 挙動から判断すると、モジュールユニットで `dllexport` した場合、インポート側では`dllimport`として扱われるようです。
* 従って、モジュールを使えば `dllimport` と `dllexport` の使い分けが不要になります。

より連携を強くするのであれば、`__declspec(dllexport) export module foo;` などとモジュールに対して指定できると便利かもしれません。

# 参考文献

* [P1103R3 Merging Modules](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1103r3.pdf)

# 謝辞

この記事は[C++20を相談しながら調べる会 #1](https://cpp20survey.connpass.com/event/124051/)の結果として書かれました。