HybridCLR 中 [MonoPInvokeCallback] 使用 string 参数导致的问题及解决方案

问题描述

运行报错:ExecutionEngineException: GetReversePInvokeWrapper fail. not find wrapper of method:System.IntPtr VideoScreen::CallBackTest(System.IntPtr)

在 Unity 项目中使用 HybridCLR 进行热更新时,若定义如下委托和回调方法:

public delegate string UnityCallBack(string s);

[MonoPInvokeCallback(typeof(UnityCallBack))]
public static string CallBackTest(string s)
{
    Debug.Log("debug:" + s);
    s += " Csharp";
    return s;
}

可能会遇到以下两类错误:

  1. 编辑器构建时错误
    MonoPInvokeCallback method System.String VideoScreen::CallBackTest(System.String) has unsupported parameter or return type.

  2. 运行时错误(若构建绕过编辑器检查)
    ExecutionEngineException: GetReversePInvokeWrapper fail. not find wrapper of method:System.String VideoScreen::CallBackTest(System.String)

即使按照建议将签名改为 IntPtr 后,仍可能遇到:

ExecutionEngineException: GetReversePInvokeWrapper fail. not find wrapper of method:System.IntPtr VideoScreen::CallBackTest(System.IntPtr)

根本原因

  • HybridCLR 的限制:对于标记了 [MonoPInvokeCallback] 的方法(反向 P/Invoke),不支持需要封送(marshaling)的托管类型,如 string、数组、类等。这些类型在 native 代码调用时,无法自动生成正确的包装器(wrapper)。
  • 包装器缺失:即使改为 IntPtr,若 HybridCLR 未正确为该方法生成反向 P/Invoke 包装器,运行时仍会找不到对应入口。

解决方案

1. 修改委托和方法签名

将委托和所有标记 [MonoPInvokeCallback] 的方法的参数和返回值改为 IntPtr

修改前:

public delegate string UnityCallBack(string s);

[MonoPInvokeCallback(typeof(UnityCallBack))]
public static string CallBackTest(string s) { ... }

修改后:

using System;
using System.Runtime.InteropServices;
using AOT;

public delegate IntPtr UnityCallBack(IntPtr s);

[MonoPInvokeCallback(typeof(UnityCallBack))]
public static IntPtr CallBackTest(IntPtr s)
{
    // 1. 将 native 字符串转为 C# string(假设 UTF-8 编码)
    string input = Marshal.PtrToStringUTF8(s);
    Debug.Log("debug:" + input);

    // 2. 业务处理
    string output = input + " Csharp";

    // 3. 将结果 string 转为 IntPtr(分配非托管内存)
    IntPtr result = Marshal.StringToCoTaskMemUTF8(output);
    return result;
}

2. 重要注意事项

  • 内存管理:C# 侧通过 Marshal.StringToCoTaskMemUTF8 分配的内存,必须由 native 调用方负责释放(例如调用 CoTaskMemFree)。若 native 端无法修改,需约定其他内存管理方式(如使用 Marshal.AllocHGlobal 配合 FreeHGlobal)。
  • 委托类型匹配[MonoPInvokeCallback] 中指定的委托类型必须与方法签名完全一致。

3. 重新生成反向 P/Invoke 包装器

修改签名后,必须手动触发 HybridCLR 的包装器生成:

  • 菜单栏:HybridCLR → Generate → ReversePInvokeWrapper(或 Generate All)。

生成成功后,检查输出目录(通常为 HybridCLRData/ReversePInvokeWrapper/)是否包含你的方法。

4. 清理旧构建缓存

旧的缓存可能导致新包装器未被使用,建议执行一次 Clean Build

  • Build Settings → 点击 Build 旁的下拉箭头 → 选择 Clean Build
  • 或手动删除 Library/BeeLibrary/PlayerDataCacheLibrary/il2cpp_cache 目录后重新构建。

5. 验证热更新程序集配置

确保你的热更新程序集已被 HybridCLR 的包装器生成器扫描到:

  • 打开 HybridCLR → SettingsReversePInvokeWrapper 选项卡,确认你的程序集已勾选。
  • 若未出现,可尝试重新导入程序集或检查程序集定义文件(Assembly Definition)的设置。

常见后续问题排查

问题:修改为 IntPtr 后仍然报 GetReversePInvokeWrapper fail

可能原因及解决步骤:

  1. 委托定义与签名不一致
    检查委托是否更新为 IntPtr,且 [MonoPInvokeCallback(typeof(YourDelegate))] 正确引用。

  2. 包装器未生成
    重复执行步骤 3,并查看生成日志是否包含你的方法名。

  3. 热更新程序集未被扫描
    确认程序集在 HybridCLR 设置中启用,或临时将方法移到 AOT 主工程测试(若移动后正常,则问题在热更新侧配置)。

  4. 缓存干扰
    执行步骤 4 的 Clean Build,或删除整个 Library 目录后重新导入项目(谨慎操作)。

总结

  • HybridCLR 的 [MonoPInvokeCallback] 不支持 string 等托管类型,必须使用 IntPtr 并手动转换字符串。
  • 修改签名后,务必重新生成反向 P/Invoke 包装器并清理缓存。
  • 注意内存管理约定,避免泄漏。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