📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

文字列「__FORCETOC__」を「{{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,Podman,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This pag…
 
(同じ利用者による、間の18版が非表示)
1行目: 1行目:
== 概要 ==
== 概要 ==
C++ EXEからC# DLLの関数を呼び出す方法は、幾つか方法が存在しており、各々にメリットとデメリットがある。<br>
C++からC#ライブラリの関数を呼び出す方法は、複数の方法が存在しており、各々にメリットおよびデメリットがある。<br>
下記の表1に代表的な4種類の方法を示す。<br><br>
下表に代表的な5種類の方法を示す。<br><br>
<center>
<center>
'''表1. C++ EXEからC# DLLの関数を呼び出す方法'''
{| class="wikitable" | style="background-color:#fefefe;"
{| class="wikitable" | style="background-color:#fefefe;"
|+ C++からC#ライブラリのメソッドを呼ぶ方法
|-
|-
! style="background-color:#66CCFF;" | 方法  
! style="background-color:#66CCFF;" | 方法  
10行目: 10行目:
! style="background-color:#66CCFF;" | デメリット
! style="background-color:#66CCFF;" | デメリット
|-
|-
| C++/CLIを使う || 最も簡単<br>VisualStudioのIntelliSenseも使用可能 || プロジェクトの設定で[CLIを使う]に変更する必要がある。
| C++のプロジェクトをC++/CLIに設定する || C++のプロジェクトの設定において、[CLIを使う]に変更する。<br>VisualStudioのIntelliSenseも使用可能。 || C++/CLIに関するドキュメントが少ない。<br><br><u>Windowsのみ使用可能。</u>
|-
|-
| .NET DLLExportを使用して、<br>C# DLLのメソッドをエクスポートする || [CLIを使う]に変更しなくてよい<br><code>GetProcAddress</code>関数が使用できるため、<br>よく知られた方法で関数を呼び出す事が出来る || C# DLL側のソースコードが無い場合は利用できない。
| [https://github.com/3F/DllExport .NET DLLExport]を使用して、<br>C#ライブラリのメソッドをエクスポートする || C++/CLIを使用する必要がない。<br><br><code>GetProcAddress</code>関数が使用できるため、<br>よく知られた方法で関数を呼び出す事が出来る || C#ライブラリのソースコードが無い場合は利用できない。<br><br><u>Windowsのみ使用可能。</u>
|-
|-
| C# DLLに対するC++/CLIのラッパーDLLを作成して、<br>C++ EXEから使う || [CLIを使う]に変更しなくてよい<br>COMを使用しない場合において、<br>元のプロジェクトの設定を変更したくない場合に使用可能 || やり方がスマートではない。
| C++からC#ライブラリを呼ぶための<br>C++/CLIのラッパーライブラリを開発する || COMを使用しなくてよい。<br>C++およびC#のプロジェクトの設定を変更しなくてよい。 || C#ライブラリ、および、C++/CLIライブラリの2つのライブラリを作成する必要がある。<br>C++/CLIに関するドキュメントが少ない。<br><br><u>Windowsのみ使用可能。</u>
|-
|-
| Monoを使う || C++実行ファイルとC#ライブラリの2つを作成するだけでよい。<br><u>Linux、MacOSでも使用可能</u><br><br>C++の実行ファイルにおいて、<br>Monoに関連するヘッダファイルをインクルードして、C#のライブラリを呼び出す。<br> || Mono/Mono Develop (開発終了) を使用して、C#のライブラリを作成する必要がある。<br>したがって、.NET Core / .NET 5以降を使用することができない。<br>また、実行環境にもMonoをインストールする必要がある。
| Embedded Monoを使用する || C++およびC#ライブラリの2つのプロジェクトを作成するだけでよい。<br><u>Linux、MacOSでも使用可能である。</u><br><br><u>ただし、Linux上で使用する場合、</u><br><u><code>async, await</code>や他の様々なシンタックスが使用できない等の</u><br><u>デメリットも多い。</u><br><br>C++のプロジェクトにおいて、<br>Monoに関連するヘッダファイルをインクルードして、C#ライブラリを呼ぶ。<br> || Linuxの場合は、.NET Standard (.NET Core / .NET 5以降ではない) またはMonoを使用して、<br>C#ライブラリを作成する必要がある。<br><br>Windowsの場合は、.NET FrameworkまたはMonoを使用して、C#ライブラリを作成する必要がある。<br><br>したがって、.NET Core / .NET 5以降は使用することができない。<br><br>また、実行環境にもMonoをインストールする必要がある。
|-
|-
| C# DLLをCOM参照可能にする || [CLIを使う]に変更しなくてよい || C++ EXEのコード量が増えて面倒である。
| C#ライブラリをCOM参照可能にする || C++/CLIライブラリは不要である。 || C++のソースコード量が増えて設計が煩雑となる。<br><br>COM (Component Object Model) は、Windows独自の技術であり、<br><u>Linuxでは直接サポートされていないため、Windowsのみ使用可能である。</u><br><br>COMはWindowsのオブジェクト指向プログラミングモデルであり、<br>Microsoftの開発環境やWindows APIと密接に関連している。
|}
|}
</center>
</center>
<br>
<br>
上表1において、以下の手順を記載する。<br>
上表1において、以下の手順を記載する。<br>
* C++/CLIを使う
* C++のプロジェクトをC++/CLIに設定する
* C# DLL側で関数をエクスポートする
* [https://github.com/3F/DllExport .NET DLLExport]を使用して、C#ライブラリのメソッドをエクスポートする
* C++/CLIのラッパープロジェクトを作成する
* C++/CLIのラッパーライブラリを開発する
* C# DLLをCOM参照可能にする
* Embedded Monoを使用する
* C#ライブラリをCOM参照可能にする
<br><br>
<br><br>


318行目: 319行目:
  #include <mono/jit/jit.h>
  #include <mono/jit/jit.h>
  #include <mono/metadata/assembly.h>
  #include <mono/metadata/assembly.h>
#include <mono/metadata/object.h>
#include <mono/metadata/appdomain.h>
  #include <mono/metadata/debug-helpers.h>
  #include <mono/metadata/debug-helpers.h>
#include <mono/metadata/exception.h>
   
   
  int main()
  int main()
  {
  {
     // ドメインの初期化
     // ドメインの初期化
     MonoDomain* domain = mono_jit_init("SampleApp");
     MonoDomain *domain = mono_jit_init("SampleApp");
    if (!domain) return -1;
   
   
     // C#ライブラリのアセンブリの読み込み
     // C#ライブラリのアセンブリの読み込み (中間言語IL)
     MonoAssembly* assembly = mono_domain_assembly_open(domain, "SampleLibrary.dll");
     MonoAssembly *assembly = mono_domain_assembly_open(domain, "SampleLibrary.dll");
    if (!assembly) mono_jit_cleanup(domain); return -1;
   
   
     // C#ライブラリのクラスとメソッドの取得
     // アセンブリのイメージの読み込み (アセンブリ内のコード情報を保持しているもの)
     MonoImage  *image      = mono_assembly_get_image(assembly);
     MonoImage  *image      = mono_assembly_get_image(assembly);
     const char *className  = "SampleLibrary.SampleClass";          // "<名前空間名>.<クラス名>"を入力する
     if (!image) mono_jit_cleanup(domain); return -1;
    const char *methodName = "SampleMethod";                      // メソッド名を入力する
    MonoClass  *klass      = mono_class_from_name(image, "", className);
    MonoMethod *method    = mono_class_get_method_from_name(klass, methodName, 0);
   
   
     // インスタンスの作成
     // 名前空間およびクラス名を指定
     MonoObject *instance   = mono_object_new(domain, klass);
    const char *nameSpace  = "SampleLibrary";  // 名前空間を指定する
     mono_runtime_object_init(instance);
    const char *className  = "SampleClass";    // クラス名を指定する
    MonoClass  *klass      = mono_class_from_name(image, nameSpace, className);
    if (!klass) mono_jit_cleanup(domain); return -1;
    // クラスのインスタンスを生成
     MonoObject *instance = mono_object_new(domain, klass);
     if (!instance) mono_jit_cleanup(domain); return -1;
    // メソッド情報の取得 (以下のいずれかの形式でよい)
    //// 方法 1 -->
    ////// C#ライブラリのメソッドの取得
    MonoMethod *method    = mono_class_get_method_from_name(klass, methodName, <メソッドの引数の数  例: 引数が無い場合は0を指定する>);
    //// <-- 方法 1
 
    //// 方法 2 -->
    ////// メソッド情報の取得
    MonoMethodDesc *methodDesc = mono_method_desc_new("<名前空間名>.<クラス名>::<メソッド名>", true);
    if (!methodDesc) mono_jit_cleanup(domain); return -1;
    ////// メソッドの検索
    MonoMethod *method = mono_method_desc_search_in_class(methodDesc, klass);
    if (!method) mono_jit_cleanup(domain); return -1;
    //// <-- 方法 2
   
   
     // C#ライブラリのメソッドに引数がある場合 (以下の例では、第1引数 : int型、第2引数 : string型)
     // C#ライブラリのメソッドに引数がある場合 (以下の例では、第1引数 : int型、第2引数 : string型)
391行目: 416行目:
<br>
<br>
したがって、C#ライブラリ内でデータ型のポインタを受け取り、その変数の値を変更したとしても、それはC#ランタイムの管理するメモリ内で行われることになる。<br>
したがって、C#ライブラリ内でデータ型のポインタを受け取り、その変数の値を変更したとしても、それはC#ランタイムの管理するメモリ内で行われることになる。<br>
C++実行ファイル側では、C#ランタイムのメモリに直接アクセスできないため、その変更が反映されることはない。<br>
C++実行ファイル側では、C#ランタイムのメモリに直接アクセスできないため、その変更が反映されることはない可能性がある。<br>
<br>
<br>
異なるランタイム環境でのメモリ管理の違いからくる制約を考慮して、C++とC#間でデータの受け渡しを行う場合は、適切な手法やデータ構造を選択する必要がある。<br>
異なるランタイム環境でのメモリ管理の違いからくる制約を考慮して、C++とC#間でデータの受け渡しを行う場合は、適切な手法やデータ構造を選択する必要がある。<br>
例えば、C#ライブラリ側で変更可能な値を戻り値として返し、それをC++で受け取る等の方法がある。<br>
例えば、C#ライブラリ側で変更可能な値を戻り値として返し、それをC++で受け取る等の方法がある。<br>
<br>
以下の例では、C++側でint型とMonoString*型を定義して、C#ライブラリ側で値を変更している。<br>
<syntaxhighlight lang="c++">
// C++
 
// ...略
// メソッド情報の取得
const char *methodName = "func";
auto  method = mono_class_get_method_from_name(mainClass, methodName, 2);
int intValue                = 0;
MonoString *stringMonoValue = nullptr;
// 引数を格納する配列
void *params[] = {
    &intValue,
    &stringMonoValue
};
// C#ライブラリのメソッドの呼び出し
MonoObject* excObject = nullptr;
mono_runtime_invoke(method, classInstance, params, &excObject);
if (excObject)
{
    MonoString *excString  = mono_object_to_string(excObject, nullptr);
    const char *excCString = mono_string_to_utf8(excString);
    std::cout << "メソッドの実行時における例外 : " << excCString << std::endl;
    mono_jit_cleanup(domain);
    return -1;
}
else
{
    // 第1引数のint型を参照にしてC#ライブラリ側で変更した場合
    std::cout << intValue << std::endl;  // 100を出力する
    // 第2引数のMonoString*型を参照にしてC#ライブラリ側で変更した場合
    std::string retStringMonoValue = mono_string_to_utf8(stringMonoValue);
    std::cout << retStringMonoValue << std::endl;  // "abcあいう"を出力する
}
// ...略
</syntaxhighlight>
<br>
<syntaxhighlight lang="c#">
// C#
public void func(ref int value1, ref string value2)
{
    value1 = 100;
    value2 = "abcあいう";
    return;
}
</syntaxhighlight>
<br>
<br>


451行目: 533行目:
     // メモリの解放
     // メモリの解放
     mono_free(stringValue);
     mono_free(stringValue);
}
</syntaxhighlight>
<br>
===== 戻り値が配列の場合 (非コレクション) =====
以下の例では、C#ライブラリのメソッドの戻り値はint型の配列であり、C++でそれを受け取っている。<br>
<br>
まず、<code>mono_runtime_invoke</code>関数でC#のメソッドを呼び出して、戻り値を<code>MonoObject*</code>型で受け取る。<br>
次に、<code>reinterpret_cast</code>を使用して<code>MonoObject*</code>型を<code>MonoArray*</code>に変換する。<br>
最後に、<code>mono_array_get</code>関数を使用して<code>int</code>型の配列の各要素を取得している。<br>
<br>
<syntaxhighlight lang="c#">
// C#ライブラリ
public int[] RetList()
{
    return [1, 2, 3];
}
</syntaxhighlight>
<br>
<syntaxhighlight lang="c++">
// C++
MonoObject *pResult = mono_runtime_invoke(method, instance, nullptr, &exc);
// エラーの確認
if (exc != nullptr) {
    // エラー情報の取得
    // ...略
}
else {
    // MonoObject*型をint型の配列に変換
    MonoArray* resultArray = reinterpret_cast<MonoArray*>(pResult);
    // 配列の長さを取得
    auto arrayLength = mono_array_length(resultArray);
    // int型の配列に変換
    std::vector<int> ivec(0, 0);
    for (auto i = 0; i < arrayLength; i++) {
      // MonoArray*型をint型に変換
      ivec.push_back(mono_array_get(resultArray, int, i));
    }
    for (auto i : ivec) {
      std::cout << i << std::endl;
    }
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
464行目: 592行目:
<syntaxhighlight lang="c++">
<syntaxhighlight lang="c++">
  // C#ライブラリがタプル型の戻り値を返す場合
  // C#ライブラリがタプル型の戻り値を返す場合
struct TupleResult {
    int        Field1;  // int型
    MonoString* Field2;  // string型
};
// ...略
  MonoObject *pResult = mono_runtime_invoke(method, instance, params, &exc);
  MonoObject *pResult = mono_runtime_invoke(method, instance, params, &exc);
   
   
471行目: 606行目:
     // ...略
     // ...略
  }
  }
  else
  else {
{
     // タプル型から個々の値を取得
     // タプル型から個々の値を取得
     MonoArray *pResultArray = reinterpret_cast<MonoArray*>(pResult);
     TupleResult *tupleResult = static_cast<TupleResult*>(mono_object_unbox(pResult));
     int        intValue = tupleResult->Field1;
     TupleResult tupleResult;
     std::string strValue = mono_string_to_utf8(tupleResult->Field2);
    tupleResult.intValue   = *reinterpret_cast<int*>(mono_array_addr(pResultArray, int, 0));
     tupleResult.stringValue = mono_string_to_utf8(reinterpret_cast<MonoString*>(mono_array_addr(pResultArray, MonoString*, 1)));
   
   
     // 結果を出力
     // 結果を出力
     std::cout << "Method result: " << tupleResult.intValue << ", " << tupleResult.stringValue << std::endl;
     std::cout << "Method result: " << intValue << ", " << strValue << std::endl;  
    // メモリの解放
    mono_free(tupleResult.stringValue);
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
761行目: 890行目:
# [デバッガーの種類]項目を、[混合]または[自動]に設定して、[OK]ボタンを押下する。
# [デバッガーの種類]項目を、[混合]または[自動]に設定して、[OK]ボタンを押下する。
<br><br>
<br><br>
{{#seo:
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,Podman,電気回路,電子回路,基板,プリント基板
|description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux
|image=/resources/assets/MochiuLogo_Single_Blue.png
}}


__FORCETOC__
__FORCETOC__
[[カテゴリ:C++]]
[[カテゴリ:C++]]