C++の関数を、C#で利用します
C++の関数は例として、「配列の最大値を検索し、インデックスと最大値を返す」というものを作成してみます。
基本の手順は(02)C#から、C++の関数の実行(簡単な例)と変わりませんが、C++プロジェクトも作成するので少し複雑になります。
下の手順で作成されたソリューションは、ここにあります(サンプルプログラムにおける警告について)。
1. 準備
- C#プロジェクトの新規作成
- ファイル → 新規作成 → プロジェクト → Visual C# → Windows → Windowsフォームアプリケーション
- 例として名前は「test02」とする
- 例として名前は「test02」とする
- ファイル → 新規作成 → プロジェクト → Visual C# → Windows → Windowsフォームアプリケーション
- C++プロジェクトの新規作成
- ソリューションエクスプローラーのソリューションを右クリック → 追加 → 新しいプロジェクト → Visual C++ → Win32 プロジェクト
- 例として名前は「NativeFunc」
- アプリケーションの種類は「DLL」
- シンボルのエクスポートにチェック
- ソリューションエクスプローラーのソリューションを右クリック → 追加 → 新しいプロジェクト → Visual C++ → Win32 プロジェクト
- C++/CLR(CLI)ラッパークラスの新規作成
- ソリューションエクスプローラーのソリューションを右クリック → 追加 → 新しいプロジェクト → Visual C++ → CLR → クラスライブラリ
- 例として名前は「WrapperClass」
- ソリューションエクスプローラーの「WrapperClass」を右クリック → プロパティ → リンカー → 入力 →追加の依存ファイル
- $(OutDir)NativeFunc.lib
- 「すべての構成」にしておくこと
- ソリューションエクスプローラーのソリューションを右クリック → 追加 → 新しいプロジェクト → Visual C++ → CLR → クラスライブラリ
- 依存関係の設定など
- ソリューションエクスプローラーのソリューションを右クリック → プロジェクト依存関係
- コンボボックスの「test02」を選択 → 「WrapperClass」にチェック
- コンボボックスの「WrapperClass」を選択 → 「NativeFunc」にチェック
- ソリューションエクスプローラーの「test02」を左クリック → 参照を右クリック → 参照の追加
- 「WrapperClass」を追加する
- 「WrapperClass」を追加する
- ソリューションエクスプローラーのソリューションを右クリック → プロジェクト依存関係
- 構成マネージャの設定
- ビルド → 構成マネージャ
- アクティブ ソリューション プラットフォームをx86に設定する
- 「test02」のプラットフォームのAny CPUをクリック → 新規作成 → 新しいプラットフォームをX86 → OK
- すべてのプロジェクトのビルドにチェックがついていることを確認する
- DebugとReleaseの両方を設定する
- ビルド → 構成マネージャ
- C#プロジェクトの出力先の変更
- ソリューションエクスプローラーの「test02」を右クリック → プロパティ → ビルド → 出力パス(※1)
- Debugにおいて、「bin\x86\Debug\」を「..\Debug\」に変更する
- Releaseにおいて、「bin\x86\Release\」を「..\Release\」に変更する
- 今回の例では、C++の出力先と同じになるように変更しました
- ソリューションエクスプローラーの「test02」を右クリック → プロパティ → ビルド → 出力パス(※1)
2. C++のコード
- NativeFunc.hの内容を、以下に置き換えます。
#ifdef NATIVEFUNC_EXPORTS
#define NATIVEFUNC_API __declspec(dllexport)
#else
#define NATIVEFUNC_API __declspec(dllimport)
#endif
namespace Native
{
// num個のint配列srcから、最大値とそのインデックスを探す関数です
NATIVEFUNC_API void Max(int* src, int num, int* mx, int* mxIndex);
}
- NativeFunc.cppの内容を、以下に置き換えます。
#include "stdafx.h"
#include "NativeFunc.h"
NATIVEFUNC_API void Native::Max(int* src, int num, int* mx, int* mxIndex)
{
*mx = src[0];
*mxIndex = 0;
for (int i = 0; i < num; i++)
{
if (src[i] > *mx)
{
*mxIndex = i;
*mx = src[i];
}
}
}
- ポイントは特にありません。普通にコーディングします。
3. C++/CLR(CLI)ラッパークラスのコード
- WrapperClass.hの内容を、以下に置き換えます。
#pragma once
using namespace System;
namespace Wrapper {
public ref class WrapperClass
{
public:
void Max(array<int>^ src, int num, int% mx, int% mxIndex);
};
}
- WrapperClass.cppの内容を、以下に置き換えます。
#include "stdafx.h"
#include "WrapperClass.h"
#include "..\NativeFunc\NativeFunc.h"
void Wrapper::WrapperClass::Max(array<int>^ src, int num, int% mx, int% mxIndex)
{
// ★ここがポイント
// 実行中、ガベージコレクションされないように、pin_ptrを使って固定する
pin_ptr<int> psrc = &src[0];
pin_ptr<int> pmx = &mx;
pin_ptr<int> pMxIndex = &mxIndex;
// 自作関数実行
Native::Max(psrc, num, pmx, pMxIndex);
// ★ここがポイント
// 固定解除
psrc = nullptr;
pmx = nullptr;
pMxIndex = nullptr;
}
- ポイント
- マネージドな変数
array<int>^
を、pin_ptr
で固定して、
アンマネージドな変数を扱う関数(Native::Max
)にポインタを渡しています - 固定した後は、解除します(必須!)
- マネージドな変数
4. C#のコード
- Form1.csの内容を、以下に置き換えます。
using System;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Wrapper;
namespace test02
{
public partial class Form1 : Form
{
WrapperClass _wr = new WrapperClass(); //WrapperClassのインスタンスを作成
public Form1()
{
InitializeComponent();
int[] a = { 1, 2, 3, 4, 5, 6, 5, 4, 3, 2 };
int mx = 0;
int mxIndex = 0;
int num = 10;
MessageBox.Show("int[] a = { 1, 2, 3, 4, 5, 6, 5, 4, 3, 2 }");
_wr.Max(a, num, ref mx, ref mxIndex); //処理実行
MessageBox.Show("Max = " + mx.ToString() + ", MaxIndex=" + mxIndex.ToString());
Environment.Exit(0);
}
}
}
結果
int[] a = { 1, 2, 3, 4, 5, 6, 5, 4, 3, 2 }
から最大値と最大値のインデックスを取得し表示します。
実行ファイルは、.\Debug
と.\Release
にあります。
処理前、
処理後、
※1
x64を選択した場合には、「..\x64\Debug(とRelease)」にします。
※
C++自作関数内でブレークポイントを設定したい時は、test02プロジェクトのプロパティ → デバッグ → ネイティブ コードのデバッグを有効にするにチェックを入れてください(無料でこれができるなんて感動ものです)