はじめに
D言語
から、C#
のソースコードやライブラリを利用したい場合について、その利用方法を考えてみました。
D言語とC#との連携方式
D言語
からC#
の関数を呼び出す際、C++
(C++/CLI
)を経由するのが簡単だと思っています。
以前の記事でも、1つの実装例を紹介しましたが、他にも連携方式を考えてみましたので、ここにまとめます。
EXE
ファイルやDLL
ファイルの作成方法で、4つのパターンに分類しました。
# | 説明 |
---|---|
パターン1 | C++、C#のソースごとにDLLを作成する。DのソースでEXEファイルを作成する。 |
パターン2 | C++、C#のソースをあわせて1つのDLLを作成する。DのソースでEXEファイルを作成する。 |
パターン3 | C#のソースでDLL、C++、DのソースをあわせてEXEファイルを作成する。 |
パターン4 | D、C++、C#のソースをあわせて、1つのEXEファイルを作成する。 |
環境
以下のソースコードは、以下の環境でコンパイルしています。
- Windows 10
- Visual C++ ビルドツール 2019 → インストール手順
- DMD v2.094.2
パターン1
C++
、C#
のソースコードごとにDLL
を作成、D言語
のソースコードでEXE
ファイルを作成するパターンです。
ソースコード
C#
のソースコードstr.cs
をD言語
から呼び出したいと仮定します。
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言語
からの呼び出しを中継する役割です。
#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#
に渡すように実装しました。
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 |
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
実行結果
D:\Dev> case1
input : Hello C#
Hello C# -> 589585528
output : 589585528
パターン2
C++
、C#
のソースコードをあわせて1つのDLL
、D言語
のソースコードでEXE
ファイルを作成するパターンです。
ソースコード
C#
のソースコードstr.cs
は、パターン1と同じものを使用します。
パターン1C++
のソースコード1行目は#using <str.dll>
でしたが、パターン2では#using <str.netmodule>
とします。
#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言語
のソースコードは、パターン1とpragma
行以外は同じです。
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 |
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
実行結果
パターン1と同じ実行結果となりました。
D:\Dev> case2
input : Hello C#
Hello C# -> 589585528
output : 589585528
パターン3
C#
のソースコードでDLL
、C++
、D言語
のソースコードをあわせてEXE
ファイルを作成するパターンです。
ソースコード
C#
のソースコードstr.cs
は、パターン1と同じものを使用します。
C++
のcallMyStringClass
関数は、VC_DLL_EXPORTS
からextern "C"
になっています。
#using <str.dll>
using namespace System;
using namespace MyLibrary;
extern "C" int callMyStringClass(wchar_t *wc)
{
return ( MyStringClass::ComplexCalc(wc) );
}
D
のソースコードはpragma
行から、extern (C)
ブロックで関数宣言しています。
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" |
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
ファイルは生成されますが、実行時エラーが発生しました。
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
を指定しない場合、リンク時エラーとなります。
D:\Dev> dmd -m64 -L=/NODEFAULTLIB:libcmt.lib case3.d intermediate3.obj
LINK : fatal error LNK1104: ファイル 'MSCOREE.lib' を開くことができません。
Error: linker exited with status 1104
実行結果
パターン1と同じ実行結果となりました。
D:\Dev> case3
input : Hello C#
Hello C# -> 589585528
output : 589585528
パターン4
D
、C++
、C#
のソースをあわせて、1つのEXE
ファイルを作成するパターンです。
ソースコード
C#
のソースコードstr.cs
は、パターン1と同じものを使用します。
C++
のソースコードは、パターン2と同じく#using <str.netmodule>
とします。
#using <str.netmodule>
using namespace System;
using namespace MyLibrary;
extern "C" int callMyStringClass(wchar_t *wc)
{
return ( MyStringClass::ComplexCalc(wc) );
}
パターン4では、C++
コンパイラでEXE
ファイルを作成するため、Phobos
ライブラリではなくC
標準ライブラリを使用するようコーディングしています。
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
が提供されています。参照情報(英語)
# | 説明 | コマンド |
---|---|---|
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 |
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
コード生成しています。
コード生成が終了しました。
実行結果
パターン1と同じ実行結果となりました。
D:\Dev> case4
input : Hello C#
Hello C# -> 589585528
output : 589585528
私見、感想
4つのパターンを実装してみた結果、パターン3が私のお気に入りです。
パターン3であれば、C#
のソースコートが手元になく、DLL
のみの提供であっても対応できます。
パターン4は、1つのEXEファイルにまとめられるというアイデアは面白いと思いました。
ただ、D言語
を使う上でPhobos
ライブラリが使えない等の制約があるため、私にとっては実用的ではないと思いました。