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
コメント