项目所需,需要在unity中调用C++封装的dll,需要传递结构体数组。C++中封装的函数的参数为结构体指针。在网上找了一下博客,然后结合MSDN上的C#编程参考手册,找到了方法。
这里创建了一个.h文件,声明了很简单两个结构体和一个类,类中还声明了一个Vector6D的数组,没有使用更复杂的结构体。
//StructTest.h
#ifndef _STRUCTTEST_H_
#define _STRUCTTEST_H_
#include
#include
using namespace std;
//Vector3D结构体、指向Vector3D的指针、包含Vector3D结构体的结构体:Vector6D
typedef struct
{
int v_X;
int v_Y;
int v_Z;
} Vector3D;
typedef struct
{
Vector3D pos_X;
Vector3D pos_Y;
Vector3D pos_Z;
} Vector6D;
class StructTest
{
public:
Vector3D v3;
Vector6D v6;
Vector6D v6_arr[3];
StructTest();
~StructTest();
void setVector3D(int x, int y, int z);
void setVector6D(Vector3D *v_3); //传入的是Vector3D的指针,其实是一个数组
void setVector6D_Arr(Vector6D *v_6); //这里也一样
bool saveVector(); //这里将数据写入文件,可以比较直观的看到反馈
};
#endif // !_STRUCTTEST_H_
这里是对上面的定义(看着多,其实很简单)
//StructTest.cpp
#include "StructTest.h"
StructTest::StructTest()
{
//初始化v3
v3.v_X = rand() % 6;
v3.v_Y = rand() % 6;
v3.v_Z = rand() % 6;
//初始化v6
Vector3D temp3D;
temp3D.v_X = rand() % 11 + 10;
temp3D.v_Y = rand() % 11 + 10;
temp3D.v_Z = rand() % 11 + 10;
v6.pos_X = temp3D;
temp3D.v_X = rand() % 11 + 10;
temp3D.v_Y = rand() % 11 + 10;
temp3D.v_Z = rand() % 11 + 10;
v6.pos_Y = temp3D;
temp3D.v_X = rand() % 11 + 10;
temp3D.v_Y = rand() % 11 + 10;
temp3D.v_Z = rand() % 11 + 10;
v6.pos_Z = temp3D;
//初始化v6_arr(数组)
for (int i = 0; i < 3; i++)
{
Vector3D temp3D;
Vector6D temp6D;
temp3D.v_X = rand() % 11 + 20;
temp3D.v_Y = rand() % 11 + 20;
temp3D.v_Z = rand() % 11 + 20;
temp6D.pos_X = temp3D;
temp3D.v_X = rand() % 11 + 20;
temp3D.v_Y = rand() % 11 + 20;
temp3D.v_Z = rand() % 11 + 20;
temp6D.pos_Y = temp3D;
temp3D.v_X = rand() % 11 + 20;
temp3D.v_Y = rand() % 11 + 20;
temp3D.v_Z = rand() % 11 + 20;
temp6D.pos_Z = temp3D;
v6_arr[i] = temp6D;
}
}
StructTest::~StructTest()
{
}
void StructTest::setVector3D(int x, int y, int z)
{
v3.v_X = x;
v3.v_Y = y;
v3.v_Z = z;
}
void StructTest::setVector6D(Vector3D *v_3) //传Vector3D v_3[3]
{
v6.pos_X = v_3[0];
v6.pos_Y = v_3[1];
v6.pos_Z = v_3[2];
}
void StructTest::setVector6D_Arr(Vector6D *v_6) //传Vector6D v_6[3]
{
v6_arr[0] = v_6[0];
v6_arr[1] = v_6[1];
v6_arr[2] = v_6[2];
}
bool StructTest::saveVector()
{
ofstream ofs;
ofs.open("Struct.txt", ios::app); //用追加的方式打开文件,其他方式也没问题
if (!ofs.is_open())
{
return false;
}
ofs << "This is Vector3D:" << endl;
ofs << v3.v_X << endl;
ofs << v3.v_Y << endl;
ofs << v3.v_Z << endl;
ofs << "This is Vector6D:" << endl;
ofs << v6.pos_X.v_X << endl;
ofs << v6.pos_X.v_Y << endl;
ofs << v6.pos_X.v_Z << endl;
ofs << v6.pos_Y.v_X << endl;
ofs << v6.pos_Y.v_Y << endl;
ofs << v6.pos_Y.v_Z << endl;
ofs << v6.pos_Z.v_X << endl;
ofs << v6.pos_Z.v_Y << endl;
ofs << v6.pos_Z.v_Z << endl;
ofs << "This is Vector6D Array:" << endl;
for (int i = 0; i < 3; i++)
{
ofs << v6_arr[i].pos_X.v_X << endl;
ofs << v6_arr[i].pos_X.v_Y << endl;
ofs << v6_arr[i].pos_X.v_Z << endl;
ofs << v6_arr[i].pos_Y.v_X << endl;
ofs << v6_arr[i].pos_Y.v_Y << endl;
ofs << v6_arr[i].pos_Y.v_Z << endl;
ofs << v6_arr[i].pos_Z.v_X << endl;
ofs << v6_arr[i].pos_Z.v_Y << endl;
ofs << v6_arr[i].pos_Z.v_Z << endl;
}
ofs << "-----END-----" << endl;
ofs.close();
return true;
}
然后建立一个.cpp文件,用于定义Dll的封装函数。而且这里涉及到类的封装,所以这里采用“拆解”的方法去封装需要调用的类,就是把类的方法在类外用封装函数来调用。然后生成即可,注意我这里由于要放在Unity中,所以采用的是配置是:Release,平台是x64。这里生成和调用的配置与平台要一致。
#include "StructTest.h"
#define DLLEXPORT _declspec(dllexport) //这个是关键,用此标识符声明的函数是表示从dll导出
//新建函数,使用这些函数来代替类的函数,但是类还是那个类
//将类的方法拆开来,用别的函数来调用,但是具体的实现还是类本身定义的方法实现
StructTest* st = nullptr; //在dll中实例化一个类的对象
extern "C" //这个也是关键,我理解的是表示用C语言的方式编译
{
//声明 为了规范,其实应该还需要声明一个函数用来释放指针和内存
DLLEXPORT bool GenerateStruct();
DLLEXPORT void setVector3D(int x, int y, int z);
DLLEXPORT void setVector6D(Vector3D* v_3);
DLLEXPORT void setVector6D_Arr(Vector6D* v_6);
DLLEXPORT bool saveVector();
//定义,可以放在extern “C”外
DLLEXPORT bool GenerateStruct()
{
st = new StructTest();
if (rs == nullptr)
{
return false;
}
return true;
}
DLLEXPORT void setVector3D(int x, int y, int z)
{
st->setVector3D(x, y, z);
}
DLLEXPORT void setVector6D(Vector3D* v_3)
{
st->setVector6D(v_3);
}
DLLEXPORT void setVector6D_Arr(Vector6D* v_6)
{
st->setVector6D_Arr(v_6);
}
DLLEXPORT bool saveVector()
{
return st->saveVector();
}
}
然后是Unity中,在Assets下创建一个目录“Scripts”用于存放C#脚本;再创建一个目录“Plugins”用于存放.dll文件,将刚刚生成的dll文件直接拖进来即可。
然后再这个C#文件TestDll_03.cs中:因为C#属于托管语言,C++是非托管,两者在交互时涉及一些底层的东西,尤其时对待字符类型数据的时候,涉及到编码方式之类的,所以要在托管内存和非托管内存之间进行交互需要用到一个类:Marshall,具体详细使用可以参考官方文档。
这几篇博客对我帮助很大:C#引用c++DLL结构体数组注意事项(数据发送与接收时)
C#调用C++DLL传递结构体数组的终极解决方案
C#结构体指针的定义及使用详解
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices; //这个是关键,想使用DLL就需要引进来
using System;
//使用函数来代替类的相关方法,类还是那个类
public class TestDll_03 : MonoBehaviour
{
[StructLayout(LayoutKind.Sequential)] //这个是关键,在官方文档中为StructLayoutAttribute,作用是允许你控制内存中类或结构体的数据字段的物理布局。
public struct Vector3D
{
public int x, y, z;
}
[StructLayout(LayoutKind.Explicit)] //这个的具体使用可以参考官方文档,不同参数表示不同的对齐方式
public struct Vector6D //当结构体含有字符串,字符数组等类型时,这里特别关键,采用不同的参数,来严格设置内存布局
{
[FieldOffset(0)] public Vector3D pos_X;
[FieldOffset(12)] public Vector3D pos_Y;
[FieldOffset(24)] public Vector3D pos_Z;
}
//声明需要从dll中导入的函数,我的dll名称为:TestStructure_03_02。这个DllImport的使用也有很多参数,在官方文档中可以查到
[DllImport("TestStructure_03_02")]
public static extern bool GenerateStruct();
[DllImport("TestStructure_03_02")]
public static extern void setVector3D(int x, int y, int z);
[DllImport("TestStructure_03_02")]
public static extern void setVector6D(IntPtr v_3); //注意,我这里采用的时IntPtr,好像用普通指针*的时候说什么unsafe啥的我不太懂。
[DllImport("TestStructure_03_02")]
public static extern void setVector6D_Arr(IntPtr v_6);
[DllImport("TestStructure_03_02")]
public static extern bool saveVector();
private bool st; //用来表示创建一个StructTest是否成功
void Start() //进行初始化
{
//初始化数组
//定义数组元素个数
int count = 3;
//计算两个结构体的大小
int size_3D = Marshal.SizeOf(typeof(Vector3D));
int size_6D = Marshal.SizeOf(typeof(Vector6D));
//用Marshal分配两个结构体数组空间(托管->非托管)
//这里也是很关键的地方:Marshal类:提供了一个方法集合,这些方法用于分配非托管内存、
//复制非托管内存块、将托管类型转换为非托管类型,
//此外还提供了在与非托管代码交互时使用的其他杂项方法
IntPtr ptr_3DArray = Marshal.AllocHGlobal(count * size_3D);
IntPtr ptr_6DArray = Marshal.AllocHGlobal(count * size_6D);
//创建两个结构体数组
Vector3D[] v3Array = new Vector3D[count];
Vector6D[] v6Array = new Vector6D[count];
//初始化v3Array
for(int i = 0; i < count; i++)
{
v3Array[i].x = 120 + (i + 1) * 5;
v3Array[i].y = 130 + (i + 1) * 5;
v3Array[i].z = 140 + (i + 1) * 5;
}
//初始化v6Array
for (int i = 0; i < count; i++)
{
Vector3D[] temp3D = new Vector3D[count];
for(int j = 0; j < count; j++)
{
temp3D[j].x = 150 + (j + i + 1) * 5;
temp3D[j].y = 160 + (j + i + 1) * 5;
temp3D[j].z = 170 + (j + i + 1) * 5;
}
v6Array[i].pos_X = temp3D[0];
v6Array[i].pos_Y = temp3D[1];
v6Array[i].pos_Z = temp3D[2];
}
//将初始化完毕的结构体数组v3Array传入开辟好的非托管内存块中(ptr_3DArray指向的)
for (int i = 0; i < count; i++)
{
//这里要特别注意,因为是一块连续的内存空间,但是指针指向的只是开头,所以指针也要按照结构体大小递增dddd
IntPtr tempPtr = (IntPtr)((UInt32)ptr_3DArray + i * size_3D);
Marshal.StructureToPtr(v3Array[i], tempPtr, false); //这个StructureToPtr很关键!作用是:将数据从托管对象(C#)封送到非托管内存块(C++)中,与之相对的是PtrToStructure。
}
//将初始化完毕的结构体数组v6Array传入开辟好的非托管内存块中(ptr_6DArray指向的)
for (int i = 0; i < count; i++)
{
IntPtr tempPtr = (IntPtr)((UInt32)ptr_6DArray + i * size_6D);
Marshal.StructureToPtr(v6Array[i], tempPtr, false);
}
//创建StructTest类,并进行初始化(DLL中进行的)
this.st = GenerateStruct();
if (st)
{
Debug.Log("StructTest创建成功");
}
else
{
Debug.Log("StructTest创建失败");
}
//保存数据至Struct.txt中
bool save = saveVector();
if (save)
{
Debug.Log("StructTest初始化数据保存成功");
}
else
{
Debug.Log("StructTest初始化数据保存失败");
}
//设置Vector3D,只是简单的传递int型数据
setVector3D(120, 130, 140);
//设置Vector6D,传递的是Vector3D结构体类型的数组的指针,这个指针现在指向的是Marshall开辟出来的非托管内存,存储着Vector3D结构体类型的数组
setVector6D(ptr_3DArray);
//设置Vector6D_Arr,传递的是Vector6D结构体类型的数组的指针,这个指针现在指向的是Marshall开辟出来的非托管内存,存储着Vector6D结构体类型的数组
setVector6D_Arr(ptr_6DArray);
save = saveVector();
if (save)
{
Debug.Log("StructTest修改后数据保存成功");
}
else
{
Debug.Log("StructTest修改后数据保存失败");
}
//记得释放内存啊
Marshal.FreeHGlobal(ptr_6DArray);
Marshal.FreeHGlobal(ptr_3DArray);
}
void Update()
{
}
}
//这里还提一嘴,我这里只涉及到从C#中传结构体数组指针到DLL中去,还有一种是接受DLL中的结构体数组,这里不同的就是要使用与上面StructureToPtr相对的PtrToStructure,作用是:将数据从非托管内存块封送到托管对象。
//定义完结构体VGAStat entries后,就可将接收到的C#结构体指针(用Marshall分配出来的)转换为定义的结构体对象。
VGAStat entries = (VGAStat)Marshal.PtrToStructure(iptr, typeof(VGAStat));
//iptr为接收到的非托管的结构体指针。
脚本创建完后,将其拖到一个object上,然后点击运行即可。最后可以在Unity项目的文件夹下发现Struct.txt文件: