nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

C#からC++のDLLを呼び出す [C#]

はじめに

過去に作成した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)にすることで作成可能です。

f:id:nprogram:20180516205424p:plain

以下の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 修飾子を指定しなければなりません。

以下のメソッド宣言で、refoutというパラメーター修飾子を使っています。

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()));

        }
    }
}

実行結果

f:id:nprogram:20180516205657p:plain

参考リンク