ちょび日記

明日は明日の風が吹く

2016-10-02

P/Invokeで構造体を渡す時


Unity側のMonoで処理しているとどーしても関数のオーバーヘッドがきつくて演算がつらい、 というときはC++側で書いてP/Invokeで処理してしまうのがお手軽です。が、よく渡し方を忘れるのでメモ

主にWindows向けの話なので他プラットフォームの方はいいかんじに読み替えて下さい。

P/Invoke基本(C++側)

とりあえず、InterlopTest.dllというdllを作るのでVSでプロジェクトをつくって行きます。 まず適当なソースを追加してC++側に同じ構造体を定義します。

#include <iostream>
struct Vector3
{
  float x;
  float y;
  float z;
};

適当な関数を定義してあげる

extern "C" __declspec(dllexport) void Vector3Test(Vector3 hoge)
{
  std::cout << hoge.x << " " << hoge.y << " " << hoge.z << std::endl;
}

dllにするので命名をあわせるextern “C”と__declspec(dllexport)が必要となります。

とりあえずはこれで準備OKです。 Propertyの現在の構成から ・構成プロパティ→全般→プロジェクトの既定値→構成の種類→ダイナミックライブラリ(.dll) ・構成プロパティ→全般→全般→ターゲットの拡張子→.dll

になってることを確認してビルドすればdllが出来上がるはずです。 多分大多数の人は64bit版のUnityを使っているはずなので、プラットフォームはx64に合わせてください。

P/Invoke基本(C#)側

出来上がったdllをPlugins/以下に配置しておきます。今回はInterlopTest.dllという名前でした。

[DllImport("InteropExample")]
public static extern void Vector3Test(Vector3 hoge);

これでdllの位置があっていればVector3Test(hoge)が呼べるようになるはずです。 構造体をそのまま渡すときはそのまんまでおっけです。

ケース1)refで渡したい

・C++側の関数定義をVector3* hogeにしてビルドしておきます。 ・C#側のメソッド定義にrefをつけておきます:Vector3Test(ref Vector3 hoge); あとはrefつけて呼ぶだけ。

ケース2)配列を渡したい

managed 配列を渡す場合はGCHandle.AllocのPinnedを使って対象のアドレスを固定してから送ります。

それに伴い関数定義も変わります。

まずはC++側

extern "C" __declspec(dllexport) void Vector3Test(Vector3* hoge, int length)
{
  for (int i = 0; i < length; i++)
  {
    auto v = hoge[i];
    std::cout << v.x << " " << v.y << " " << v.z << length;
  }
}

つづいてC#側

public static extern void Vector3Test(IntPtr hoge, int length);

使い方

# bはVector3[]とおもいねぇ
var pin = GCHandle.Alloc(b, GCHandleType.Pinned);
Vector3Test(pin.AddrOfPinnedObject(), b.Length);
pin.Free();

unsafeを使って同じこともできますが、大抵の場合GCHandle.Allocでどうにか出来るのでこっち使いましょう。

ソースコード

最後にまるっとソースコード載せときます。

#include <iostream>

struct Vector3
{
  float x;
  float y;
  float z;
};

extern "C" __declspec(dllexport) void Vector3Test(Vector3 hoge)
{
  std::cout << hoge.x << " " << hoge.y << " " << hoge.z << std::endl;
}

extern "C" __declspec(dllexport) void Vector3Test2(Vector3* hoge)
{
  std::cout << hoge->x << " " << hoge->y << " " << hoge->z << std::endl;
}

extern "C" __declspec(dllexport) void Vector3Test3(Vector3* hoge, int length)
{
  for (int i = 0; i < length; i++)
  {
    auto v = hoge[i];

    std::cout << v.x << " " << v.y << " " << v.z << length;

  }
}
using System;
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;

public class NewBehaviourScript : MonoBehaviour {

    // Use this for initialization
    void Start ()
    {
        Vector3 a = new Vector3(1, 2, 3);
        Vector3[] b = new Vector3[2];
        b[0] = new Vector3(1, 2, 3);
        b[1] = new Vector3(4, 5, 6);

        Vector3Test(a);
        Vector3Test2(ref a);

        var pin = GCHandle.Alloc(b, GCHandleType.Pinned);
        Vector3Test3(pin.AddrOfPinnedObject(), b.Length);
        pin.Free();
    }

    [DllImport("InteropExample")]
    public static extern void Vector3Test(Vector3 hoge);

    [DllImport("InteropExample")]
    public static extern void Vector3Test2(ref Vector3 hoge);

    [DllImport("InteropExample")]
    public static extern void Vector3Test3(IntPtr hoge, int length);
}



Copyright© 2016, chobie All rights reserved.