こんにちは、Unityエンジニアのオオバです。
C#とC++間のデータやりとりを
実装したことありますか?
Unityが提供する機能を普通に使っているだけだと、
あまり出てこないシチュエーションかも知れません。
Unityが提供していない機能にアクセス といった
Unityの外へ一歩出ようとすると
C++や別の言語が必要になります。
今回は変数が値型のみの構造体をC++に渡してみます。
ビルド環境構築はコチラの記事をどうぞ
C#からC++に文字列を渡す方法
C#、C++ともに同じ構造体をつくる
結論を簡単にまとめます。
- 型を合わせた構造体をC#、C++ともに定義
- 構造体をマーシャリングして送信
マーシャリングとは、
異なるプラットフォーム間での
データのやり取りに使う技術で、
メモリ空間をC#とC++で合わせる必要があるのです。
C#からC++に配列を渡してマーシャリングを理解しよう
今回はC#からC++へデータを送信するだけでなく、
C++からC# へデータを戻す
ということもやっていきます。
では構造体のデータ送信について解説を始めます。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
C++に送信するC#の構造体
使用する構造体はコチラ。
int型1つ、float型1つのシンプルなデータです。
💻ソースコード : C++に送るC#で定義した構造体
public struct StructData
{
public int id;
public float value;
}
さっそくC++の実装から進めます。
C#と同じ構造体のポインタをC++で定義
C#の構造体を受け止めるために、
C++内でも同様の構造体を定義します。
💻ソースコード : C++で定義した構造体
typedef struct
{
int id;
float value;
}StructData;
↑のとおりです。
文法は多少違えど理解できますよね。
この際C++に慣れていきましょう。
💻ソースコード : C#から呼ぶC++内のメソッド定義抜粋
DllExport void TestStruct(StructData* output, StructData* input);
以下の2つの引数を持つTestStruct
メソッドを定義
- C++からC#に戻す
output
- C#からC++に渡す
input
どちらもポインタ型です。
💻ソースコード : 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#側の実装に移ります。
[DllImport("TestDll.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void TestStruct(ref StructData output, IntPtr input);
- 第1引数 output : C++からC#へ構造体の参照が返る
- 第2引数 input : C#からC++へ構造体のポインタを渡す
このようにC#で定義します。
新しいポイントとして、
C++から構造体の参照がC#に渡ってくるところです。
戻ってきた構造体を
C#側からは普通にアクセス可能です。
C#からC++に送る構造体のマーシャリング
繰り返しになりますが、
マーシャリングとは 異なるシステム間のデータ変換 です。
C#/C++間でデータをやり取りする場合は、
マーシャリングが必要になります。
// アンマネージドの構造体のメモリ確保
IntPtr inputPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(StructData)));
ポインタ生成(メモリ確保)方法は前回記事のマーシャリングと同じで、
上記のMarshal.AllocCoTaskMem
関数を使用します。
アンマネージドメモリに構造体をコピーしてC++に送る
// マネージド構造体をアンマネージドにコピーする
Marshal.StructureToPtr(inputData, inputPtr, false);
前回は配列をコピーする上でMarshal.Copy
関数を使用しました。
今回は構造体なのでMarshal.StructureToPtr
関数を使用します。
// C++から戻す結果構造体
var result = new StructData();
TestStruct(ref result, inputPtr);
このように第一引数のresult変数にC++が
書き換えた構造体の参照が入って完了です。
まとめ : C#からC++に構造体を渡す方法
記事の内容を簡単にまとめます。
- C#、C++ともに構造体の型をそろえる
- 構造体をマーシャリングしてC++へ送る
- C++からは構造体の参照をC#に返す
こんな感じです。
今回はシンプルな構造体だったため、
そこまで難しくはありませんでした。
次回はもっと複雑な構造体を
試してみようかと思います
今回のサンプルコード全体
最後に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++間データ送信ライフをお過ごしください。
- VisualStudioCommunity2017 v15.9.8
- Windows10