はじめに
過去に作成したC++の関数(API)をC#から呼び出したい場合があると思います。
その場合は、C++のプロジェクトをDLLにして、C#からAPIを呼び出すことが可能です。
ただし、C#(マネージ・コード)の変数とC++(アンマネージ・コード)の変数は、
メモリへの配置が基本型以外異なっており、単純にC#からC++に渡すことができません。
例えば、文字列を扱う場合は、C#のstring型とC++のstd::string型は同一ではないため、マーシャリングの処理が必要です。
ここで、マーシャリングとは、異なる2つのシステム間で、データを交換できるようにデータを操作する処理を指します。
環境
- IDE : Visual Studio Community 2017 (Version 15.7.1)
コード
DLLは、C++のプロジェクトを右クリックしてプロパティ⇒構成プロパティ⇒全般⇒構成の種類をダイナミック ライブラリ (.dll)にすることで作成可能です。
以下のDLLの関数呼び出しを行います。
関数説明 | 宣言 | |
---|---|---|
1 | 整数型の引数を2つ渡して戻り値に整数型を返す関数 | int Mul(int x, int y); |
2 | 引数に、整数型のポインタを3つ渡す関数 (内、1つは出力用) | void MulI(int x, int y, int *result); |
3 | マルチバイト文字列を設定する関数 | void SetNameStr(const char_t *t); |
4 | マルチバイト文字列を取得する関数 | const char_t * GetNameStr(); |
5 | Unicode文字列を設定する関数 | void SetNameWStr(const char_t *t); |
6 | Unicode文字列を取得する関数 | const wchar_t * GetNameWStr(); |
[C++のソースコード]
#include <string> #ifdef __cplusplus #define DLLEXPORT extern "C" __declspec(dllexport) #else #define DLLEXPORT __declspec(dllexport) #endif static std::string _str = ""; static std::wstring _wstr = L""; DLLEXPORT int __stdcall Mul(int x, int y) { return x * y; } DLLEXPORT void __stdcall MulUsePointer(int *x, int *y, int *result) { *result = (*x) * (*y); } DLLEXPORT void __stdcall SetNameStr(const char *t) { _str = std::string(t); } DLLEXPORT const char * __stdcall GetNameStr() { return _str.c_str(); } DLLEXPORT void __stdcall SetNameWStr(const wchar_t *t) { _wstr = std::wstring(t); } DLLEXPORT const wchar_t * __stdcall GetNameWStr() { return _wstr.c_str(); }
C#のコードで、DLL関数を呼び出すため、DLLImportを行います。
DllImpoortは、以下のように記載します。CPlusDLL
は、DLL名を指します。C#のプロジェクトの実行ファイルと同じ階層にDLLファイルがある場合は、DLLファイル名のみでかまいません。
違うパスにある場合は、Full Pathで指定する必要があります。
C#側でC++のAPIを呼び出そうとしたとき、「エントリーポイントが見つかりません。」と実行時にエラーが出た場合は、DllImport文に誤りがある可能性が高いです。
[DllImport("CPlusDLL", EntryPoint = "SetNameStr", CharSet = CharSet.Ansi)]
パラメータ | 説明 | 既定値 |
---|---|---|
EntryPoint | DLL内の関数の名前 | |
CharSet | 文字列のマーシャリング方法を示すCharSet列挙 | CharSet.Auto |
SetLastError | Win32エラー情報を維持するか? | FALSE |
ExactSpelling | EntryPointの関数名を厳密に一致させるか? | FALSE |
PreserveSig | 定義通りのメソッドのシグネチャを維持するか? | |
CallingConvention | EntryPointで使用するモードを指定するCallingConvention列挙 | StdCall |
なお、C# 言語では外部メソッドを呼び出す場合、必ず、メソッドに extern 修飾子を指定しなければなりません。
以下のメソッド宣言で、ref
とout
というパラメーター修飾子を使っています。
out修飾子はreturn以外でメソッド内からメソッド外へデータを受け渡す場合で使用されます。
ref修飾子はメソッド外からメソッド内へデータを渡し、変更を外部へ反映させる必要がある場合に使用します。
static extern void _MulUsePointer(ref int x, ref int y, out int result);
以下のコードstatic extern IntPtr _GetNameStr();
では、IntPtr型を戻り値の型として使用しています。
IntPtr型は、汎用的なポインタを表す型で、ほぼ void* と同義です。
[C#のコード]
using System; using System.Runtime.InteropServices; namespace ConsoleApp1 { class Program { [DllImport("CPlusDLL", EntryPoint = "Mul")] static extern int _Mul(int x, int y); [DllImport("CPlusDLL", EntryPoint = "MulUsePointer")] static extern void _MulUsePointer(ref int x, ref int y, out int result); [DllImport("CPlusDLL", EntryPoint = "SetNameStr", CharSet = CharSet.Ansi)] static extern void _SetNameStr(string t); [DllImport("CPlusDLL", EntryPoint = "GetNameStr", CharSet = CharSet.Ansi)] static extern IntPtr _GetNameStr(); [DllImport("CPlusDLL", EntryPoint = "SetNameWStr", CharSet = CharSet.Unicode)] static extern void _SetNameWStr(string t); [DllImport("CPlusDLL", EntryPoint = "GetNameWStr", CharSet = CharSet.Unicode)] static extern IntPtr _GetNameWStr(); static void Main(string[] args) { int x = 10; int y = 20; int resultMul = _Mul(x, y); Console.WriteLine("Mul = " + resultMul); _MulUsePointer(ref x, ref y, out int resultMulUsePointer); Console.WriteLine("MulUsePointer = " + resultMulUsePointer); string testString = "東京都新宿 1-1"; _SetNameStr(testString); Console.WriteLine("GetNameStr = " + Marshal.PtrToStringAnsi(_GetNameStr())); _SetNameWStr(testString); Console.WriteLine("GetNameWStr = " + Marshal.PtrToStringUni(_GetNameWStr())); } } }
実行結果
参考リンク
- C#からDLL関数の呼び出し
- Moonmile Solutions Blog