はじめに
先日、Unity6(C#)にC++から値を渡したい記事 を公開しましたが「別にUnityに限定しなくてもいいんじゃない?」と思い、書き直すことにしました。
内容は前回よりもパワーアップしております。
やりたいこと
C#からC++の関数を呼び出し、戻り値として値や文字列、配列を受け取りたいです。
前回は、C++側からの戻り値を単にコンソールに表示するだけでしたが、
今回はC#側から引数を渡し、それを基にC++側で一度処理を行ったうえで結果を返すようにします。
具体的には以下のような型の関数をC#から呼び出します。
・int
・double
・bool
・文字列 (const char*)
・int配列 (int[])
・void (戻り値なし)
環境
・Visual Studio 2022 Community
・C++ 17
・C# 7.3
・.NET Framework 4.8
.dllプロジェクトを作成する
今回はVisual Studio 2022 の Community版を使用します。
新しいプロジェクトの作成(N)
→ Windowsデスクトップウィザード
→ パス&プロジェクト名設定
→ Windowsデスクトッププロジェクト
と進み、
アプリケーションの種類(T): ダイナミックライブラリ(.dll)
追加のオプション: 空のプロジェクト(E)
と設定します。
(今回はプロジェクト名をReturnValue
としています。)
C/C++側
ReturnValue.h
とReturnValue.cpp
を作成し、以下のように記述します。
ReturnValue.h
#pragma once
#ifdef RETURNVALUE_EXPORTS
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __declspec(dllimport)
#endif
extern "C" EXPORT int ReturnInt(int i);
extern "C" EXPORT double ReturnDouble(double d);
extern "C" EXPORT bool ReturnBool(bool b);
extern "C" EXPORT const char* ReturnString(const char* c);
extern "C" EXPORT const int* ReturnIntAry(int add, std::size_t* size);
extern "C" EXPORT void ShowFuncSig();
ReturnValue.cpp
#include <string>
#include <iostream>
#include "ReturnValue.h"
// 123 に int型 の引数を足して返す
__declspec(dllexport) int ReturnInt(int i)
{
return 123 + i;
}
// 1.23 に double型 の引数を足して返す
__declspec(dllexport) double ReturnDouble(double d)
{
return 1.23 + d;
}
// bool型 の引数を受け取って返す
__declspec(dllexport) bool ReturnBool(bool b)
{
return b;
}
// Hello, C++!\n に const char*型 の引数を足して返す
__declspec(dllexport) const char* ReturnString(const char* c)
{
static std::string result;
result = std::string(c) + "Hello, C++!\n";
return result.c_str();
}
// int型配列 の各要素に int型引数 を足して返す
__declspec(dllexport) const int* ReturnIntAry(int add, std::size_t* size)
{
static const int array[] = { 1, 3, 5, 7, 9, 11, 13 };
constexpr std::size_t N = sizeof(array) / sizeof(array[0]);
static int result[N];
for (std::size_t i = 0; i < N; ++i)
result[i] = array[i] + add;
if (size) *size = N;
return result;
}
// 現在のソース行番号 と 関数シグネチャ を表示する
__declspec(dllexport) void ShowFuncSig()
{
std::cout << __LINE__ << ": " << __FUNCSIG__ << '\n';
}
C#呼び出し側
using System;
using System.Runtime.InteropServices;
namespace ConsoleApp
{
internal class Program
{
[DllImport("ReturnValue.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int ReturnInt(int i);
[DllImport("ReturnValue.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern double ReturnDouble(double d);
[DllImport("ReturnValue.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern bool ReturnBool(bool b);
[DllImport("ReturnValue.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr ReturnString(string s);
[DllImport("ReturnValue.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr ReturnIntAry(int add, out UIntPtr size);
[DllImport("ReturnValue.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void ShowFuncSig();
static void Main(string[] args)
{
// 123 に int型 の引数を足して返す
Console.Write($"int: {ReturnInt(123333)}\n");
// 1.23 に double型 の引数を足して返す
Console.Write($"double: {ReturnDouble(0.00456)}\n");
// bool型 の引数を受け取って返す
Console.Write($"bool: {ReturnBool(true)}\n");
// Hello, C++!\n に const char*型 の引数を足して返す
IntPtr pStr = ReturnString("From C#: ");
string str = Marshal.PtrToStringAnsi(pStr);
Console.Write(str);
// int型配列 の各要素に int型引数 を足して返す
IntPtr p = ReturnIntAry(1, out var sizeUP);
if (p == IntPtr.Zero) return;
int len = checked((int)sizeUP);
int[] result = new int[len];
Marshal.Copy(p, result, 0, len);
Console.Write($"int[]: {string.Join(", ", result)}");
// .dll側のソース行番号 と 関数シグネチャ を表示する
Console.WriteLine("");
ShowFuncSig();
}
}
}
結果出力
int: 123456
double: 1.23456
bool: True
From C#: Hello, C++!
int[]: 2, 4, 6, 8, 10, 12, 14
45: void __cdecl ShowFuncSig(void)
なにか間違っている点があれば教えてください。
参考文献
C++ライブラリ(DLL)をUnity(C#)向けに作成して利用するシンプルな方法
C#からC/C++の関数をコールする方法 まとめ①