C#プログラミング備忘録

C#からC言語の関数(DLL)を呼び出す

C#

C#からDLL内のC言語で書かれた関数を呼び出す方法の備忘録。DLLの作成方法はこちらの記事を参照。

今回もC#の実行ファイルと同じフォルダ内に作成してある。

まずCの関数定義のヘッダーファイルとソースファイルは以下のとおり。

#ifdef CPPLIB_EXPORTS
#define CPPLIB_API __declspec(dllexport)
#else
#define CPPLIB_API __declspec(dllimport)
#endif

typedef struct _TESTSTRUCT {
	const char *ptrOfChar;
	char arrOfChar[64];
} TESTSTRUCT;

typedef struct _TESTSTRUCT_W {
	const wchar_t *ptrOfWchar;
	wchar_t arrOfWchar[128];
} TESTSTRUCT_W;

// 関数ポインタの型
typedef unsigned int (*FnCppCallBack)(unsigned int n);

#ifdef  __cplusplus
extern "C" {
#endif

// 関数定義
CPPLIB_API int fnCppLib(void);
CPPLIB_API int fnAddInt(int, int);
CPPLIB_API int fnSumOfIntArray(int *, int);
CPPLIB_API void fnCppStruct(TESTSTRUCT *, TESTSTRUCT_W *);
CPPLIB_API unsigned int fnCppCallBackCalc(FnCppCallBack, unsigned int);

#ifdef  __cplusplus
}
#endif

extern “C” はC++コンパイラによる名前マングリング(name mangling)を回避するために必要なものである。簡単に言うとC++ではなくCリンケージでコンパイルするように指示するためのもの。名前マングリングについてはこちらの記事の補足を参照。

// CppLib.cpp : DLL 用にエクスポートされる関数を定義します。
//

#include "pch.h"
#include "framework.h"
#include "CppLib.h"

CPPLIB_API int fnCppLib(void)
{
    return 42;
}

CPPLIB_API int fnAddInt(int a, int b)
{
    return a + b;
}

CPPLIB_API int fnSumOfIntArray(int *pArray, int arrSize) 
{
    int sum = 0;
    for (size_t sz = 0; sz < arrSize; sz++) 
    {
        sum += pArray[sz];
    }
    return sum;
}

CPPLIB_API void fnCppStruct(TESTSTRUCT *pTestStruct, TESTSTRUCT_W *pTestStructW) 
{
    pTestStruct->ptrOfChar = "Function call of C function";
    pTestStruct->arrOfChar[0] = 'H';
    pTestStruct->arrOfChar[1] = 'e';
    pTestStruct->arrOfChar[2] = 'l';
    pTestStruct->arrOfChar[3] = 'l';
    pTestStruct->arrOfChar[4] = 'o';
    pTestStruct->arrOfChar[5] = '\0';


    pTestStructW->ptrOfWchar = L"C言語の関数呼び出し";
    pTestStructW->arrOfWchar[0] = L'こ';
    pTestStructW->arrOfWchar[1] = L'ん';
    pTestStructW->arrOfWchar[2] = L'に';
    pTestStructW->arrOfWchar[3] = L'ち';
    pTestStructW->arrOfWchar[4] = L'は';
    pTestStructW->arrOfWchar[5] = L'\0';
}

CPPLIB_API unsigned int fnCppCallBackCalc(FnCppCallBack func, unsigned int n)
{
    return func(n);
}

次に上記4つのC言語の関数を呼び出すC#のコードは以下のとおり。

using System;
using System.Runtime.InteropServices;

namespace CSharpStudy
{
    class Program
    {   
        static void Main(string[] args)
        {
            int c = NativeMethods.fnCppLib();
            Console.WriteLine(c);

            c = NativeMethods.fnAddInt(35, 51);
            Console.WriteLine(c);

            int[] array = new int[10];
            for(int i = 0; i < array.Length; i++)
            {
                array[i] = i + 1;
            }
            int sum = NativeMethods.fnSumOfIntArray(array, array.Length);
            Console.WriteLine(sum);

            TestStruct myStruct = new TestStruct();
            TestStructW myStructW = new TestStructW();

            NativeMethods.fnCppStruct(ref myStruct, ref myStructW);

            Console.WriteLine(Marshal.PtrToStringAnsi(myStruct.ptrOfChar));
            Console.WriteLine(myStruct.myString);

            Console.WriteLine(Marshal.PtrToStringUni(myStructW.ptrOfWchar));
            Console.WriteLine(myStructW.myStringW);

            // 5の階乗を計算する
            uint answer = NativeMethods.fnCppCallBackCalc(new FnCppCallBack(CalcFactorial), 5);
            Console.WriteLine(answer);

            Console.ReadLine();
        } 

        // 階乗の計算をする
        static uint CalcFactorial(uint n)
        {
            return n == 0 ? 1 : n * CalcFactorial(n - 1);
        }
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    internal struct TestStruct
    {
        public IntPtr ptrOfChar;
        
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
        public string myString;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    internal struct TestStructW
    {
        public IntPtr ptrOfWchar;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string myStringW;
    }
    
    // Cの関数ポインタと同等のデリゲート型の定義
    delegate uint FnCppCallBack(uint n);

    internal static class NativeMethods
    {
        [DllImport("CppLib.dll")]
        internal static extern int fnCppLib();
        [DllImport("CppLib.dll")]
        internal static extern int fnAddInt(int a, int b);
        [DllImport("CppLib.dll")]
        internal static extern int fnSumOfIntArray(int[] array, int size);
        [DllImport("CppLib.dll")]
        internal static extern void fnCppStruct(ref TestStruct testSt, ref TestStructW testStW);
        [DllImport("CppLib.dll")]
        internal static extern uint fnCppCallBackCalc(FnCppCallBack func, uint n);
    }
}
  • fnCppLib
  • fnAddInt
  • fnSumOfIntArray

上記3つの関数はデータ型(int)が blittable 型であるためマーシャリングは不要。 

ほとんどのデータ型の表現はマネージド メモリとアンマネージド メモリの両方で共通しているため、相互運用マーシャラーによる特別な処理は必要ありません。 これらの型は、マネージド コードとアンマネージド コード間での受け渡しの際に変換が必要でないため、blittable 型 と呼ばれます。

blittable 型の詳細については以下を参照。

Blittable 型と非 Blittable 型

Blittable 型と非 Blittable 型 - .NET Framework
Blittable 型と非 Blittable 型について説明します。 Blittable データ型の表現は、マネージド メモリとアンマネージド メモリで共通しており、特別な処理は必要ありません。
  • fnCppStruct

上記の関数は構造体を引数に取るためマーシャリングが必要となる。構造体のマーシャリングの詳細については以下を参照。

クラス、構造体、および共用体のマーシャリング

クラス、構造体、および共用体のマーシャリング - .NET Framework
クラス、構造体、および共用体をマーシャリングする方法を確認します。 クラス、入れ子になった構造体を含む構造体、構造体の配列、および共用体のマーシャリング サンプルを表示します。
  • fnCppCallBackCalc

Cの関数ポインタについて追記した。C#においてCの関数ポインタはデリゲートオブジェクトに相当する。デリゲートのマーシャリングの詳細については以下を参照。

コールバックメソッドとしてのデリゲートのマーシャリング

コールバック メソッドとしてのデリゲートのマーシャ リング - .NET Framework
デリゲートをコールバック メソッドとしてマーシャリングする方法について説明します。 関数ポインターを予期しているアンマネージド関数にデリゲートを渡す方法の例を示します。

出力結果は以下のとおり。

42
86
55
Function call of C function
Hello
C言語の関数呼び出し
こんにちは
120

コメント