こんにちは、Unityエンジニアのオオバです。
Unityでゲーム開発しているとOS固有の機能にアクセスしたくなるときがあります。すると途端にC#では対応できず、C++やObjective-C、Swift、Javaといったプラットフォームに対応した言語を使う必要が出てきます。
本記事はC#からC++にデータ転送基礎シリーズ。過去にint型、文字列型と解説していきました。
今回は 「配列型」 データをC#からC++に渡す方法を解説していきます。
ビルド環境についてはコチラの記事をどうぞ。
マーシャリングを理解して値を変換する
最初に結論をお話すると C#からC++に配列はそのまま送信できません。 マーシャリングして、C++が読み取り可能なメモリ空間にC#の値を変換します。
①マーシャリングとは?
②Blittable型と非Blittable型の理解
③具体的なマーシャリング方法
④具体的な配列型の転送方法
マーシャリングやBlittableといった特殊なキーワードが出てきましたが、これらは記事内で回収しますのでご安心を。
1つ言えることは過去紹介した文字列型やint型のときより内容が複雑です。とはいっても本ブログはだれにでもわかりやすく伝えるをテーマにしているので、頑張ってわかりやすく紹介していきます。
では本編にいきます。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
マーシャリングとは「C#とC++間のデータ変換」
今回最も重要になのは マーシャリングの理解 です。配列をC#からC++に引き渡す上で必要になる概念になります。
マーシャリングとは 異なるシステム間のデータ変換 のことです。今回の場合は、C#とC++間のデータの変換 を指します。
その通り、C#からC++にint型を送信する際、マーシャリングは不要でした。理由は int型がBlittable型 だったからです。
マーシャリング、Blittable型を理解する上で「マネージドメモリ」と「アンマネージドメモリ」について理解をする必要があります。
マネージドメモリとアンマネージドメモリとは
皆さんがプログラムで扱うデータは次の2つのメモリを使用しています。
- マネージドメモリ
- アンマネージドメモリ
マネージドメモリとはC#(.NET)が管理する領域です。一方アンマネージドメモリとはC++側のメモリ領域です。両者は性質が異なります。
具体的にはマネージドメモリはGC(ガベージコレクション)という仕組みで自動でメモリ管理をしてくれます。方アンマネージドメモリは開発者が自分でメモリを開放する必要があるのです。
C#とC++でデータを受け渡すとき2つのメモリ領域でデータが行き来しているということを意識しておいてください。
マネージドとアンマネージドのイメージができたら「Blittable型」について解説していきます。
Blittable型とは?
Blittable型とはマネージドメモリとアンマネージドメモリで共通して使用されるデータ型です。C#では以下がBlittable型です。
- System.Byte
- System.SByte
- System.Int16
- System.UInt16
- System.Int32
- System.UInt32
- System.Int64
- System.UInt64
- System.IntPtr
- System.UIntPtr
- System.Single
- System.Double
- Blittable型で構成した構造体
これらはC#からC++にデータ送信する上で変換の不要です。
非Blittable型とは?
Blittable型の反対「非Blittable型」もあります。Blittable型の逆なのでC#からC++へ送信時にデータの変換(マーシャリング)が必要になります。
非Blittable型とは以下のような型です。
- 配列型
- bool型
- 文字列型(Char, String)
- Object型
- Class
- ValueType
- T[]
など。
これらの 非Blittable型 をC#とC++間でやり取りする場合は マーシャリングが必要 ということを覚えておきましょう。
文字列も非Blittable型?
以前紹介したC#からC++に文字列を渡す記事ではマーシャリングが登場しませんでした。
理由はC#側からは文字列のポインターを渡すだけで済むため、明示的なマーシャリング処理が不要だからです。
もちろんプログラムの内部ではマーシャリングされています。

今回取り扱う 配列 は 非Blittable型 です。文字列のようにそのままC++に渡すことはできないためマーシャリングが必要になるのです。
- Blittable型 : マーシャリング不要
- 非Blittable型 : マーシャリング必要
以降の章で具体的なマーシャリング方法について解説していきます。
具体的な配列型のマーシャリング
ではここから具体的なマーシャリング及び、C#とC++間のデータをやり取りしていきます。
データを受け取るC++側の処理
まずはデータを受け取るC++側の処理を実装していきます。
C#側から実行されるメソッド 「TestIntArray」 を宣言し、C#から 配列のポインタ を受け取る処理を記述していきます。
配列型を受け取るときは以下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#側の処理を実装していきます。次の3つの手順です。
- C++に送るアンマネージドメモリの確保
- C#の配列データを「1.」で確保したアンマネージドメモリにコピー
- C#からC++に「1.」のポインタと要素数を渡す
マーシャリングは「1.」「2.」のタイミングです。
繰り返しになりますがマーシャリングとは 異なるシステム間のデータ変換 です。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++で管理するアンマネージドメモリは自分で開放する必要があります。前述のソースコード内に以下の行に注目してみてください。
Marshal.FreeCoTaskMem(ptr);
このタイミングで確保したアンマネージドメモリの解放を行っています。この処理を入れないとメモリーリークを起こしてアプリがクラッシュするかもしれません。
主にネイティブプラグインを作成するときにC++に触れることが多くなりますが、メモリの解放は念入りにチェックしておきましょう。
まとめ : C#からC++に配列を渡す方法
int型、文字列の引き渡しは簡単でしたが、配列はマーシャリング処理が必要となり
少し難易度が上がりました。
- アンマネージドメモリを確保
- C#配列のデータをコピー
- C++にポインタと要素数を引き渡す
この一連の流れがイメージできるとそんなに難しくはないかもしれません。
C#とC++間のデータがやり取りできるようになると、作れるものの幅が広がっておもしろいです!

筆者のXをフォローしよう
- VisualStudioCommunity2017 v15.9.8
- Windows10