Unity C++原生插件必须用extern "C"导出函数以避免name mangling,且需严格匹配ABI、平台架构、C++标准及调用约定,内存管理须由C#主导。
Unity C++ 原生插件必须导出 C 风格函数
Unity 的原生插件机制只识别
导出的符号,C++ 编译器默认会做名字修饰(name mangling),导致
找不到函数。不加
,哪怕函数签名完全匹配,也会报
或
。
实操建议:
立即学习
“
C++免费学习笔记(深入)
”;
所有供 C# 调用的函数必须包在
块里
避免重载函数——C 风格导出不支持重载,同名函数只会保留一个符号
头文件中用
包裹,确保 C# 项目引用时不会误解析 C++ 特性
Windows 下推荐用
显式导出;macOS/Linux 用
,并编译时加
防止符号污染
Unity 支持的平台 ABI 和二进制格式要严格匹配
Unity 不会帮你做 ABI 兼容转换。比如在 Windows 上用 MSVC 2019 编译的 x64 DLL,就不能被 IL2CPP + UWP 构建的 ARM64 Unity Player 加载;macOS 上用 Clang 15 编译的 dylib 若启用了
,而 Unity Editor 自带的 libcpp 是 C++17 ABI,则可能在构造
时崩溃。
实操建议:
立即学习
“
C++免费学习笔记(深入)
”;
Windows:统一用 Visual Studio 2019(或 Unity 官方文档标注的版本),目标平台选
或
,与 Unity Player 架构一致
macOS:用 Xcode 自带 Clang,C++ 标准限定为
,禁用
等非标准扩展
Android:必须用 Unity 内置 NDK(如 r21e),且
,生成的
文件名要和
中写的完全一致(含
前缀和
后缀)
构建后用
(macOS/Linux)或
(Windows)检查目标架构是否匹配
C# DllImport 的路径、名称和调用约定不能写错
Unity 查找原生库依赖于平台规则,不是简单按文件名硬匹配。
在 Windows 上实际找的是
,macOS 找
,Android 找
。路径错误、大小写不一致、漏掉
前缀,都会直接触发
。
C知道
CSDN推出的一款AI技术问答工具
下载
实操建议:
立即学习
“
C++免费学习笔记(深入)
”;
把插件放在
下,并按子目录区分平台:
、
、
C# 中
的字符串只写“裸名”,不要带路径、扩展名或
前缀(例如写
,不是
)
显式指定
,避免 Windows 上因默认
导致栈不平衡
Android 上需在
或
中确保
(Unity 默认已设)
传参和内存管理必须由 C# 主导,C++ 插件不主动 new/delete 托管内存
C++ 插件里如果用
分配内存再传给 C#,或者试图
C# 传来的
,大概率触发崩溃或 GC 异常。Unity 的托管堆和本地堆完全隔离,跨边界的内存必须明确所有权归属。
实操建议:
立即学习
“
C++免费学习笔记(深入)
”;
基本类型(
、
、
)直接传值,安全
字符串统一用
输入,C# 侧用
;输出字符串则让 C++ 写入 C# 提前分配好的
缓冲区
数组传参优先用
+ 长度参数,C++ 用
访问,避免封送开销
绝对不要在 C++ 里调用
或释放 C# 传来的指针——这块内存生命周期归 GC 管
C++ 插件最常崩在 ABI 不一致和字符串/数组封送方式错配上,尤其当 Unity 升级到新 LTS 版本后,自带的 C++ 运行时可能变化,老插件不重新编译就直接失效。别信“编译一次,到处运行”。
extern "C"DllImportextern "C"DllNotFoundExceptionEntryPointNotFoundExceptionextern "C" { ... }#ifdef __cplusplus__declspec(dllexport)__attribute__((visibility("default")))-fvisibility=hidden-std=c++20std::stringx86_64x86-std=c++17-fblocksAPP_ABI := armeabi-v7a arm64-v8a.soDllImportlib.sofileDumpBin /headersDllImport("MyPlugin")MyPlugin.dlllibMyPlugin.dyliblibMyPlugin.solibDllNotFoundExceptionAssets/Plugins/Assets/Plugins/x86_64/Assets/Plugins/Android/Assets/Plugins/iOS/DllImportlib"MyPlugin""./Plugins/MyPlugin.dll"CallingConvention = CallingConvention.CdeclStdCallAndroidManifest.xmlgradleandroid:extractNativeLibs="true"newdeletebyte*intfloatboolconst char*[MarshalAs(UnmanagedType.LPStr)]StringBuilderIntPtrreinterpret_cast(ptr.ToPointer()) Marshal.AllocHGlobal