こんにちわ、C#大好きオオバです。

C#/C++間データやり取りをテーマに執筆しています。

今回はC#の構造体をC++に送信してみたいと思います。

まずは手始めに変数が値型のみの構造体をC++に渡してみます。
値型だけということは、
Blittable型のみで構成されたデータということです。

環境構築については、以前の記事を参考にどうぞ。
C#からC++(DLL)に文字列を渡す

📝 目次

C#からC++へ送信後C++からC#へ

今回はC#からC++へデータを
送信するだけではありません。

C++からC#へデータを返す
ということもやっていきます。

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

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

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

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

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

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

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

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

DllExport void TestStruct(StructData* output, StructData* input);  

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

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

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);  

C#からはinputにStructDataのポインタを渡し、
戻ってくるデータはStructDataそのものが返ってきます。

構造体をマーシャリング

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

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

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

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

C#からC++(DLL)に配列を渡す

アンマネージドメモリに構造体をコピー

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

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

C++を呼ぶ

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

このように第一引数のresultに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;  
}
[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);  
}

その他

本日のイチロー引退会見を見て、自分を成長させるためには、少しずつ以前の自分を超えていくしかないと改めて感じた次第です。

👉 オススメ記事

2021秋 Asset Refreshセール
100以上のアセットがなんと50%OFF!!オオバもいくつか買いました!
期間 : 10月2日午後3時59分まで

👩‍💻 検証環境

🙏 参考サイト