D言語 → C++ → C#の連携方式

# はじめに

`D言語`から、`C#`のソースコードやライブラリを利用したい場合について、その利用方法を考えてみました。

# D言語とC#との連携方式

`D言語`から`C#`の関数を呼び出す際、`C++`（`C++/CLI`）を経由するのが簡単だと思っています。
[以前の記事](https://qiita.com/devmynote/items/015483085e1d6e1a209b)でも、１つの実装例を紹介しましたが、他にも連携方式を考えてみましたので、ここにまとめます。

`EXE`ファイルや`DLL`ファイルの作成方法で、４つのパターンに分類しました。

|#|説明|
|:--|:--|
|パターン１|C++、C#のソースごとにDLLを作成する。DのソースでEXEファイルを作成する。|
|パターン２|C++、C#のソースをあわせて１つのDLLを作成する。DのソースでEXEファイルを作成する。|
|パターン３|C#のソースでDLL、C++、DのソースをあわせてEXEファイルを作成する。|
|パターン４|D、C++、C#のソースをあわせて、１つのEXEファイルを作成する。|

# 環境
以下のソースコードは、以下の環境でコンパイルしています。

* Windows 10
* [Visual C++ ビルドツール 2019](https://visualstudio.microsoft.com/ja/downloads/)　→　[インストール手順](https://www.kkaneko.jp/tools/win/buildtool.html)
* [DMD](https://dlang.org/download.html) v2.094.2

# パターン１
`C++`、`C#`のソースコードごとに`DLL`を作成、`D言語`のソースコードで`EXE`ファイルを作成するパターンです。

## ソースコード
`C#`のソースコード`str.cs`を`D言語`から呼び出したいと仮定します。

```C#:str.cs
using System;

namespace MyLibrary
{
	public class MyStringClass
	{
		unsafe public static int ComplexCalc(Char *c)
		{
			String s = new String(c);
			Console.WriteLine("{0} -> {1:d}", s, s.GetHashCode());
			return ( s.GetHashCode() );
		}
	}
}
```

`C++`（`C++/CLI`）の`intermediate1.cpp`は、`D言語`からの呼び出しを中継する役割です。

```C++:intermediate1.cpp
#using <str.dll>

using namespace System;
using namespace MyLibrary;

#define VC_DLL_EXPORTS extern "C" __declspec(dllexport)

VC_DLL_EXPORTS int callMyStringClass(wchar_t *wc)
{
	return ( MyStringClass::ComplexCalc(wc) );
}
```

`C#`の`Char`型が、`D`の`wchar`型に対応してします。
`D`の`wstring`を変換して、`C#`に渡すように実装しました。

```D:case1.d
import std.conv;
import std.stdio;

pragma(lib, "intermediate1.lib");

extern (Windows) nothrow @nogc {
	int callMyStringClass(wchar* wc);
}

void main()
{
	wstring ws = "Hello C#";
	writefln("input  : %s", ws);
	int res = callMyStringClass(ws.to!(wchar[]).ptr);
	writefln("output : %d", res);
}
```

## コンパイル
コンパイルは「VS2019用 x64 Native Tools コマンドプロンプト」から実行します。
コンパイルは、以下の順序で進めます。
コンパイルの結果、`str.dll`、`intermediate1.dll`、`case1.exe`が作成されます。

|#|説明|コマンド|
|:--|:--|:--|
|1|C#からDLLを作成|csc -unsafe -target:library str.cs|
|2|C++からDLLを作成|cl /clr /LD intermediate1.cpp|
|3|DからEXEを作成|dmd -m64 case1.d|

```text:VS2019用_x64_Native_Tools_コマンドプロンプト
D:\Dev> where csc
C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\Roslyn\csc.exe
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe

D:\Dev> csc -unsafe -target:library str.cs
Microsoft (R) Visual C# Compiler バージョン 3.4.1-beta4-19610-02 (c4e5d138)
Copyright (C) Microsoft Corporation. All rights reserved.

D:\Dev> where cl
C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.24.28314\bin\Hostx64\x64\cl.exe

D:\Dev> cl /clr /LD intermediate1.cpp
Microsoft(R) C/C++ Optimizing Compiler Version 19.24.28314
Microsoft (R) .NET Framework の場合 バージョン 4.08.4250.0
Copyright (C) Microsoft Corporation.  All rights reserved.

intermediate1.cpp
Microsoft (R) Incremental Linker Version 14.24.28314.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:intermediate1.dll
/dll
/implib:intermediate1.lib
intermediate1.obj
   ライブラリ intermediate1.lib とオブジェクト intermediate1.exp を作成中

D:\Dev> dmd -m64 case1.d
```

## 実行結果
```text:コマンドプロンプト
D:\Dev> case1
input  : Hello C#
Hello C# -> 589585528
output : 589585528
```

# パターン２
`C++`、`C#`のソースコードをあわせて１つの`DLL`、`D言語`のソースコードで`EXE`ファイルを作成するパターンです。

## ソースコード
`C#`のソースコード`str.cs`は、パターン１と同じものを使用します。
パターン１`C++`のソースコード１行目は`#using <str.dll>`でしたが、パターン２では`#using <str.netmodule>`とします。

```C++:intermediate2.cpp
#using <str.netmodule>

using namespace System;
using namespace MyLibrary;

#define VC_DLL_EXPORTS extern "C" __declspec(dllexport)

VC_DLL_EXPORTS int callMyStringClass(wchar_t *wc)
{
	return ( MyStringClass::ComplexCalc(wc) );
}
```
`D言語`のソースコードは、パターン１と`pragma`行以外は同じです。

```D:case2.d
import std.conv;
import std.stdio;

pragma(lib, "intermediate2.lib");

extern (Windows) nothrow @nogc {
	int callMyStringClass(wchar* wc);
}

void main()
{
	wstring ws = "Hello C#";
	writefln("input  : %s", ws);
	int res = callMyStringClass(ws.to!(wchar[]).ptr);
	writefln("output : %d", res);
}
```

## コンパイル
コンパイルは「VS2019用 x64 Native Tools コマンドプロンプト」から実行します。
コンパイルは、以下の順序で進めます。
コンパイルの結果、`intermediate2.dll`、`case2.exe`が作成されます。

|#|説明|コマンド|
|:--|:--|:--|
|1|C#からstr.netmoduleを作成|csc -unsafe -target:module str.cs|
|2|C++とstr.netmoduleからDLLを作成|cl /clr /LD intermediate2.cpp str.netmodule /link /LTCG|
|3|DからEXEを作成|dmd -m64 case2.d|

```text:VS2019用_x64_Native_Tools_コマンドプロンプト
D:\Dev\> csc -unsafe -target:module str.cs
Microsoft (R) Visual C# Compiler バージョン 3.4.1-beta4-19610-02 (c4e5d138)
Copyright (C) Microsoft Corporation. All rights reserved.

D:\Dev> cl /clr /LD intermediate2.cpp str.netmodule /link /LTCG
Microsoft(R) C/C++ Optimizing Compiler Version 19.24.28314
Microsoft (R) .NET Framework の場合 バージョン 4.08.4250.0
Copyright (C) Microsoft Corporation.  All rights reserved.

intermediate2.cpp
Microsoft (R) Incremental Linker Version 14.24.28314.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:intermediate2.dll
/dll
/implib:intermediate2.lib
/LTCG
intermediate2.obj
str.netmodule
   ライブラリ intermediate2.lib とオブジェクト intermediate2.exp を作成中
コード生成しています。
コード生成が終了しました。

D:\Dev> dmd -m64 case2.d
```

## 実行結果
パターン１と同じ実行結果となりました。

```text:コマンドプロンプト
D:\Dev> case2
input  : Hello C#
Hello C# -> 589585528
output : 589585528
```

# パターン３
`C#`のソースコードで`DLL`、`C++`、`D言語`のソースコードをあわせて`EXE`ファイルを作成するパターンです。

## ソースコード

`C#`のソースコード`str.cs`は、パターン１と同じものを使用します。
`C++`の`callMyStringClass`関数は、`VC_DLL_EXPORTS`から`extern "C"`になっています。

```C++:intermediate3.cpp
#using <str.dll>

using namespace System;
using namespace MyLibrary;

extern "C" int callMyStringClass(wchar_t *wc)
{
	return ( MyStringClass::ComplexCalc(wc) );
}
```
`D`のソースコードは`pragma`行から、`extern (C)`ブロックで関数宣言しています。

```D:case3.d
import std.conv;
import std.stdio;

extern (C) {
	int callMyStringClass(wchar* wc);
}

void main()
{
	wstring ws = "Hello C#";
	writefln("input  : %s", ws);
	int res = callMyStringClass(ws.to!(wchar[]).ptr);
	writefln("output : %d", res);
}
```

## コンパイル
コンパイルは「VS2019用 x64 Native Tools コマンドプロンプト」から実行します。
コンパイルは、以下の順序で進めます。
コンパイルの結果、`str.dll`、`case3.exe`が作成されます。

`mscoree.dll`は、`C++/CLI`から`.NET`ランタイムを呼び出すための`DLL`で、`mscoree.lib`はそのライブラリです。
`D言語`と`C++`との`ABI互換`により、`mscoree.lib`をそのまま使用できました。

|#|説明|コマンド|
|:--|:--|:--|
|1|C#からDLLを作成|csc -unsafe -target:library str.cs|
|2|C++からobjファイルを作成|cl /clr /c intermediate3.cpp|
|3|DとobjファイルからEXEを作成|dmd -m64 -L=/NODEFAULTLIB:libcmt.lib case3.d intermediate3.obj "C:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\Lib\um\x64\mscoree.lib"|

```text:VS2019用_x64_Native_Tools_コマンドプロンプト
D:\Dev> csc -unsafe -target:library str.cs
Microsoft (R) Visual C# Compiler バージョン 3.4.1-beta4-19610-02 (c4e5d138)
Copyright (C) Microsoft Corporation. All rights reserved.

D:\Dev> cl /clr /c intermediate3.cpp
Microsoft(R) C/C++ Optimizing Compiler Version 19.24.28314
Microsoft (R) .NET Framework の場合 バージョン 4.08.4250.0
Copyright (C) Microsoft Corporation.  All rights reserved.

intermediate3.cpp

D:\Dev> dmd -m64 -L=/NODEFAULTLIB:libcmt.lib case3.d intermediate3.obj "C:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\Lib\um\x64\mscoree.lib"
```

### `DMD`でのコンパイル時の注意事項
`DMD`でのコンパイル時の注意事項として、`-L=/NODEFAULTLIB:libcmt.lib`オプションの指定と`mscoree.lib`の指定が必要です。

`-L=/NODEFAULTLIB:libcmt.lib`オプションを指定しない場合、`warning`メッセージと共に`EXE`ファイルは生成されますが、実行時エラーが発生しました。

```text:失敗例１
D:\Dev> dmd -m64 case3.d intermediate3.obj "C:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\Lib\um\x64\mscoree.lib"
LINK : warning LNK4098: defaultlib 'MSVCRT' は他のライブラリの使用と競合しています。/NODEFAULTLIB:library を使用してください。

D:\Dev> case3
ハンドルされていない例外: System.TypeInitializationException: '<Module>' のタイプ初期化子が例外をスローしました。 ---> System.AccessViolationException: 保護されているメモリに読み取りまたは書き込み操作を行おうとしました。他のメモリが壊れていることが考えられます。
   場所 _initterm_e((fnptr)* pfbegin, (fnptr)* pfend)
   場所 <CrtImplementationDetails>.LanguageSupport.InitializeNative(LanguageSupport* )
   場所 <CrtImplementationDetails>.LanguageSupport._Initialize(LanguageSupport* )
   場所 <CrtImplementationDetails>.LanguageSupport.Initialize(LanguageSupport* )
   場所 .cctor()
   --- 内部例外スタック トレースの終わり ---
```

また、`mscoree.lib`を指定しない場合、リンク時エラーとなります。

```text:失敗例２
D:\Dev> dmd -m64 -L=/NODEFAULTLIB:libcmt.lib case3.d intermediate3.obj
LINK : fatal error LNK1104: ファイル 'MSCOREE.lib' を開くことができません。
Error: linker exited with status 1104
```

## 実行結果
パターン１と同じ実行結果となりました。

```text:コマンドプロンプト
D:\Dev> case3
input  : Hello C#
Hello C# -> 589585528
output : 589585528
```

# パターン４
`D`、`C++`、`C#`のソースをあわせて、１つの`EXE`ファイルを作成するパターンです。

## ソースコード

`C#`のソースコード`str.cs`は、パターン１と同じものを使用します。
`C++`のソースコードは、パターン２と同じく`#using <str.netmodule>`とします。

```C++:intermediate4.cpp
#using <str.netmodule>

using namespace System;
using namespace MyLibrary;

extern "C" int callMyStringClass(wchar_t *wc)
{
	return ( MyStringClass::ComplexCalc(wc) );
}
```
パターン４では、`C++`コンパイラで`EXE`ファイルを作成するため、`Phobos`ライブラリではなく`C`標準ライブラリを使用するようコーディングしています。

```D:case4.d
extern (C):
int callMyStringClass(wchar* wc);
int printf(immutable(char)* format, ...);

void main()
{
	wchar[] wc = cast(wchar[])"Hello C#"w;
	printf("input  : %ls\n".ptr, wc.ptr);
	int res = callMyStringClass(wc.ptr);
	printf("output : %d\n".ptr, res);
}
```

## コンパイル
コンパイルは「VS2019用 x64 Native Tools コマンドプロンプト」から実行します。
コンパイルは、以下の順序で進めます。
コンパイルの結果、`case4.exe`が作成されます。

`D`コンパイラで`str.netmodule`を処理できなかったため、`C++`コンパイラで`EXE`ファイルを作成する方法をとりました。
`D言語`から`C`の`printf`関数を使用する場合の注意事項として、`legacy_stdio_definitions.lib`を指定する必要があります。
Visual Studio 2015以降、`printf`、`scanf`系の関数が`inline`化されていて、回避手段として`legacy_stdio_definitions.lib`が提供されています。[参照情報(英語)](https://docs.microsoft.com/en-us/cpp/porting/visual-cpp-change-history-2003-2015?redirectedfrom=MSDN&view=msvc-160#stdio_and_conio)

|#|説明|コマンド|
|:--|:--|:--|
|1|C#からstr.netmoduleを作成|csc -unsafe -target:module str.cs|
|2|Dからobjファイルを作成|dmd -m64 -c case4.d|
|3|C++とobjファイルとstr.netmoduleからEXEを作成|cl /clr /Fe:case4.exe intermediate4.cpp str.netmodule case4.obj legacy_stdio_definitions.lib /link /NODEFAULTLIB:libcmt.lib /LTCG|

```text:VS2019用_x64_Native_Tools_コマンドプロンプト
D:\Dev> csc -unsafe -target:module str.cs
Microsoft (R) Visual C# Compiler バージョン 3.4.1-beta4-19610-02 (c4e5d138)
Copyright (C) Microsoft Corporation. All rights reserved.

D:\Dev> dmd -m64 -c case4.d

D:\Dev> cl /clr /Fe:case4.exe intermediate4.cpp str.netmodule case4.obj legacy_stdio_definitions.lib /link /NODEFAULTLIB:libcmt.lib /LTCG
Microsoft(R) C/C++ Optimizing Compiler Version 19.24.28314
Microsoft (R) .NET Framework の場合 バージョン 4.08.4250.0
Copyright (C) Microsoft Corporation.  All rights reserved.

intermediate4.cpp
Microsoft (R) Incremental Linker Version 14.24.28314.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:case4.exe
/NODEFAULTLIB:libcmt.lib
/LTCG
intermediate4.obj
str.netmodule
case4.obj
legacy_stdio_definitions.lib
コード生成しています。
コード生成が終了しました。
```

## 実行結果
パターン１と同じ実行結果となりました。

```text:コマンドプロンプト
D:\Dev> case4
input  : Hello C#
Hello C# -> 589585528
output : 589585528
```

# 私見、感想
４つのパターンを実装してみた結果、パターン３が私のお気に入りです。
パターン３であれば、`C#`のソースコートが手元になく、`DLL`のみの提供であっても対応できます。

パターン４は、１つのEXEファイルにまとめられるというアイデアは面白いと思いました。
ただ、`D言語`を使う上で`Phobos`ライブラリが使えない等の制約があるため、私にとっては実用的ではないと思いました。