为了账号安全,请及时绑定邮箱和手机立即绑定

Unity与C++交互入门(2)

标签:
Unity 3D

在介绍复杂数据类型的传递之前,先说一下如何在C++中回调C#函数。

一、delegate与函数指针

Unity与C++交互最麻烦的是调试的过程,在C++ DLL中直接print或cout打印log是没法看到的,我们可以在C++中调用C#的函数来输出log,这需要将delegate映射到C++的函数指针。

在上一节用到的C#脚本中添加如下代码,并在Start()的第一行调用RegisterDebugCallback()。

    void RegisterDebugCallback()
    {
        DebugDelegate callback_delegate = CallBackFunction;        //将Delegate转换为非托管的函数指针
        IntPtr intptr_delegate = Marshal.GetFunctionPointerForDelegate(callback_delegate);        //调用非托管函数
        SetDebugFunction(intptr_delegate);
    }

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]    public delegate void DebugDelegate(IntPtr strPtr);

    [DllImport("UnityCppInterop")]    public static extern void SetDebugFunction(IntPtr fp);    static void CallBackFunction(IntPtr strPtr)
    {
        Debug.LogError("CpppppppppLog: " + Marshal.PtrToStringAnsi(strPtr));
    }

IntPtr代表的是C++的指针,Marshal.GetFunctionPointerForDelegate()的作用是将C#的委托转化为函数指针,通过SetDebugFunction()将回调函数注册到C++中。

在C++项目中新建Debuger类:

//Debuger.h#pragma once#include <iostream>#include <string>using namespace std;class Debuger{public:    typedef void(*DebugFuncPtr)(const char *);    static DebugFuncPtr FuncPtr;    static void SetDebugFuncPtr(DebugFuncPtr ptr);    static char container[100];    static void Log(const string str);
};//=========================================================================//Debuger.cpp#include "Debuger.h"Debuger::DebugFuncPtr Debuger::FuncPtr;void Debuger::SetDebugFuncPtr(DebugFuncPtr ptr)
{
    FuncPtr = ptr;
}char Debuger::container[100];void Debuger::Log(const string str)
{    if (FuncPtr != nullptr)
    {
        FuncPtr(str.c_str());
    }
}

修改Bridge类:

//Bridge.h#ifdef WIN32#ifdef  UNITY_CPP_INTEROP_DLL_BRIDGE#define UNITY_CPP_INTEROP_DLL_BRIDGE    __declspec(dllexport)#else#define UNITY_CPP_INTEROP_DLL_BRIDGE    __declspec(dllimport)#endif#else// Linux#define UNITY_CPP_INTEROP_DLL_BRIDGE#endif#include <string>#include <sstream>#include "Debuger.h"using namespace std;extern "C"{    UNITY_CPP_INTEROP_DLL_BRIDGE void SetDebugFunction(Debuger::DebugFuncPtr fp);    UNITY_CPP_INTEROP_DLL_BRIDGE int Internal_Add(int a, int b);
}//===============================================//Bridge.cpp#include "Bridge.h"extern "C"{   
    void SetDebugFunction(Debuger::DebugFuncPtr fp)
    {
        Debuger::SetDebugFuncPtr(fp);
    }    int Internal_Add(int a, int b)
    {        int res = a + b;        stringstream ss;
        ss << res;
        Debuger::Log(ss.str());        return res;
    }
}

运行可以看到输出为:CpppppppppLog: 11 ,说明C++中的log成功打印了。

二、Marshal和Blittable

1.Marshal(封送):指的是将数据从托管内存封送到非托管内存的过程。

2.Blittable和Non-blittable:blittable表示可以被直接复制到非托管内存,而不需要Marshal进行转换处理的数据类型,non-blittable相反。(详见Micorsoft官方文档

Blittable类型包括:

System.ByteSystem.SByteSystem.Int16System.UInt16System.Int32System.UInt32System.Int64System.UInt64System.IntPtrSystem.UIntPtrSystem.SingleSystem.Double此外,blittable类型的一维数组(如:int[]),以及只包含blittable类型的struct或class(如:struct中只包含int, float等),也属于blittable。

Non-blittable类型包括:

Non-blittable 类型描述
System.Array转换为 C 样式数组或 SAFEARRAY
System.Boolean转换为 1、2 或 4 字节的值,true 表示 1 或 -1。
System.Char转换为 Unicode 或 ANSI 字符。
System.Class转换为类接口。
System.Object转换为变量或接口。
System.Mdarray转换为 C 样式数组或 SAFEARRAY
System.String转换为空引用中的终止字符串或转换为 BSTR。
System.Valuetype转换为具有固定内存布局的结构。
System.Szarray转换为 C 样式数组或 SAFEARRAY
delegate

C#与C++之间进行数据传递(函数参数、函数返回值等)时,blittable类型可以直接作为参数传递,non-blittable需要借助Marshal做相应转换,但作为函数返回的数据,必须是blittable类型。

三、struct和class的传递

前面提到只包含blittable类型的struct或class也属于blittable类型,因此对于简单的struct和class可以直接传递。

先看个简单的例子,在C#端定义Person结构体,并定义胶水方法ChangePerson()。

//TestCppInterop.cs[StructLayout(LayoutKind.Sequential)]public struct Person{
    public int Age;    public float Height;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]    public int[] Scores;    public bool Married;
}

