主要是想学习一下,Unity下的c++、c#混合编程。
首先是跟着这位大佬的博客:
准备工作
1.新建一个C++空项目
右键项目,打开属性一栏,设置好输出目录以及生成目标类型。(注意x86和x64的生成目录有差异
添加名为DllInterface的.h头文件和.cpp文件
2. 新建一个Unity空项目
打开Unity创建一个空项目,添加一个Main.cs的MonoBehaviour脚本作为程序入口,再添加一个DllInterface.cs空类作为接口调用。
代码编写
1.C#调用C++
假设有这么一个需求:我想通过让C#调用C++的接口计算两个物体之间的平面距离(xy坐标系)。
首先,我们在C++项目DllInterface.h头文件中添加如下代码
#pragma once
#include<math.h>
#include<string.h>
#include<iostream>
#define _DllExport _declspec(dllexport) //使用宏定义缩写下
extern "C"
{
float _DllExport GetDistance(float x1, float y1, float x2, float y2);
}
其中 _declspec(dllexport) 用于将该函数标记为导出函数。extern "c" 是让该区域的代码作为C语言来编译,避免C++编译时因函数重载令函数名改变而导致C#调用的时候找不到该函数。
在DllInterface.cpp文件添加GetDistance函数的实现。‘
float GetDistance(float x1, float y1, float x2, float y2)
{
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
然后在DllInterface.cs文件中添加如下代码
using System;
using System.Runtime.InteropServices;
using UnityEngine;
public class DllInterface {
[DllImport("CppInterface")]
public static extern float GetDistance(float x1, float y1, float x2, float y2);
}
其中DllImport属性用于标记调用C++的Dll中与该C#函数同名的函数。
在Main.cs中添加如下代码
using UnityEngine;
public class Main : MonoBehaviour {
private GameObject cube1;
private GameObject cube2;
// Use this for initialization
void Start () {
cube1 = GameObject.Find("Cube1");
cube2 = GameObject.Find("Cube2");
PrintDistanceViaUnity();
}
void PrintDistanceViaUnity()
{
var pos1 = cube1.transform.position;
var pos2 = cube2.transform.position;
Debug.Log("This is a log from Unity");
Debug.Log("Distance:" + DllInterface.GetDistance(pos1.x, pos1.y, pos2.x, pos2.y));
}
}
新建一个空场景,新建两个立方体命名为Cube1和Cube2,再新建一个空物体命名为Main并将Main.cs脚本挂载在该物体上。
右键C++的解决方案,生成Dll到Unity对应的目录中。
(当然,如果你开心也可以搞一个x64的。毕竟现在基本都是64位系统了。:)
注意:如果Unity已经在运行并且Dll已经存在,那么新的Dll写入生成会失败,此时需要关掉Unity再重新生成。
成功后点击运行按钮,可以看到输出如下,说明成功调用了C++的距离计算函数。
这里暂停说一下,Unity怎么调试c++:
1、在vs下,调试->附加到进程->unity.exe
2.在unity下点击Run,一般是可以进入断点的。
但是!!!!!!!!!!!!!!!!!@@@@@@@@@@@@@@@!!!!!!!!!!!!!!!!!!~~~~~~~~~~~~~~~
我不知道读者到这里,你是否可以进入断点,反正我搞了一个晚上,它都没进入断点,后来我又设置了这些,终于进了。虽然不清楚什么原因,但是苍天啊!!!!!!!肝疼。。。
参考的是这个博客的设置:
当然有些朋友没有这些设置,也很顺利的进入到了断点。顺便吐槽一下,看来官网倒开始逼不让用vs2015,各种不方便。
好了,我们愉快的往下进行。
2.C++调用C#
又比如这么一个需求:我想将C++的一些数据日志输出到Unity的控制台中方便查看信息和调试。
简单来看,就是将C#的函数引用传递给C++保存起来,然后C++通过函数指针调用C#。
修改DllInterface.h头文件的代码,如下
#pragma once
#include<math.h>
#include<string.h>
#include<iostream>
#define _DllExport _declspec(dllexport)
#define UnityLog(acStr) char acLogStr[512] = { 0 }; sprintf_s(acLogStr, "%s",acStr); Debug::Log(acLogStr,strlen(acStr));
extern "C"
{
//C++ Call C#
class Debug
{
public:
static void (*Log)(char* message,int iSize);
};
// C# call C++
void _DllExport InitCSharpDelegate(void (*Log)(char* message, int iSize));
float _DllExport GetDistance(float x1, float y1, float x2, float y2);
}
修改DllInterface.cpp文件的代码,如下
#include "DllInterface.h"
void(*Debug::Log)(char* message, int iSize);
float GetDistance(float x1, float y1, float x2, float y2)
{
UnityLog("GetDistance has been called");
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
void InitCSharpDelegate(void(*Log)(char* message, int iSize))
{
Debug::Log = Log;
UnityLog("Cpp Message:Log has initialized");
}
修改DllInterface.cs文件的代码,如下
using AOT;
using System;
using System.Runtime.InteropServices;
using UnityEngine;
public class DllInterface {
[DllImport("CppInterface")]
public static extern float GetDistance(float x1, float y1, float x2, float y2);
public delegate void LogDelegate(IntPtr message, int iSize);
[DllImport("CppInterface")]
public static extern void InitCSharpDelegate(LogDelegate log);
//C# Function for C++'s call
[MonoPInvokeCallback(typeof(LogDelegate))]
public static void LogMessageFromCpp(IntPtr message, int iSize)
{
Debug.Log(Marshal.PtrToStringAnsi(message, iSize));
}
}
最后修改Main.cs的代码
using UnityEngine;
public class Main : MonoBehaviour {
private GameObject cube1;
private GameObject cube2;
// Use this for initialization
void Start () {
cube1 = GameObject.Find("Cube1");
cube2 = GameObject.Find("Cube2");
//pass C#'s delegate to C++
DllInterface.InitCSharpDelegate(DllInterface.LogMessageFromCpp);
PrintDistanceViaUnity();
}
void PrintDistanceViaUnity()
{
var pos1 = cube1.transform.position;
var pos2 = cube2.transform.position;
Debug.Log("This is a log from Unity");
Debug.Log("Distance:" + DllInterface.GetDistance(pos1.x, pos1.y, pos2.x, pos2.y));
}
}
关掉unity重新生成C++的Dll,成功后再打开Unity项目运行场景,可以看到如 下打印,说明C++成功调用了Unity的Log接口将信息打印了出来
这里的代码可能有点难理解,但是奥利给,别怂,听博主我细细道来~
1、首先你得理解c#的delegate(托管),一点都不难,你大可以认为c#的托管和c++的函数指针一样,下面就是我 “人美路子野 “ 的白话文解释,可能不是很专业。
关于2的解释,也可以翻译为,或者说,LogMessageFromCpp这个函数托管到了LogDelegate这个函数指针上(可能在内部实现的时候,就是LogDelegate这个函数指针直接指向LogMessageFromCpp这个函数块了吧)
ok我觉得c#这里我已经解释的很清楚了,如果你不懂那就多看几遍,一定能看懂我在bb啥。
2、再来理解c++这里的代码,这里难度比c#大点。首先你得明白函数指针(恩···),很简单,指向函数的指针。其次你得理解宏定义,其实就是替换(恩...
)。
抱歉,上面的红字,表示的宏定义被替换以后,是:
char acLogStr[521] = {0};sprintf_s(acLogStr,"%s","aaa");
Debug::Log(acLogStr,strlen("aaa");
这里刚开始觉得cpp文件中,那个最上面的函数
void(*Debug::Log)(char* message,int iSize);
不用写,所以把它注释了,实验了一下,发现???难道静态函数除了在.h声明上,还要在.cpp定义上?和静态成员变量一样?话说.h和.cpp的作用是啥来着?
为了验证我的猜想,我把这个静态成员函数在cpp中的形式改成了:
然后我发现编译成功了。所以我懂了,就是应该这样声明、定义。
唉,边记边忘,好蛋疼。
附录:
1、extern “c"
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。
具体参考该博客。