こんにちは、Unityエンジニアのオオバです。
C#からC++にデータを渡すシリーズ続けていきます。
前回はint型をC#からC++にわたしました。
C#からC++にintを渡す方法
今回は配列を渡します。
ビルド環境については
コチラの記事をどうぞ。
C#からC++に文字列を渡す方法
マーシャリングを理解して値を変換する
C#からC++に配列はそのまま送信できません。
マーシャリングしてC++の読み取り可能なメモリ空間に
C#の値を変換します。
- Blittable型と非Blittable型の理解
- 具体的なマーシャリング方法
↑の内容を学んでいきます。
文字列、int型の送信のときより
若干内容が難しいです。
がんばって理解していきましょう。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
マーシャリングとはC#とC++間のデータの変換
今回最も重要になのはマーシャリングの理解、
配列をC#からC++に引き渡す上で必要になる概念です。
マーシャリングとは 異なるシステム間のデータ変換 を意味します。
今回の場合は、C#とC++間のデータの変換です。
以前C#からC++に int型 の値を送信しましたが、
マーシャリングしませんでした。
理由はint型がBlittable型だからです。
Blittable型とは?
- int型
- float型
- double型
- byte型
など。
このようなプリミティブ型を
Blittable型と呼ばれます。
非Blittable型とは?
非Blittable型とは以下のような型です。
- 配列型
- bool型
- 文字列型(Char, String)
- Object型
- Class
など。
非Blittable型 をC#とC++間でやり取りする場合は、
マーシャリングが必要ということです。
文字列は非Blittable型ですが、
C#側からは文字列のポインターを渡すだけで済み、
明示的な変換処理は不要でした。
※内部的にマーシャリングされています
📚 参考サイト : 文字列に対する既定のマーシャリング | Microsoft Docs
今回あつかう 配列 は非Blittable型です。
文字列のようにそのままC++に渡すことはできないため、
マーシャリングが必要になるのです
- Blittable型 : マーシャリング不要
- 非Blittable型 : マーシャリング必要
C++は配列のポインタと要素数を受け取る
まずはC++側の処理を見ていきます。
TestIntArray
メソッドでC#から
配列のポインタ を受け取ります。
- 配列のポインタ
- 要素の長さ
↑TestIntArrayメソッド
に引数を2つ用意します。
C++のソースコードを見てみましょう。
💻ソースコード : TestIntArrayDll.cpp
#include "stdafx.h"
#include <iostream>
// C#から配列と要素数を渡す関数を定義
DllExport void TestIntArray(int* array, int length);
void TestIntArray(int* array, int length)
{
for (int i = 0; i < length; i++)
{
std::cout << array[i] << std::endl;
}
}
配列を渡す場合は、
要素数 も必要があることを覚えておきましょう。
int* array
がポインタのため、
array.lengthのように
要素数を取得することはできないためです。
C#側の処理でマーシャリング
C#からC++に対して配列を送る手順です。
- C++の配列(アンマネージド配列)のメモリを確保
- C#の配列(マネージド配列)を「1.」で確保したメモリにコピー
- C#からC++に「1.」のポインタを渡す
マーシャリングはここです。
繰り返しになりますが、
マーシャリングとは 異なるシステム間のデータ変換 です。
C#とC++のメモリの扱いは違うため、
C#からC++にデータを送信するためには、
C++側のメモリ構造に合わせる必要があります。
C++にデータを渡すために、
C#側でアンマネージド配列のメモリを確保したのです。
以上を頭に入れつつC#のソースコードを見ていきましょう。
💻ソースコード : Cs2CppArray.cs
[DllImport("TestDll.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void TestIntArray(System.IntPtr array, int length);
static void Main()
{
// 配列要素数
var array = new int[] { 0, 1, 2, 3, 4 };
int length = array.length;
// 確保する配列のメモリサイズ(int型 5つ)
int size = Marshal.SizeOf(typeof(int)) * length;
// C++に渡す配列のアンマネージドメモリを確保
// ※「ptr」は確保したメモリのポインタ
System.IntPtr ptr = Marshal.AllocCoTaskMem(size);
// C#の配列をアンマネージドメモリにコピーする
Marshal.Copy(array, 0, ptr, length);
// C++に配列を渡す(ポインタを渡す)
TestIntArray(ptr, length);
// アンマネージドのメモリを解放
Marshal.FreeCoTaskMem(ptr);
}
ここで重要なのは、
確保したアンマネージドメモリの
ポインタをC++に送る点です。
あらためてマーシャリングとは?
結局マーシャリングはどこだったのか?
という方に向けて復習していきます。
マーシャリングは以下の部分で実行していました。
int size = Marshal.SizeOf(typeof(int)) * length;
// C++に送ることができるアンマネージドメモリの確保
System.IntPtr ptr = Marshal.AllocCoTaskMem(size);
Marshal.Copy(array, 0, ptr, length);
重要なのは下の2行です。
Marshal.AllocCoTaskMem(メモリ確保するサイズ)
で、
C++側に送信可能なアンマネージドメモリを確保
Marshal.Copy(C#の配列, 0, コピー先のポインタ, コピーする個数)
で、
マネージド配列(array)をアンマネージドメモリにコピーし、
C#/C++間の値の変換をしたことになります。
このC#を実行すると無事に配列の中身を
C++側でログ出力して正常に処理が実行されるでしょう。
まとめ : C#からC++に配列を渡す方法
int型、文字列の引き渡しは簡単でしたが、
配列はマーシャリング処理が必要となり、
少し難易度が上がりました。
- アンマネージドメモリを確保
- C#配列のデータをコピー
- C++にポインタと要素数を引き渡す
この一連の流れがイメージできると、
そんなに難しくはないかもしれません。
C#とC++間のデータがやり取りできるようになると、
おもしろいですよね!
この記事が気に入ったらフォローしよう
- VisualStudioCommunity2017 v15.9.8
- Windows10