赞
踩
在使用Unity做应用开发过程中,可能会需要使用一些仅支持C++的库,或者因为底层使用C++具有更高的效率优化,而选择使用C#结合C++的方式开发。Untiy为此也提供了链接C++动态链接库的接口,方便开发者使用。
本人开发环境为:Window10,Visual Studio,Unity。
使用Visual Studio生成动态链接库的方法有很多,在此就不再赘述了。
工程包含以下文件:
#pragma once
#include "DLL_EXPORTS.h"
extern "C" DLL_EXPORT int Add(int a, int b);//注意extern "c" 是必要的,当需要的函数较多时,使用extern "c" { fun1,fun2,...}即可
#include "Interface.h"
DLL_EXPORT int Add(int a, int b)//注意这里的DLL_EXPORT是必要的
{
return a + b;
}
#pragma once
#ifndef DLL_EXPORTS//需要在预处理器添加DLL_EXPORTS,以使用__declspec(dllexport)分支
#define DLL_EXPORT __declspec(dllimport)
#else
#define DLL_EXPORT __declspec(dllexport)
#endif
在生成动态链接库时,同时生成x86和x64两个版本,这样我们就得到了两个版本的TestDLL4Unity.dll。
在Assets下新建一个文件夹Plugins,并在其中建立两个子文件夹x86和x86_64:
分别将之前使用VS生成的x86和x64的TestDLL4Unity.dll放置到这两个文件夹(其他文件诸如*.lib,*.pdb并不需要)
在使用时,我在改Unity工程下建立了两个文件,Interface.cs和UpdateUI.cs。其中Interface.cs用来管理动态链接库接口的导入,UpdateUI.cs使用这些接口。
using System.Runtime.InteropServices;
public class Interface
{
[DllImport("TestDLL4Unity")]//链接动态链接库,TestDLL4Unity为库名,不需要加dll后缀
public static extern int Add(int a, int b);//在导入动态链接库后,需要重新定义一个static extern的同名同参函数作为新接口
}
using UnityEngine;
public class UseInterface : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
int a = Interface.Add(2, 3);
print(a);
}
}
可以在Unity的Console中看到运行结果:
简单的动态链接库很好理解和实现,但在实际的开发过程中 ,在C++程序中可能会使用到更多的数据类型,比如类,结构体,以及vector,list等各种容器等等。如果我们直接使用在接口上,会遇到Unity提示布局错误,无法正确链接。
出现这些问题的原因是C++和C#中各种类型,即使相似,也无法相互转换,如C++中的string和list等。因此我们需要在C++程序动态链接库导出接口之前,将需要导出的数据完全转换为最基本的数据类型,如int,float,char等。也就是说,我们需要人为的建立一个中间层,用来转换C++和C#中的各种复杂数据结构。
#pragma once #include <vector> #include <glm/glm.hpp> class CTestClass { public: CTestClass() = default; ~CTestClass() = default; const glm::vec3& getPos(); void setPos(const glm::vec3& vPos); private: glm::vec3 m_Pos;//glm::vec3包含3个float };
#include "TestClass.h"
//************************************************************************************
//Function:
const glm::vec3& CTestClass::getPos()
{
return m_Pos;
}
//************************************************************************************
//Function:
void CTestClass::setPos(const glm::vec3& vPos)
{
m_Pos = vPos;
}
#pragma once #include "DLL_EXPORTS.h" #include "TestClass.h" struct SVector3//用来将glm::vec3解析为仅包含最基本的float结构 { float x; float y; float z; }; extern "C" { DLL_EXPORT int Add(int a, int b); DLL_EXPORT CTestClass* createTestClass(); DLL_EXPORT SVector3 getPos(CTestClass* vTestClass); DLL_EXPORT void setPos(CTestClass* vTestClass, const SVector3& vPos); }
#include "Interface.h" //************************************************************************************ //Function: DLL_EXPORT int Add(int a, int b) { return a + b; } //************************************************************************************ //Function: DLL_EXPORT CTestClass* createTestClass() { return new CTestClass(); } //************************************************************************************ //Function: DLL_EXPORT SVector3 getPos(CTestClass* vTestClass) { glm::vec3 Pos = vTestClass->getPos(); SVector3 v;//数据转换 v.x = Pos.x; v.y = Pos.y; v.z = Pos.z; return v; } //************************************************************************************ //Function: DLL_EXPORT void setPos(CTestClass* vTestClass, const SVector3& vPos) { vTestClass->setPos(glm::vec3(vPos.x, vPos.y, vPos.z));//数据转换 }
注:麻烦之处就在于每次更新底层,都需要重新替换Unity工程中的dll文件。
将新生成的dll文件分别替换给Unity工程。然后在Interface.cs中添加新的接口
using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Runtime.InteropServices; using System; public struct SVector3//需要定义相同的仅包含最基本类型的结构体 { public float x; public float y; public float z; }; public class Interface { [DllImport("TestDLL4Unity")] public static extern int Add(int a, int b); [DllImport("TestDLL4Unity")] public static extern IntPtr createTestClass();//添加新的接口,其中IntPtr被包含在System中,可以用来保存指针 [DllImport("TestDLL4Unity")] public static extern SVector3 getPos(IntPtr vTestClass); [DllImport("TestDLL4Unity")] public static extern void setPos(IntPtr vTestClass, SVector3 vPos); }
using UnityEngine; using System; public class UseInterface : MonoBehaviour { // Start is called before the first frame update void Start() { int a = Interface.Add(2, 3); print(a); IntPtr TestClass = Interface.createTestClass(); SVector3 Pos = new SVector3(); Pos.x = 1; Pos.y = 0; Pos.z = 2; Interface.setPos(TestClass, Pos); SVector3 PosGet = Interface.getPos(TestClass); Vector3 p = new Vector3(PosGet.x, PosGet.y, PosGet.z); print(p); } }
在使用动态链接库时,需要注意的是,如果从Unity C#向底层C++经参数传递的数据为char*类型时,如果使用以上这种方法,会发现,只传递了首字符到底层,并没有将字符数组中所有字符传递过去。
解决方法就是,在链接动态链接库时,需要指定使用CharSet.Ansi。
[DllImport("VPLGenerator_C", CharSet = CharSet.Ansi)]
public static extern void transportCharArray(char[] vCharArray);
如果需要了解更多关于DllImport的参数信息,有大佬整理了相关内容,有兴趣可以看理解DllImportAttribute中的属性。
如有验证者需要源程序,可在此下载TestDLL4Unity。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。