こんにちは、Unityエンジニアのオオバです。

C#とC++間のデータやりとりを
実装したことありますか?

Unityが提供する機能を普通に使っているだけだと、
あまり出てこないシチュエーションかも知れません。

Unityが提供していない機能にアクセス といった
Unityの外へ一歩出ようとすると
C++や別の言語が必要になります。

今回は変数が値型のみの構造体をC++に渡してみます。

ビルド環境構築はコチラの記事をどうぞ

あわせて読みたい記事

C#からC++に文字列を渡す方法

C#、C++ともに同じ構造体をつくる

C#からC++に構造体を渡す方法_0

結論を簡単にまとめます。

マーシャリングとは、
異なるプラットフォーム間での
データのやり取りに使う技術で、
メモリ空間をC#とC++で合わせる必要があるのです。

今回はC#からC++へデータを送信するだけでなく、
C++からC# へデータを戻す
ということもやっていきます。

では構造体のデータ送信について解説を始めます。

👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!

C++に送信するC#の構造体

C#からC++に構造体を渡す方法_1

使用する構造体はコチラ。
int型1つ、float型1つのシンプルなデータです。

💻ソースコード : C++に送るC#で定義した構造体
public struct StructData  
{
    public int id;  
    public float value;  
}

さっそくC++の実装から進めます。

C#と同じ構造体のポインタをC++で定義

C#からC++に構造体を渡す方法_2

C#の構造体を受け止めるために、
C++内でも同様の構造体を定義します。

💻ソースコード : C++で定義した構造体
typedef struct  
{
 int id;  
 float value;  
}StructData;  

↑のとおりです。

文法は多少違えど理解できますよね。
この際C++に慣れていきましょう。

💻ソースコード : C#から呼ぶC++内のメソッド定義抜粋
DllExport void TestStruct(StructData* output, StructData* input);  

以下の2つの引数を持つTestStructメソッドを定義

どちらもポインタ型です。

💻ソースコード : C#から呼ぶC++内のメソッド定義全容
void TestStruct(StructData* output, StructData* input)  
{
    // outputのidに、inputのidに20加算した値を代入  
    output->id = input->id + 20;  
    // outputのvalueに、inputのvalueに0.9999加算した値を代入  
    output->value = input->value + 0.9999;  
}

TestStructの実装自体はシンプルで、
inputから渡された値に定数を加算した値を
outputに代入します。

C#側からはポインタで渡して構造体で返ってくる

C#からC++に構造体を渡す方法_3

C#側の実装に移ります。

[DllImport("TestDll.dll", CallingConvention = CallingConvention.Cdecl)]  
static extern void TestStruct(ref StructData output, IntPtr input);  

このようにC#で定義します。

新しいポイントとして、
C++から構造体の参照がC#に渡ってくるところです。

戻ってきた構造体を
C#側からは普通にアクセス可能です。

C#からC++に送る構造体のマーシャリング

C#からC++に構造体を渡す方法_4

繰り返しになりますが、
マーシャリングとは 異なるシステム間のデータ変換 です。

C#/C++間でデータをやり取りする場合は、
マーシャリングが必要になります。

// アンマネージドの構造体のメモリ確保  
IntPtr inputPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(StructData)));  

ポインタ生成(メモリ確保)方法は前回記事のマーシャリングと同じで、
上記のMarshal.AllocCoTaskMem関数を使用します。

アンマネージドメモリに構造体をコピーしてC++に送る

C#からC++に構造体を渡す方法_5

// マネージド構造体をアンマネージドにコピーする  
Marshal.StructureToPtr(inputData, inputPtr, false);  

前回は配列をコピーする上でMarshal.Copy関数を使用しました。
今回は構造体なのでMarshal.StructureToPtr関数を使用します。

// C++から戻す結果構造体  
var result = new StructData();  
TestStruct(ref result, inputPtr);  

このように第一引数のresult変数にC++が
書き換えた構造体の参照が入って完了です。

まとめ : C#からC++に構造体を渡す方法

C#からC++に構造体を渡す方法_6

記事の内容を簡単にまとめます。

こんな感じです。

今回はシンプルな構造体だったため、
そこまで難しくはありませんでした。

次回はもっと複雑な構造体を
試してみようかと思います

今回のサンプルコード全体

最後にC#、C++のコード全文を共有します。
何か参考になれば幸いです。

💻ソースコード : C#からC++に構造体を送るC++サンプル
#include "stdafx.h"
#include <iostream>

typedef struct  
{
  int id;  
  float value;  
}StructData;  

DllExport void TestStruct(StructData* output, StructData* input);  
void TestStruct(StructData* output, StructData* input)  
{
    // C#から渡された値を加工してC#に返す  
    output->id = input->id + 20;  
    output->value = input->value +0.9999;  
}
💻ソースコード : C#からC++に構造体を送るC#サンプル
[DllImport("TestDll.dll", CallingConvention = CallingConvention.Cdecl)]  
static extern void TestStruct(ref StructData output, IntPtr input);  

//[StructLayout(LayoutKind.Sequential)] 不要  
public struct StructData  
{
    public int id;  
    public float value;  
}

static void Main()  
{
    var inputData = new StructData()  
    {
        id = 10,  
        value = 0.5f  
    };  

    // アンマネージド構造体のメモリ確保  
    System.IntPtr inputPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(StructData)));  

    // マネージド構造体をアンマネージドにコピーする  
    Marshal.StructureToPtr(inputData, inputPtr, false);  
    // C++から戻す結果構造体  
    var result = new StructData();  
    TestStruct(ref result, inputPtr);  

    Console.WriteLine($"pResult : {result.id}/ {result.value} / {inputPtr}");  

    // アンマネージドのメモリを解放  
    Marshal.FreeCoTaskMem(inputPtr);  
}

「Unity初心者大学」というUnity初心者向けのYouTube始めました!!
ぜひチャンネル登録をお願いします!

最後まで読んでいただきありがとうございました!
すばらしいC#/C++間データ送信ライフをお過ごしください。

オススメ記事
検証環境
参考サイト