Unity Mod开发:预公开DLL时如何避免FieldAccessException?
解决预公开DLL引用导致的运行时FieldAccessException问题
问题根源
直接引用预公开DLL编译时,你的模组IL代码会把目标成员标记为public访问,但运行时游戏加载的是保留原私有成员权限的原始程序集。虽然Mono默认权限检查宽松,但静态构造函数(.cctor)这类场景下仍会触发严格校验,导致FieldAccessException。而BepInEx.AssemblyPublicizer.MSBuild除了生成公开DLL,还在编译阶段做了关键处理:仅用公开DLL通过编译校验,不复制到输出目录,同时配合BepInEx运行时钩子自动绕过权限检查。
不用MSBuild包的替代方案
你可以手动模拟MSBuild包的核心逻辑,分三步实现相同效果:
1. 配置编译引用,避免复制公开DLL到输出
修改项目csproj配置,确保预公开DLL仅用于编译,运行时加载游戏原DLL:
<!-- 引用预公开的游戏程序集 --> <Reference Include="$(GameSolutionFolder)GameAssemblyPublicizer\out\**\*.dll"> <Private>false</Private> <HintPath>%(FullPath)</HintPath> </Reference>
Private="false"会阻止VS将公开DLL复制到模组输出目录,确保运行时加载游戏自带的原DLL。
2. 运行时绕过私有成员权限检查
直接编译的IL会带着public访问标记,与原成员实际权限冲突,必须用BepInEx工具绕过检查:
方法一:用AccessTools直接访问(推荐)
放弃直接字段/方法调用,改用BepInEx内置的AccessTools类获取成员,这是最稳定的方式:
// 获取静态字段 var targetField = AccessTools.Field(typeof(GameClassName), "GameMemberName"); var fieldValue = targetField.GetValue(null); // 静态字段传null,实例字段传对应对象 // 调用私有方法 var targetMethod = AccessTools.Method(typeof(GameClassName), "GameMethodName"); targetMethod.Invoke(null, new object[] { param1, param2 });
方法二:动态添加InternalsVisibleTo特性(进阶)
如果想保留直接调用的语法,可以在模组启动时给原游戏程序集动态添加InternalsVisibleTo特性,让它信任你的模组:
private void Awake() { var gameAssembly = typeof(GameClassName).Assembly; var myAssemblyName = Assembly.GetExecutingAssembly().GetName().Name; // 反射获取程序集的自定义特性列表 var customAttrField = typeof(Assembly).GetField("m_CustomAttributes", BindingFlags.NonPublic | BindingFlags.Instance); var customAttrs = (List<CustomAttributeData>)customAttrField.GetValue(gameAssembly); // 添加InternalsVisibleTo特性 var ivtCtor = typeof(InternalsVisibleToAttribute).GetConstructor(new[] { typeof(string) }); customAttrs.Add(CustomAttributeData.GetCustomAttributeData( ivtCtor, new[] { CustomAttributeTypedArgument.FromConstant(myAssemblyName) } )); }
注意:此方法仅对internal成员有效,private成员仍需用AccessTools或Harmony修改可见性。
3. 确保自定义公开脚本的正确性
你的公开脚本必须完全对齐BepInEx.AssemblyPublicizer的逻辑:
- 将所有
private/internal的字段、方法、属性、嵌套类型改为public - 保留原程序集的名称、版本、强名称(如果有),避免运行时程序集不匹配
- 不要修改程序集的其他元数据(如文化信息、公钥)
关键注意事项
- 绝对不要把预公开DLL放到游戏目录或模组输出目录,必须依赖游戏自带的原DLL
- 如果游戏是IL2CPP编译的,Mono的权限检查会更严格,必须用
AccessTools或Harmony处理所有私有成员访问 - 尽量避免在静态构造函数中访问私有成员,这类场景最容易触发权限检查,建议移到模组的
Awake/Start方法中
内容的提问来源于stack exchange,提问作者Luke Vo
相关产品推荐
相关产品推荐

