LoginSignup
1
0

More than 3 years have passed since last update.

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

Posted at

はじめに

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ファイルを作成する。

環境

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

パターン1

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

ソースコード

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

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言語からの呼び出しを中継する役割です。

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型が、Dwchar型に対応してします。
Dwstringを変換して、C#に渡すように実装しました。

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.dllintermediate1.dllcase1.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
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

実行結果

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

パターン2

C++C#のソースコードをあわせて1つのDLLD言語のソースコードでEXEファイルを作成するパターンです。

ソースコード

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

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言語のソースコードは、パターン1とpragma行以外は同じです。

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.dllcase2.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
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

実行結果

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

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

パターン3

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

ソースコード

C#のソースコードstr.csは、パターン1と同じものを使用します。
C++callMyStringClass関数は、VC_DLL_EXPORTSからextern "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)ブロックで関数宣言しています。

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.dllcase3.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"
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ファイルは生成されますが、実行時エラーが発生しました。

失敗例1
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を指定しない場合、リンク時エラーとなります。

失敗例2
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

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

ソースコード

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

intermediate4.cpp
#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標準ライブラリを使用するようコーディングしています。

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言語からCprintf関数を使用する場合の注意事項として、legacy_stdio_definitions.libを指定する必要があります。
Visual Studio 2015以降、printfscanf系の関数が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
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
コード生成しています。
コード生成が終了しました。

実行結果

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

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

私見、感想

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

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0