【Unity】调用c++代码
本文系统讲解了Unity与C++动态库交互的完整流程。主要内容包括:C++库的编写与编译(Windows DLL、Android SO、iOS A),Unity中通过P/Invoke机制调用C++库的方法,以及实现C++回调C#的双向通信机制。重点介绍了跨平台适配技巧、目录结构规范、调用约定选择、IL2CPP兼容性处理等关键技术点,并提供了内存管理、线程安全等常见问题的解决方案。通过本文的实战指南
文章目录
1. 前言
Unity引擎以C#为主,但出于性能、保密性、跨平台复用等需求,经常需要调用C++编写的动态库(Windows为.dll,Android为.so,iOS为.a)。通过P/Invoke(Platform Invoke)机制,Unity可实现C#与C++的双向调用。本文将系统讲解从C++库编写、编译到Unity调用、回调、跨平台适配的完整流程,并给出实战示例与常见问题解决方案。
2. 环境准备
- 开发工具:Visual Studio(C++编译)、Unity Hub、Unity Editor。
- 目标平台:Windows(.dll)、Android(.so)、iOS(.a)。
- 基础知识:了解C++导出、C# DllImport、IL2CPP、NDK等概念。
3. 编写C++动态库
3.1 C++头文件示例(ServerDLL.h)
#pragma once
#ifdef _WIN32
#define BUILD_DLL
#endif
#if defined(BUILD_DLL)
#define GAMEAPI extern "C" __declspec(dllexport)
#else
#define GAMEAPI extern "C"
#endif
GAMEAPI int _stdcall Hello(unsigned char* buff, int len);
GAMEAPI void _stdcall Start(int nid);
3.2 C++实现(Server.cpp)
#include "ServerDLL.h"
GAMEAPI int Hello(unsigned char* buff, int len) {
std::cout << buff << std::endl;
return 100;
}
GAMEAPI void Start(int nid) {
// 示例实现
}
说明:
extern "C"避免C++函数名修饰,__declspec(dllexport)用于Windows导出,Android平台使用__attribute__((visibility("default")))。
4. 编译生成动态库
- Windows:VS新建“动态链接库(DLL)”项目,编译生成
.dll。 - Android:使用NDK或Linux环境交叉编译,生成
libxxx.so。 - iOS:Xcode编译生成
.a静态库。
关键点:确保目标CPU架构(ARMv7、ARM64、x86)与Unity发布平台一致。
5. Unity中调用C++库
5.1 目录结构
将生成的库放入Unity工程Assets/Plugins/下对应平台目录:
Assets/
Plugins/
x86_64/ // Windows 64位
MyLib.dll
Android/
arm64-v8a/
libMyLib.so
iOS/
libMyLib.a
5.2 C#封装示例
using System.Runtime.InteropServices;
public class GameDLL {
#if UNITY_IOS
const string GAMEDLL = "__Internal";
#elif UNITY_EDITOR || UNITY_ANDROID
const string GAMEDLL = "MyLib";
#endif
[DllImport(GAMEDLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int Hello(byte[] buf, int len);
[DllImport(GAMEDLL, CallingConvention = CallingConvention.Cdecl)]
public static extern void Start(int x);
}
注意:Android调用时去掉
lib前缀和.so后缀;iOS使用__Internal。
6. 跨平台与调用约定
- CallingConvention:通常为
Cdecl(C++默认)或StdCall(Windows API)。 - 平台宏:使用
UNITY_ANDROID、UNITY_IOS等宏区分平台。 - IL2CPP:发布移动端时IL2CPP会将C#转为C++,此时C++接口需严格匹配。
7. C++回调C#(双向通信)
7.1 C++端(注册回调)
typedef void (*NativeCallback)(const char*);
std::map<std::string, NativeCallback> _callbackMap;
extern "C" {
GAMEAPI void RegisterNativeCallback(const char* functionName, NativeCallback callback) {
_callbackMap[std::string(functionName)] = callback;
}
GAMEAPI void UpdateNative() {
_callbackMap["Log"]("Native Log");
}
}
7.2 C#端(设置回调)
using AOT;
using System;
using System.Runtime.InteropServices;
public class TestCPPLib : MonoBehaviour {
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void NativeCallback(string args);
[DllImport("MyLib")]
static extern void RegisterNativeCallback(string functionName, IntPtr callback);
[MonoPInvokeCallback(typeof(NativeCallback))]
static void CallbackLog(string args) {
Debug.Log(args);
}
void Start() {
RegisterNativeCallback("Log", Marshal.GetFunctionPointerForDelegate(new NativeCallback(CallbackLog)));
}
}
说明:
MonoPInvokeCallback确保IL2CPP下回调可用。
8. 常见问题与注意事项
- DLLNotFoundException
- 检查库是否在正确目录、CPU架构是否匹配、文件名是否正确(去掉
lib和.so)。
- 检查库是否在正确目录、CPU架构是否匹配、文件名是否正确(去掉
- 符号找不到
- 确保C++函数用
extern "C"导出,避免C++名修饰。
- 确保C++函数用
- 内存管理
- C++分配的内存需在C++释放,避免跨语言内存泄漏。
- 线程安全
- 避免在非主线程调用Unity API,可使用
UnityMainThreadDispatcher等方案。
- 避免在非主线程调用Unity API,可使用
- 调试技巧
- 使用
Debug.Log、Profiler.BeginSample等在C++中输出日志。
- 使用
9. 参考资料
这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!
更多推荐


所有评论(0)