【LearnUnity·二】Unity C#结合C++动态链接库编程

Unity C#结合C++动态链接库编程

在使用Unity做应用开发过程中,可能会需要使用一些仅支持C++的库,或者因为底层使用C++具有更高的效率优化,而选择使用C#结合C++的方式开发。Untiy为此也提供了链接C++动态链接库的接口,方便开发者使用。

本人开发环境为:Window10,Visual Studio,Unity。

一、最简单的动态链接库使用

使用Visual Studio生成动态链接库的方法有很多,在此就不再赘述了。

1.使用VS创建一个工程TestDLL4Unity

工程包含以下文件:

  • Interface.h
#pragma once
#include "DLL_EXPORTS.h"

extern "C" DLL_EXPORT int Add(int a, int b);//注意extern "c" 是必要的,当需要的函数较多时,使用extern "c" { fun1,fun2,...}即可
  • Interface.cpp
#include "Interface.h"

DLL_EXPORT int Add(int a, int b)//注意这里的DLL_EXPORT是必要的
{
	return a + b;
}
  • DLL_EXPORTS.h
#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。

2.创建Unity工程TestDLL

在Assets下新建一个文件夹Plugins,并在其中建立两个子文件夹x86和x86_64:
在这里插入图片描述
分别将之前使用VS生成的x86和x64的TestDLL4Unity.dll放置到这两个文件夹(其他文件诸如*.lib,*.pdb并不需要)

在使用时,我在改Unity工程下建立了两个文件,Interface.cs和UpdateUI.cs。其中Interface.cs用来管理动态链接库接口的导入,UpdateUI.cs使用这些接口。

  • Interface.cs
using System.Runtime.InteropServices;

public class Interface
{
    [DllImport("TestDLL4Unity")]//链接动态链接库,TestDLL4Unity为库名,不需要加dll后缀
    public static extern int Add(int a, int b);//在导入动态链接库后,需要重新定义一个static extern的同名同参函数作为新接口
}
  • UseInterface.cs
using UnityEngine;
public class UseInterface : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        int a = Interface.Add(2, 3);
        print(a);
    }
}
3.运行结果

可以在Unity的Console中看到运行结果:
在这里插入图片描述

二、复杂数据的传递

简单的动态链接库很好理解和实现,但在实际的开发过程中 ,在C++程序中可能会使用到更多的数据类型,比如类,结构体,以及vector,list等各种容器等等。如果我们直接使用在接口上,会遇到Unity提示布局错误,无法正确链接。
出现这些问题的原因是C++和C#中各种类型,即使相似,也无法相互转换,如C++中的string和list等。因此我们需要在C++程序动态链接库导出接口之前,将需要导出的数据完全转换为最基本的数据类型,如int,float,char等。也就是说,我们需要人为的建立一个中间层,用来转换C++和C#中的各种复杂数据结构。

1.修改VS工程文件
  • TestClass.h
#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
};
  • TestClass.cpp
#include "TestClass.h"

//************************************************************************************
//Function:
const glm::vec3& CTestClass::getPos()
{
	return m_Pos;
}

//************************************************************************************
//Function:
void CTestClass::setPos(const glm::vec3& vPos)
{
	m_Pos = vPos;
}
  • Interface.h
#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);
}
  • Interface.cpp
#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));//数据转换
}
  • DLL_EXPORTS.h 同上未作改变

注:麻烦之处就在于每次更新底层,都需要重新替换Unity工程中的dll文件。

2.修改Unity工程文件

将新生成的dll文件分别替换给Unity工程。然后在Interface.cs中添加新的接口

  • 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);
}
  • UseInterface.cs
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);
    }
}
3.运行结果

在这里插入图片描述

三、字符串传递需要注意

在使用动态链接库时,需要注意的是,如果从Unity C#向底层C++经参数传递的数据为char*类型时,如果使用以上这种方法,会发现,只传递了首字符到底层,并没有将字符数组中所有字符传递过去。
解决方法就是,在链接动态链接库时,需要指定使用CharSet.Ansi。

[DllImport("VPLGenerator_C", CharSet = CharSet.Ansi)]
    public static extern void transportCharArray(char[] vCharArray);

如果需要了解更多关于DllImport的参数信息,有大佬整理了相关内容,有兴趣可以看理解DllImportAttribute中的属性

如有验证者需要源程序,可在此下载TestDLL4Unity

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值