Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9322

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

$
0
0

はじめに

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

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

D言語からC#の関数を呼び出す際、C++C++/CLI)を経由するのが簡単だと思っています。
以前の記事でも、1つの実装例を紹介しましたが、他にも連携方式を考えてみましたので、ここにまとめます。

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

#説明
パターン1C++、C#のソースごとにDLLを作成する。DのソースでEXEファイルを作成する。
パターン2C++、C#のソースをあわせて1つのDLLを作成する。DのソースでEXEファイルを作成する。
パターン3C#のソースでDLL、C++、DのソースをあわせてEXEファイルを作成する。
パターン4D、C++、C#のソースをあわせて、1つのEXEファイルを作成する。

環境

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

パターン1

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

ソースコード

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

str.cs
usingSystem;namespaceMyLibrary{publicclassMyStringClass{unsafepublicstaticintComplexCalc(Char*c){Strings=newString(c);Console.WriteLine("{0} -> {1:d}",s,s.GetHashCode());return(s.GetHashCode());}}}

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

intermediate1.cpp
#using <str.dll>
usingnamespaceSystem;usingnamespaceMyLibrary;#define VC_DLL_EXPORTS extern "C" __declspec(dllexport)
VC_DLL_EXPORTSintcallMyStringClass(wchar_t*wc){return(MyStringClass::ComplexCalc(wc));}

C#Char型が、Dwchar型に対応してします。
Dwstringを変換して、C#に渡すように実装しました。

case1.d
importstd.conv;importstd.stdio;pragma(lib,"intermediate1.lib");extern(Windows)nothrow@nogc{intcallMyStringClass(wchar*wc);}voidmain(){wstringws="Hello C#";writefln("input  : %s",ws);intres=callMyStringClass(ws.to!(wchar[]).ptr);writefln("output : %d",res);}

コンパイル

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

#説明コマンド
1C#からDLLを作成csc -unsafe -target:library str.cs
2C++からDLLを作成cl /clr /LD intermediate1.cpp
3Dから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>
usingnamespaceSystem;usingnamespaceMyLibrary;#define VC_DLL_EXPORTS extern "C" __declspec(dllexport)
VC_DLL_EXPORTSintcallMyStringClass(wchar_t*wc){return(MyStringClass::ComplexCalc(wc));}

D言語のソースコードは、パターン1とpragma行以外は同じです。

case2.d
importstd.conv;importstd.stdio;pragma(lib,"intermediate2.lib");extern(Windows)nothrow@nogc{intcallMyStringClass(wchar*wc);}voidmain(){wstringws="Hello C#";writefln("input  : %s",ws);intres=callMyStringClass(ws.to!(wchar[]).ptr);writefln("output : %d",res);}

コンパイル

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

#説明コマンド
1C#からstr.netmoduleを作成csc -unsafe -target:module str.cs
2C++とstr.netmoduleからDLLを作成cl /clr /LD intermediate2.cpp str.netmodule /link /LTCG
3Dから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>
usingnamespaceSystem;usingnamespaceMyLibrary;extern"C"intcallMyStringClass(wchar_t*wc){return(MyStringClass::ComplexCalc(wc));}

Dのソースコードはpragma行から、extern (C)ブロックで関数宣言しています。

case3.d
importstd.conv;importstd.stdio;extern(C){intcallMyStringClass(wchar*wc);}voidmain(){wstringws="Hello C#";writefln("input  : %s",ws);intres=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をそのまま使用できました。

#説明コマンド
1C#からDLLを作成csc -unsafe -target:library str.cs
2C++からobjファイルを作成cl /clr /c intermediate3.cpp
3Dと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>
usingnamespaceSystem;usingnamespaceMyLibrary;extern"C"intcallMyStringClass(wchar_t*wc){return(MyStringClass::ComplexCalc(wc));}

パターン4では、C++コンパイラでEXEファイルを作成するため、PhobosライブラリではなくC標準ライブラリを使用するようコーディングしています。

case4.d
extern(C):intcallMyStringClass(wchar*wc);intprintf(immutable(char)*format,...);voidmain(){wchar[]wc=cast(wchar[])"Hello C#"w;printf("input  : %ls\n".ptr,wc.ptr);intres=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が提供されています。参照情報(英語)

#説明コマンド
1C#からstr.netmoduleを作成csc -unsafe -target:module str.cs
2Dからobjファイルを作成dmd -m64 -c case4.d
3C++と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ライブラリが使えない等の制約があるため、私にとっては実用的ではないと思いました。


Viewing all articles
Browse latest Browse all 9322

Latest Images

Trending Articles