[DllImport("UnityCppInterop", EntryPoint = "Internal_ChangePerson")]private static extern int ChangePerson(ref Person p);void Start(){
    Person p = new Person()
    {
        Age = 18,
        Height = 178.6f,
        Scores = new[] { 66, 77, 88, 99 },
        Married = false
    };
    Debug.LogError("==========Before Cpp Change============");
    Debug.LogError(string.Format("Age: {0}, Height: {1}, Score0: {2}, Score1: {3}, Sex: {4}",       p.Age, p.Height, p.Scores[0], p.Scores[1], p.Married));
    ChangePerson(ref p);
    Debug.LogError("==========After Cpp Change============");
    Debug.LogError(string.Format("Age: {0}, Height: {1}, Score0: {2}, Score1: {3}, Sex: {4}",       p.Age, p.Height, p.Scores[0], p.Scores[1], p.Married));
}

在C++端也要定义同样的结构体Person,和ChangePerson()

//Brige.hstruct Person {public:    int Age;    float Height;    int Scores[4];    bool Married;
};extern "C"{    UNITY_CPP_INTEROP_DLL_BRIDGE void Internal_ChangePerson(Person* p);
}//Bridge.cppextern "C"{    void Internal_ChangePerson(Person* p)
    {
        p->Age += 10;
        p->Height += 10;
        p->Scores[0] += 10;
        p->Scores[1] += 10;
        p->Married = true;
    }
}

输出如下:

426

ChangePerson.png

StructLayout:传递struct或class时,C#和C++两端的struct映射是按照逐个变量去映射的,但.Net出于优化内存占用的目的,有可能会调整成员变量的排布顺序,而C++编译器不会做这些优化,为了保证两端的struct内存布局一致,需要标记[StructLayout(LayoutKind.Sequential)]特性防止.Net进行优化。

struct内包含数组:此时必须指定数组大小,[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]

在C++中,struct 和 class 除了成员的默认访问权限不一致外,二者基本一样,因此将C#中的struct映射为C++的struct或class都是可以的,C#的class也一样。但在C#中,struct是值类型,class是引用类型,由于C++端使用的是指针,struct类型作为参数传递时,必须使用ref关键字进行引用传递,class则无需ref关键字。

UnityEngine.Vector3:Vector3的成员变量布局顺序是x->y->,在C++中定义相同的Vector3结构体后,便可直接将UnityEngine.Vector3作为参数传递,Quarternion同理。

四、数组的传递

1. 普通数组的传递

传递普通数组很简单,只需要注意同时将数组的长度作为参数传递即可。

//TestCppInterop.cs[DllImport("UnityCppInterop", EntryPoint = "Internal_ChangeArray")]private static extern int ChangeArray(int[] array, int size);void Start(){        
    int[] numArray = new[] { 1, 2, 3 };
    ChangeArray(numArray, numArray.Length);    for (var i = 0; i < numArray.Length; i++)
    {
        Debug.LogError(numArray[i]);
    }
}
//Bridge.hextern "C"{    UNITY_CPP_INTEROP_DLL_BRIDGE void Internal_ChangeArray(int* arr, int len);
}//Bridge.cppextern "C"{    void Internal_ChangeArray(int* arr, int len)
    {        for (int i = 0; i < len; i++)
        {
            arr[i]++;
        }
    }
}

276

ChangeArray.png

2. struct数组的传递

还是用上面的Person,同样需要传递数组长度。

//TestCppInterop.cs[DllImport("UnityCppInterop", EntryPoint = "Internal_ChangePersonArray")]private static extern int ChangePersonArray([In, Out]Person[] array, int size);void Start(){
    var p1 = new Person(){ Age = 11, Height = 133, Married = false, Scores = new[] { 66, 77} };
    var p2 = new Person() { Age = 22, Height = 177, Married = true, Scores = new[] { 88, 99 } };
    Person[] persons = new Person[2];
    persons[0] = p1;
    persons[1] = p2;
    ChangePersonArray(persons, 2);
    foreach (var person in persons)
    {
        Debug.LogError(person.Age);
        Debug.LogError(person.Height);
        Debug.LogError(person.Married);
        Debug.LogError(person.Scores[0]);
        Debug.LogError(person.Scores[1]);
        Debug.LogError("========================");
    }
}
//Bridge.hextern "C"{    UNITY_CPP_INTEROP_DLL_BRIDGE void Internal_ChangePersonArray(Person* arr, int len);
}//Bridge.cppextern "C"{    void Internal_ChangePersonArray(Person* arr, int len)
    {
        Person* curr = arr;        for (int i = 0; i < len; i++)
        {
            arr->Age += 10;
            arr->Height += 10;
            arr->Married = !arr->Married;
            arr->Scores[0] += 10;
            arr++;
        }
    }
}

324

ChangePersonArray.png

struct数组作为参数传递时需要使用[In, Out]特性,详见0详见1



作者:食不知味_夜不能寐
链接:https://www.jianshu.com/p/8877c1d3f2e2

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消