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_ANDROIDUNITY_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. 常见问题与注意事项

  1. DLLNotFoundException
    • 检查库是否在正确目录、CPU架构是否匹配、文件名是否正确(去掉lib.so)。
  2. 符号找不到
    • 确保C++函数用extern "C"导出,避免C++名修饰。
  3. 内存管理
    • C++分配的内存需在C++释放,避免跨语言内存泄漏。
  4. 线程安全
    • 避免在非主线程调用Unity API,可使用UnityMainThreadDispatcher等方案。
  5. 调试技巧
    • 使用Debug.LogProfiler.BeginSample等在C++中输出日志。

9. 参考资料

构建适用于 iOS 的插件
适用于 Android 的原生 (C++) 插件

Logo

这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!

更多推荐