MochiuWiki : SUSE, EC, PCB
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
C++の応用 - C Sharp DLLの使用のソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
C++の応用 - C Sharp DLLの使用
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == C++ EXEからC# DLLの関数を呼び出す方法は、幾つか方法が存在しており、各々にメリットとデメリットがある。<br> 下記の表1に代表的な4種類の方法を示す。<br><br> <center> '''表1. C++ EXEからC# DLLの関数を呼び出す方法''' {| class="wikitable" | style="background-color:#fefefe;" |- ! style="background-color:#66CCFF;" | 方法 ! style="background-color:#66CCFF;" | メリット ! style="background-color:#66CCFF;" | デメリット |- | C++ EXEをC++/CLIに設定して使う || 最も簡単<br>VisualStudioのIntelliSenseも使用可能 || C++ EXEのプロジェクトの設定において、[CLIを使う]に変更する必要がある。<br><br><u>Windowsのみ使用可能。</u> |- | .NET DLLExportを使用して、<br>C# DLLのメソッドをエクスポートする || [CLIを使う]に変更しなくてよい<br><code>GetProcAddress</code>関数が使用できるため、<br>よく知られた方法で関数を呼び出す事が出来る || C# DLL側のソースコードが無い場合は利用できない。<br><br><u>Windowsのみ使用可能。</u> |- | C# DLLに対するC++/CLIのラッパーDLLを作成して、<br>C++ EXEから使う || [CLIを使う]に変更しなくてよい<br>COMを使用しない場合において、<br>元のプロジェクトの設定を変更したくない場合に使用可能 || C#のDLL、および、C++/CLIのDLLの2つのライブラリを作成する必要がある。<br><br><u>Windowsのみ使用可能。</u> |- | Monoを使う || C++実行ファイルとC#ライブラリの2つを作成するだけでよい。<br><u>Linux、MacOSでも使用可能である。ただし、デメリットも多い。</u><br><br>C++の実行ファイルにおいて、<br>Monoに関連するヘッダファイルをインクルードして、C#のライブラリを呼び出す。<br> || Linuxの場合は、.NET Standard (.NET Core / .NET 5以降ではない) またはMonoを使用して、C#のライブラリを作成する必要がある。<br>Windowsの場合は、.NET FrameworkまたはMonoを使用して、C#のライブラリを作成する必要がある。<br>したがって、.NET Core / .NET 5以降は使用することができない。<br><br>また、実行環境にもMonoをインストールする必要がある。 |- | C# DLLをCOM参照可能にする || C++/CLIは不要となる。 || C++ EXEのコード量が増えて煩雑となる。<br><br>COM (Component Object Model) は、Windows独自の技術であり、<br><u>Linuxでは直接サポートされていないため、Windowsのみ使用可能である。</u><br>COMはWindowsのオブジェクト指向プログラミングモデルであり、Microsoftの開発環境やWindows APIと密接に関連している。 |} </center> <br> 上表1において、以下の手順を記載する。<br> * C++/CLIを使う * C# DLL側で関数をエクスポートする * C++/CLIのラッパープロジェクトを作成する * C# DLLをCOM参照可能にする <br><br> == C++/CLIを使う方法 == <syntaxhighlight lang="c#"> // SampleDLL.cs namespace SampleDLL { public class Class1 { public static int Sum(int a, int b) { return a + b; } } } </syntaxhighlight> Visual C++のプロジェクト設定を開いて、[共通言語ランタイム サポート (/clr)]に変更する。<br> <syntaxhighlight lang="c++"> // SampleEXE.cpp #include <Windows.h> #include <iostream> #using "SampleDLL.dll" using namespace SampleDLL; int main() { std::cout << Class1::Sum(1, 2) << std::endl; return 0; } </syntaxhighlight> <br><br> == C# DLL側で関数をエクスポートする方法 == まず、プロジェクトを作成してソースコードを記述する。<br> <syntaxhighlight lang="c#"> // SampleDLL.cs namespace SampleDLL { public class Class1 { [DllExport] public static int Sum(int a, int b) { return a + b; } } } </syntaxhighlight> <br> <syntaxhighlight lang="c++"> // SampleEXE.cpp #include <Windows.h> #include <iostream> typedef int (*Sum)(int a, int b); int main() { auto hModule = LoadLibrary(L"DllExportTest.dll"); auto sum = reinterpret_cast<Sum>(GetProcAddress(hModule, "Sum")); std::cout << sum(1, 2) << std::endl; return 0; } </syntaxhighlight> <br> 次に、[https://github.com/3F/DllExport/releases DllExport.batをダウンロード]して、DllExport.batをC# DLLのslnファイルと同じ階層に配置する。<br><br> 続いて、コマンドプロンプトを開いて以下のコマンドを実行して、.NET DLLExportを起動する。<br> DllExport.bat -action Configure .NET DLLExportダイアログにて、[Installed]チェックボックスにチェックを入力して、[Apply]ボタンを押下する。<br> [[ファイル:dotNET_DLLExport.png|フレームなし|中央]] <br> 最後に、C# DLLのプロジェクトをリビルドすると、作成した関数がエクスポートされる。<br><br> == C++/CLIのラッパープロジェクトを使用する方法 == ==== C#ライブラリの作成 ==== まず、C#ライブラリを作成する。<br> <syntaxhighlight lang="c#"> // CSharpDLL.cs namespace CSharpDLL { public static class CSharpDLLClass { public static void ShowValue(ref int value) { DialogResult result = MessageBox.Show("C# Message Box", "C# Message Box", MessageBoxButtons.OKCancel); if (result == DialogResult.OK) { value = 1; } else { value = 2; } return; } } } </syntaxhighlight> <br> ==== C++/CLIライブラリの作成 ==== 次に、C++/CLIライブラリを作成する。<br> <br> ソリューションエクスプローラからC++/CLIプロジェクトを右クリックして[参照の追加]を選択、上記で作成したC# DLLファイルを追加する。<br> <br> また、Visual C++のプロジェクト設定を開いて、[構成プロパティ] - [全般] - [共通言語ランタイム サポート (/clr)]に変更する。<br> 同様に、[構成プロパティ] - [C/C++] - [プリプロセッサ] - [プリプロセッサの定義]項目に、<code>DLL</code>プリプロセッサを追加する。<br> <syntaxhighlight lang="c++"> // CppCLIDLL.cpp #include "stdafx.h" #include "CppCLIDLL.h" using namespace System; using namespace System::Reflection; using namespace CSharpDLL; namespace CppCLIDll { public ref class CppCLIClass { public:void ShowCSharpMessageBox(int *value) { CSharpDLLClass::ShowValue(*value); return; } }; } void ShowMessageBox(int *value) { CppCLIDll::CppCLIClass clsCLI; clsCLI.ShowCSharpMessageBox(value); } </syntaxhighlight> <br> <syntaxhighlight lang="c++"> // CppCLIDLL.h #pragma once #ifdef DLL __declspec(dllexport) void ShowMessageBox(int *value); #else __declspec(dllimport) void ShowMessageBox(int *value); #endif </syntaxhighlight> <br> ==== C++実行バイナリの作成 ==== C++実行バイナリのプロジェクトを作成する。<br> <br> ===== 暗黙的リンクを行う場合 ===== メニューバーから[Visual C++のプロジェクト設定]を選択して、[構成プロパティ] - [C/C++] - [全般] - [追加のインクルードディレクトリ]項目に、<br> CppCLIDLL.hが存在するディレクトリを追加する。<br> <br> 同様に、[構成プロパティ] - [リンカー] - [全般] - [追加のライブラリディレクトリ]項目に、CppCLIDLL.libが存在するディレクトリを追加する。<br> さらに、[構成プロパティ] - [リンカー] - [入力] - [追加の依存ファイル]項目に、CppCLIDLL.libを追加する。<br> <syntaxhighlight lang="c++"> // CppEXE.cpp #include "stdafx.h" #include <windows.h> #include "CppEXE.h" #include "CppCLIDLL.h" int _tmain() { int result = 0; ShowMessageBox(&result); if (result == 1) { printf("Ok Was Pressed \n"); printf("%d\n", result); } else if (result == 2) { printf("Cancel Was Pressed \n"); printf("%d\n", result); } else { printf("Unknown result \n"); } system("pause"); return 0; } </syntaxhighlight> <br> ===== 明示的リンクを行う場合 ===== # まず、<code>LoadLibrary</code>関数を使用して、C++/CLIライブラリを読み込む。<br> # 次に、<code>GetProcAddress</code>関数を使用して、C++/CLIライブラリ内の関数オブジェクトのアドレスを取得する。<br> # C++/CLIライブラリの関数を呼び出す。 <br> <syntaxhighlight lang="c++"> // CppEXE.cpp #include "stdafx.h" #include <windows.h> #include "CppEXE.h" using fnShowMessageBox void(*)(int*); int _tmain() { int result = 0; // C++/CLIライブラリを呼び出す auto hModule = ::LoadLibrary(L"CppCLIDLL.dll"); if (NULL == hModule) { return -1; } // C++/CLIライブラリの関数を読み込む auto ShowMessageBox = reinterpret_cast<fnShowMessageBox>(::GetProcAddress(hModule, "ShowMessageBox")); // C++/CLIライブラリの関数を実行する ShowMessageBox(&result); if (result == 1) { std::cout << "Ok Was Pressed" << std::endl; std::cout << result << std::endl; } else if (result == 2) { std::cout << "Cancel Was Pressed" << std::endl; std::cout << result << std::endl; } else { std::cout << "Unknown result" << std::endl; } system("pause"); return 0; } </syntaxhighlight> <br><br> == Monoを使用する場合 == ==== Monoのインストール ==== * RHEL *: [[インストール - Mono|インストール - Mono(RHEL)]]を参照して、Monoをインストールする。<br> * SUSE *: [[インストール - Mono(SUSE)]]を参照して、Monoをインストールする。<br> * Windows *: [https://www.mono-project.com/download/stable/ Monoの公式Webサイト]にアクセスして、[Download Mono 64-bit (no GTK#)]を選択して、Monoをダウンロードする。 *: Monoをインストールする。 *: <br> *: 必要ならば、スタートメニューのMonoプログラムグループ下に[Monoコマンドプロンプトを開く]ショートカットを作成する。 *: このショートカットは、Mono関連のパス情報がすでに設定されたコマンドシェルを起動するものである。 <br> ==== C#ライブラリの作成 ==== 以下の例では、SampleLibraryという名前のライブラリを作成している。<br> <syntaxhighlight lang="c#"> using System; namespace SampleLibrary { public class SampleClass { public void SampleMethod(int intValue, string stringValue) { Console.WriteLine($"Received int: {intValue}, string: {stringValue}"); } } } </syntaxhighlight> <br> ==== C++実行ファイルの作成 ==== SUSEでは、コンパイル時において、<code>-L/usr/lib64 -lmono-2.0</code>オプション、および、<code>-I/usr/include/mono-2.0</code>オプションを付加する必要がある。<br> または、plg-configツールを使用することもできる。<br> g++ -o <アプリケーション名 例: SampleApp> main.cpp `pkg-config --cflags --libs mono-2` <br> C#ライブラリのメソッドを呼び出す<code>mono_runtime_invoke</code>関数に指定する引数を、以下に示す。<br> * 第1引数 : <code>void**</code>型 *: C#のメソッドがインスタンスメソッドである場合、メソッドを呼び出すインスタンスのポインタを指定する。 *: Staticメソッドの場合は、<code>nullptr</code>を指定する。 * 第2引数 : <code>void**</code>型 *: メソッドに渡す引数を指定する。 *: 引数がある場合、各引数の値へのポインタが配列として渡される。 *: 引数が無い場合は、<code>nullptr</code>を指定する。 * 第3引数 : <code>MonoObject**</code>型 *: エラーが発生した場合に、エラー情報を格納する<code>MonoObject</code>型のポインタを指定する。 *: エラーを取得しない場合は、<code>nullptr</code>を指定する。 <br> <syntaxhighlight lang="c++"> #include <iostream> #include <mono/jit/jit.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/exception.h> int main() { // ドメインの初期化 MonoDomain *domain = mono_jit_init("SampleApp"); if (!domain) return -1; // C#ライブラリのアセンブリの読み込み (中間言語IL) MonoAssembly *assembly = mono_domain_assembly_open(domain, "SampleLibrary.dll"); if (!assembly) mono_jit_cleanup(domain); return -1; // アセンブリのイメージの読み込み (アセンブリ内のコード情報を保持しているもの) MonoImage *image = mono_assembly_get_image(assembly); if (!image) mono_jit_cleanup(domain); return -1; // 名前空間およびクラス名を指定 const char *nameSpace = "SampleLibrary"; // 名前空間を指定する 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型) int intValue = 42; const char *stringValue = "Hello from C++"; // 引数を格納する配列 void *params[] = { &intValue, // int型へのポインタ mono_string_new(domain, stringValue) // string型へのポインタ }; // C#ライブラリのメソッドに引数がない場合 void *params[] = { nullptr }; // エラー情報が格納される変数 MonoObject *exc = nullptr; // C#ライブラリのメソッドの呼び出し // C#ライブラリのStaticメソッド(引数あり)を呼び出す場合 --> 例: mono_runtime_invoke(method, nullptr, params, nullptr); // C#ライブラリのStaticメソッド(引数なし)を呼び出す場合 --> 例: mono_runtime_invoke(method, nullptr, nullptr, nullptr); // C#ライブラリのStaticメソッド(引数あり)、かつ、エラー情報不要で呼び出す場合 --> 例: mono_runtime_invoke(method, nullptr, params, nullptr); mono_runtime_invoke(method, instance, params, &exc); // エラーの確認 if (exc != nullptr) { // エラー情報の取得 MonoClass *excClass = mono_object_get_class(exc); MonoMethod *toStringMethod = mono_class_get_method_from_name(excClass, "ToString", 0); MonoString *errorMessage = (MonoString*)mono_runtime_invoke(toStringMethod, exc, nullptr, nullptr); // エラーメッセージの出力 const char *pstrErrMessage = mono_string_to_utf8(errorMessage); std::cout << "Error: " << pstrErrMessage << std::endl; mono_free(pstrErrMessage); } // ドメインの解放 mono_jit_cleanup(domain); return 0; } </syntaxhighlight> <br> ==== 引数 ==== ===== ポインタを渡す場合 ===== C#ライブラリ側でデータ型のポインタを受け取り、その変数の値を変更しても、その変更がC++実行ファイル側に反映されることは期待できない。<br> これは、C#とC++が異なるメモリ管理とランタイム環境を持っており、それぞれ独自のメモリ管理を行うためである。<br> <br> C#では、ガベージコレクションが行われ、メモリの確保や解放はCLR (Common Language Runtime) によって管理されている。<br> 一方、C++では開発者が手動でメモリの確保と解放を行う。<br> <br> したがって、C#ライブラリ内でデータ型のポインタを受け取り、その変数の値を変更したとしても、それはC#ランタイムの管理するメモリ内で行われることになる。<br> C++実行ファイル側では、C#ランタイムのメモリに直接アクセスできないため、その変更が反映されることはない可能性がある。<br> <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> ==== 戻り値がある場合 ==== ===== 戻り値がint型の場合 ===== C#ライブラリが戻り値を返す場合は、<code>mono_runtime_invoke</code>関数でC#のメソッドを呼び出して、戻り値を<code>MonoObject*</code>型で受け取る。<br> その後、<code>mono_field_get_value</code>関数を使用して、<code>MonoObject*</code>型のオブジェクトからint型の戻り値を取得する。<br> <br> <u>※注意</u><br> <u><code>mono_runtime_invoke</code>関数の戻り値は<code>MonoObject*</code>型であるため、実際のint型のデータは内部的には<code>m_value</code>と呼ばれる領域に格納されていることである。</u><br> <u>これにより、int型の戻り値を取り出すことができる。</u><br> <br> <syntaxhighlight lang="c++"> // C#ライブラリがint型の戻り値を返す場合 MonoObject *pResult = mono_runtime_invoke(method, instance, params, &exc); // エラーの確認 if (exc != nullptr) { // エラー情報の取得 // ...略 } else { // int型に変換 int returnValue; mono_field_get_value(pResult, mono_class_get_field_from_name(mono_object_get_class(result), "m_value", "System.Int32"), &returnValue); // 結果を出力 std::cout << "Method result: " << returnValue << std::endl; } </syntaxhighlight> <br> ===== 戻り値がstring型の場合 ===== 以下の例では、<code>mono_runtime_invoke</code>関数でC#のメソッドを呼び出して、戻り値を<code>MonoObject*</code>型で受け取る。<br> 次に、<code>reinterpret_cast</code>を使用して<code>MonoObject*</code>型を<code>MonoString*</code>に変換する。<br> 最後に、<code>mono_string_to_utf8</code>関数を使用して<code>string</code>型の戻り値を取得している。<br> <syntaxhighlight lang="c++"> // C#ライブラリがstring型の戻り値を返す場合 MonoObject *pResult = mono_runtime_invoke(method, instance, params, &exc); // エラーの確認 if (exc != nullptr) { // エラー情報の取得 // ...略 } else { // string型に変換 MonoString *pResultString = reinterpret_cast<MonoString*>(pResult); const char *stringValue = mono_string_to_utf8(pResultString); // 結果を出力 std::cout << "Method result: " << stringValue << std::endl; // メモリの解放 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> <br> ===== 戻り値がタプル型の場合 ===== タプル型において、C#からC++に直接渡すことは難しいため、複数の戻り値を配列や構造体で受け取る必要がある。<br> もし可能であれば、代わりに複数の引数を使用して値を取得することを検討することもできる。<br> <br> 以下の例では、タプル型 (int, string) の戻り値を配列として受け取り、各要素を取り出している。<br> <u>ただし、戻り値の型や順序に依存するため、メソッドが変更されると対応が必要となることに注意する。</u><br> <br> <syntaxhighlight lang="c++"> // C#ライブラリがタプル型の戻り値を返す場合 struct TupleResult { int Field1; // int型 MonoString* Field2; // string型 }; // ...略 MonoObject *pResult = mono_runtime_invoke(method, instance, params, &exc); // エラーの確認 if (exc != nullptr) { // エラー情報の取得 // ...略 } else { // タプル型から個々の値を取得 TupleResult *tupleResult = static_cast<TupleResult*>(mono_object_unbox(pResult)); int intValue = tupleResult->Field1; std::string strValue = mono_string_to_utf8(tupleResult->Field2); // 結果を出力 std::cout << "Method result: " << intValue << ", " << strValue << std::endl; } </syntaxhighlight> <br><br> == C# DLLをCOM参照可能にしてC++ EXEから使用する方法 == まず、C# DLLプロジェクトを作成して、アセンブリ情報を設定する。<br> # C# DLLプロジェクトのプロパティを開く。 # プロパティ画面左にある[アプリケーション]タブを選択して、[アセンブリ情報]ボタンを押下する。 # [アセンブリをCOM参照可能にする]チェックボックスにチェックを入力して、[OK]ボタンを押下する。 <br> 次に、ビルドの設定を行う。<br> # C# DLLプロジェクトのプロパティを開く。 # プロパティ画面左にある[ビルド]タブを選択して、[COM相互運用機能の登録]チェックボックスにチェックを入力する。 # C# DLLプロジェクトのプロパティを保存する。 <br> <u>※注意1</u><br> <u>[COM相互運用機能の登録]は、<code>regasm</code>コマンドによるCOMのレジストリ登録を、ビルド時に自動で行う機能と思われる。</u><br> <u>そのため、開発時(デバッグ時)は有効にした方が便利であるが、インストール時はCOMのレジストリ登録が自動で行われないため注意すること。</u><br> <br> <u>※注意2</u><br> <u>Windowsにログインしているアカウントの権限によっては、ビルド時にレジストリへの登録に失敗するエラーが発生することがある。</u><br> <u>その時は、Visual Studioを管理者権限で実行すれば登録できる。</u><br> <br> C# DLLでは、以下のような内容のソースコードを記述する。<br> この時、C++ EXE側から呼ぶクラスには、以下の属性を付加する。<br> * ComVisible * ClassInterface * Guid (Visual Studioの[ツール]メニューバー - [GUIDの作成]を選択する) <syntaxhighlight lang="c#"> // CSharpCOMDLL.csファイル using System; using System.Runtime.InteropServices; namespace CSharpCOMDLL { [ComVisible(true)] [ClassInterface(ClassInterfaceType.AutoDual)] [Guid("85555B74-E2E0-4493-9869-3CE95F13CB99")] // Visual Studioの[ツール]メニューバー - [GUIDの作成]を選択する public class CSharpCOMDLLClass { public Int32 Add(Int32 iParam1, Int32 iParam2) { int iRet = iParam1 + iParam2; return (Int32)iRet; } public Int32 AddStr([MarshalAs(UnmanagedType.BStr)]string str) // 文字列を指定する場合はマーシャリングする { Console.WriteLine(str); return (Int32)0; } } } </syntaxhighlight> <br> C++ EXEプロジェクトを作成して、以下のような内容のソースコードを記述する。<br> <syntaxhighlight lang="c++"> // main.cppファイル #include <iostream> #include <Windows.h> IDispatch *pIDisp = NULL; IUnknown *pIUnk = NULL; long Init(void); long Finalize(void); long AddInt(long p_Number1, long p_Number2); long AddStr(); int main() { // COMの初期化処理 Init(); // C# DLLのメソッドを呼ぶ int l_Result = Add(300, 500); //後処理 Finalize(); printf("Calc Result : %d", l_Result); return 0; } // 初期化関数 long Init(void) { // COMの初期化 ::CoInitialize(NULL); // ProcIDからCLSIDを取得(ネームスペース名.クラス名) CLSID clsid; HRESULT h_result = CLSIDFromProgID(L"CSharpCOMDLL.CSharpCOMDLLClass", &clsid); // 第1引数は、呼び出すC# DLLの<名前空間名>.<クラス名>にすること if (FAILED(h_result)) { return -1; } // インスタンスの生成 h_result = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pIUnk); if (FAILED(h_result)) { return -2; } // インターフェースの取得(pIDispは共通変数) h_result = pIUnk->QueryInterface(IID_IDispatch, (void**)&pIDisp); if (FAILED(h_result)) { return -3; } return 0; } // COMの終了処理 long Finalize() { // インスタンスの開放 pIDisp->Release(); // インターフェイスの開放 pIUnk->Release(); // COMの開放 ::CoUninitialize(); return 0; } // C# DLLのメソッドを呼ぶ long AddInt(long p_Number1, long p_Number2) { // メソッド名からID(DISPID)を取得(関数名の設定) DISPID dispid = 0; OLECHAR *Func_Name[] = { SysAllocString (L"Add") }; // C# DLLの呼び出すメソッド名を指定する HRESULT h_result = pIDisp->GetIDsOfNames(IID_NULL, Func_Name, 1, LOCALE_SYSTEM_DEFAULT, &dispid); if (FAILED(h_result)) { return -1; } // メソッドに渡すパラメータを作成(DISPPARAMS、 VariantInit等) DISPPARAMS params = {0}; params.cNamedArgs = 0; params.rgdispidNamedArgs = NULL; params.cArgs = 2; // 呼び出す関数の引数の数 // 引数の指定 (順番が逆になることに注意すること) VARIANTARG* pVarg = new VARIANTARG[params.cArgs]; pVarg[0].vt = VT_I4; pVarg[0].lVal = p_Number2; pVarg[1].vt = VT_I4; pVarg[1].lVal = p_Number1; params.rgvarg = pVarg; VARIANT vRet; VariantInit(&vRet); // C# DLLのメソッドを呼ぶ(pIDisp->Invoke) pIDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶ms, &vRet, NULL, NULL); delete[] pVarg; return vRet.lVal; } long AddStr() { // メソッド名からID(DISPID)を取得(関数名の設定) DISPID dispid = 0; OLECHAR *Func_Name[] = { SysAllocString (L"AddStr") }; HRESULT h_result = pIDisp->GetIDsOfNames(IID_NULL, Func_Name, 1, LOCALE_SYSTEM_DEFAULT, &dispid); if (FAILED(h_result)) { return -1; } // メソッドに渡すパラメータを作成(DISPPARAMS、 VariantInit等) DISPPARAMS params = {0}; params.cNamedArgs = 0; params.rgdispidNamedArgs = NULL; params.cArgs = 1; // 呼び出すメソッドの引数の数 // 引数の指定 (順番が逆になることに注意すること) VARIANT var; DISPPARAMS dispParams; var.vt = VT_BSTR; // 引数に渡すデータ型をBSTRにする var.bstrVal = SysAllocString(L"あいうえお"); // 引数に渡す文字列 dispParams.cArgs = 1; dispParams.rgvarg = &var; dispParams.cNamedArgs = 0; dispParams.rgdispidNamedArgs = NULL; VARIANT vRet; VariantInit(&vRet); // C# DLLのメソッドを呼ぶ(pIDisp->Invoke) printf("[OK] Invoke start\r\n"); h_result = pIDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispParams, &vRet, NULL, NULL); if (FAILED(h_result)) { printf("[NG] Invoke failed\r\n"); return -2; } return vRet.lVal; } </syntaxhighlight> <br> 管理者権限でPowerShellまたはコマンドプロンプトを実行する。<br> 次に、<code>regasm</code>コマンドを使用して、C# DLL(COM)を登録する。<br> C# DLLのプラットフォーム(x86/x64)により、x86/x64向けの<code>regasm</code>と合致させる必要があることに注意する。<br> <br> また、regasm.exeをC++ EXE側のプロジェクトディレクトリにコピーして実行しても構わない。<br> # プロジェクトがx86の場合 C:\Windows\Microsoft.NET\Framework\<.NETのバージョン>\regasm /codebase <C# DLLのファイル名>.dll # プロジェクトがx64の場合 C:\Windows\Microsoft.NET\Framework64\<.NETのバージョン>\regasm /codebase <C# DLLのファイル名>.dll <br> C++ EXEを実行する場合、以下の内容のバッチファイルを作成して、管理者権限で実行する。<br> 常に<code>regasm</code>コマンドを実行する場合、C# DLLプロジェクトの[COM相互運用機能の登録]の設定は不要である。<br> <u>ただし、デバッグ時においては有効にした方が便利である。</u><br> <syntaxhighlight lang="bat"> rem exerun.batファイル @echo off cd %~dp0 regasm /codebase <C# DLLのファイル名>.dll start /wait <C++ EXEのファイル名>.exe echo exeからの戻り値は %ERRORLEVEL% です pause </syntaxhighlight> <br> 下表に、C#のデータ型、C++のデータ型、VARTYPEの関係を示す。<br> <br> C++から引数を指定する場合、および、C# DLLからの戻り値を取得するために、VARIANT型を使用する必要がある。<br> そのため、C#、C++、VARIANT型の関係を理解する必要がある。<br> <center> {| class="wikitable" | style="background-color:#fefefe;" |- ! style="background-color:#66CCFF; width: 200px;" | C++ ! style="background-color:#66CCFF; width: 200px;" | C# ! style="background-color:#66CCFF; width: 200px;" | VARTYPE ! style="background-color:#66CCFF; width: 200px;" | 使用するメンバ |- style="text-align: center;" | SHORT (short) || short (System.Int16) || VT_I2 || iVal |- style="text-align: center;" | INT (int)<br>LONG (long) || int (System.Int32) || VT_I4 || lVal |- style="text-align: center;" | BOOL (long) || bool (System.Boolean) || VT_BOOL || boolVal |- style="text-align: center;" | LPCSTR (const char *)<br>LPCWSTR (const wchar_t *) || string (System.String) || VT_BSTR || bstrVal |- style="text-align: center;" | FLOAT (float) || float (System.Single) || VT_R4 || fltVal |- style="text-align: center;" | DOUBLE (double) || double (System.Double) || VT_R8 || dblVal |} </center> <br> <u>※注意3</u><br> * <u>C# DLLがx64の場合、C++ EXEもx64でビルドする必要がある。</u><br><u>同様に、C++ EXE -> C# COM DLL(ラッパーDLL) -> C# DLLとする場合も、ラッパーDLLはx64である必要がある。</u> * <u>C# DLLがx86の場合、C++ EXEもx86でビルドする必要がある。</u><br><u>同様に、C++ EXE -> C# COM DLL(ラッパーDLL) -> C# DLLとする場合も、ラッパーDLLはx86である必要がある。</u> <br><br> == デバッグ == C++プロジェクトからC#プロジェクトに対するデバッグを有効にする手順を記載する。<br> <br> # [ソリューションエクスプローラー]に表示されているC++プロジェクトを右クリックして、[プロパティ]を選択する。<br> # [<プロジェクト名> プロパティページ]画面にて、[構成プロパティ] - [デバッグ]を選択する。 # [デバッガーの種類]項目を、[混合]または[自動]に設定して、[OK]ボタンを押下する。 <br><br> __FORCETOC__ [[カテゴリ:C++]]
C++の応用 - C Sharp DLLの使用
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse