7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【.dll】C++からC#に値を渡したり、関数を実行したい。

Last updated at Posted at 2025-05-19

はじめに

先日、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# 12.0
・.NET 8.0.16

.dllプロジェクトを作成する

今回はVisual Studio 2022 の Community版を使用します。

新しいプロジェクトの作成(N)Windowsデスクトップウィザード
パス&プロジェクト名設定Windowsデスクトッププロジェクト
と進み、

アプリケーションの種類(T): ダイナミックライブラリ(.dll)
追加のオプション: 空のプロジェクト(E)
image.png
と設定します。
(今回はプロジェクト名をReturnValueとしています。)

C/C++側

ReturnValue.hReturnValue.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 ReturnPtrFree(void* p);
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) char* ReturnString(const char* c)
{
	std::string s = std::string(c) + "Hello, C++!\n";

	std::size_t byte = s.size() + 1;
	char* p = static_cast<char*>(::CoTaskMemAlloc(byte));
	if (!p) return nullptr;

	std::memcpy(p, s.c_str(), byte);
	return p;
}

// int型配列 の各要素に int型引数 を足して返す
__declspec(dllexport) int* ReturnIntAry(int add, std::size_t* size)
{
	static const int array[] = { 1, 3, 5, 7, 9, 11, 13 };
	constexpr std::size_t N = std::size(array);

	int* p = static_cast<int*>(::CoTaskMemAlloc(sizeof(int) * N));
	if (!p) return nullptr;

	for (std::size_t i = 0; i < N; ++i)
		p[i] = array[i] + add;

	if (size) *size = N;

	return p;
}

// 共通解放関数
__declspec(dllexport)
void ReturnPtrFree(void* p)
{
	::CoTaskMemFree(p);
}

// 現在のソース行番号 と 関数シグネチャ を表示する 
__declspec(dllexport) void ShowFuncSig()
{
	std::cout << __LINE__ << ": " << __FUNCSIG__ << '\n';
}

ReturnValue.dll を生成する

リリースビルドをするとソリューションエクスプローラーの\x64\Release\の中に

・ReturnValue.dll
・ReturnValue.exp
・ReturnValue.lib
・ReturnValue.pdb

image.png

が生成されていると思いますので、ReturnValue.dll をコピーしておきます。

C#.exeプロジェクトを作成する

新しいプロジェクトの作成(N)C#コンソール アプリ
パス&プロジェクト名設定
と進み、以下のように設定します。
(今回はプロジェクト名を ConsoleApp としています。)

C#コンソール アプリ(.NET Framework) ではないことに注意してください。

・フレームワーク(F): .NET 8.0(長期的なサポート)

・コンテナーのサポートを有効にする: Docker/Kubernetes などで動かす予定は無いため、チェックは外しておきます。

・最上位レベルのステートメントを使用しない(T): static void Main(...) メソッドはコード側に含まれているので、チェックは外しておきます。

・native AOT 発行を有効にする: 今回はデバッグ用なので、チェックは外しておきます。

image.png

以上設定ができましたら、作成(C) をクリックします。

プロジェクトのビルド設定

C# コンソール プロジェクトを作成すると、おそらくAny CPUとなっていると思います。
なのでここを正しい環境に合わせて設定する必要があります。

ビルド(B)構成マネージャー(O) と進むと、
新たにウィンドウが出てくると思います。
image.png

アクティブ ソリューション プラットフォーム(P): Any CPU
をクリックして <新規作成...> をクリックします。

image.png

するとまたウィンドウが出てくると思いますので、

新しいプラットフォームを入力または選択してください(P): x64

とします。

image.png

設定ができましたら OK を押して戻ります。
ウィンドウ上部が Debug, x64 となっていれば大丈夫です。

image.png

ReturnValue.dll の配置

先ほど .dll プロジェクトで生成した ReturnValue.dll を C#コンソールプロジェクト側に正しく配置をする必要があります。
ConsoleApp\bin\x64\Debug\net8.0 の中に ReturnValue.dll をペーストします。
フォルダが見当たらない場合、一度Visual Studio上でビルドを実行すると生成されるかと思います。

適切に.dllを配置できていない場合、以下手順を進めても

System.DllNotFoundException: 'Unable to load DLL 'ReturnValue.dll' or one of its dependencies: 指定されたモジュールが見つかりません。 (0x8007007E)'

DLLが見つからないというエラーが表示され、正常に動作しません。

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)]
        static extern void ReturnPtrFree(IntPtr p);

        [DllImport("ReturnValue.dll", CallingConvention = CallingConvention.Cdecl)]
        private static extern void ShowFuncSig();


        static void Main(string[] args)
        {
            Console.WriteLine(RuntimeInformation.FrameworkDescription);

            // 123 に int型 の引数を足して返す
            Console.Write($"int: {ReturnInt(123333)}\n");


            // 1.23 に double型 の引数を足して返す
            Console.Write($"double: {ReturnDouble(0.00456):F5}\n");


            // bool型 の引数を受け取って返す
            Console.Write($"bool: {ReturnBool(true)}\n");


            // Hello, C++!\n に const char*型 の引数を足して返す
            IntPtr pStr = ReturnString("From C#: ");
            string? str = Marshal.PtrToStringAnsi(pStr);
            ReturnPtrFree(pStr);
            Console.Write(str);

            // int型配列 の各要素に int型引数 を足して返してから
            IntPtr pArry = ReturnIntAry(2, out var size);
            int len = checked((int)size);
            var result = new int[len];
            Marshal.Copy(pArry, result, 0, len);
            ReturnPtrFree(pArry);
            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++の関数をコールする方法 まとめ①

(C#コンソールプロジェクト設定を追記しました。)
(確保と解放をdll側で統一しました。)

7
6
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?