相同PublicKeyToken的不同版本Siemens.Engineering程序集并行使用问题
同强名标识Siemens.Engineering多版本共存实现方案
问题根因
CLR的程序集绑定规则明确:程序集名称、Culture、PublicKeyToken 三项一致时,无论版本号存在多大差异,都会被视为同一绑定目标,应用域全局仅允许加载单一份。所有当前遇到的异常均由该规则触发:
- 编译阶段MSBuild无法区分两个同标识的引用优先级,因此生成“随机选择版本”的冲突警告
- 运行时CLR受绑定规则限制,仅会匹配首个路径命中的主程序集,对应的关联依赖(如
Siemens.Engineering.Contract)也只会绑定到该版本的路径,跨版本调用时就会触发TypeLoadException - 当前App.config中配置的两个同
assemblyIdentity节点,CLR只会解析首个条目,第二个codeBase规则完全失效,无法实现双版本绑定。
落地实现步骤
1. 清理无效的配置
移除App.config中所有针对Siemens.Engineering的dependentAssembly绑定节点,这类同标识的配置不仅无法实现双版本加载,还会干扰CLR的正常绑定流程。
2. 拆分版本独立目录
不要将两个版本的主程序集及关联依赖直接放在程序输出根目录,按版本拆分独立子目录存放,避免文件冲突:
程序输出根目录/ ├─ 主程序.exe ├─ v15/ │ ├─ Siemens.Engineering.dll (15版本主程序集) │ └─ 所有15版本对应的关联依赖(含Siemens.Engineering.Contract.dll) └─ v16/ ├─ Siemens.Engineering.dll (16版本主程序集) └─ 所有16版本对应的关联依赖(含Siemens.Engineering.Contract.dll)
注意:每个版本目录必须存放该版本对应的全部依赖文件,禁止跨目录复用依赖。
3. 调整编译引用配置
保持extern alias的核心编译逻辑不变,按如下要求调整项目引用属性:
- 将15版本
Siemens.Engineering.dll引用的Aliases属性设置为Siemens_Engineering_15,取消默认的global别名 - 将16版本
Siemens.Engineering.dll引用的Aliases属性设置为Siemens_Engineering_16,取消默认的global别名 - 两个引用的
Copy Local(即项目文件中的Private)属性设置为False,避免编译时自动拷贝程序集到根目录造成文件冲突 - 两个引用的
SpecificVersion属性设置为True,让MSBuild基于完整版本号区分引用边界,消除编译阶段的版本冲突警告。
对应的csproj引用配置参考:
<Reference Include="Siemens.Engineering"> <HintPath>Lib\Siemens.Engineering.15.dll</HintPath> <Aliases>Siemens_Engineering_15</Aliases> <Private>False</Private> <SpecificVersion>True</SpecificVersion> </Reference> <Reference Include="Siemens.Engineering"> <HintPath>Lib\Siemens.Engineering.16.dll</HintPath> <Aliases>Siemens_Engineering_16</Aliases> <Private>False</Private> <SpecificVersion>True</SpecificVersion> </Reference>
4. 挂载自定义程序集解析逻辑
在程序启动入口(Main方法最开始)挂接AppDomain.CurrentDomain.AssemblyResolve事件,绕开CLR默认的单绑定规则,根据请求加载的程序集版本,从对应子目录加载匹配的文件:
using System; using System.Reflection; using System.IO; internal class Program { private static void Main(string[] args) { // 注册自定义解析事件,必须在所有业务代码执行前挂载 AppDomain.CurrentDomain.AssemblyResolve += OnCustomAssemblyResolve; // 正常使用extern alias声明对应版本的实例即可 var tiaInstance15 = new Siemens_Engineering_15::Siemens.Engineering.TiaPortal(...); var project15 = new Siemens_Engineering_15::Siemens.Engineering.Project(...); var tiaInstance16 = new Siemens_Engineering_16::Siemens.Engineering.TiaPortal(...); } private static Assembly OnCustomAssemblyResolve(object sender, ResolveEventArgs args) { var requestedName = new AssemblyName(args.Name); // 匹配所有Siemens.Engineering前缀的程序集(含主程序集、Contract依赖等) if (requestedName.Name.StartsWith("Siemens.Engineering")) { // 按程序集主版本号匹配对应存放目录 string targetDir = requestedName.Version.Major switch { 15 => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "v15"), 16 => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "v16"), _ => null }; if (targetDir == null) return null; string targetFilePath = Path.Combine(targetDir, $"{requestedName.Name}.dll"); return File.Exists(targetFilePath) ? Assembly.LoadFrom(targetFilePath) : null; } return null; } }
关键注意事项
- 自定义解析逻辑必须覆盖所有Siemens.Engineering相关的依赖程序集,不能仅解析主程序集,否则仍会触发
TypeLoadException - 不要尝试通过篡改PublicKeyToken的方式让两个版本成为独立标识的程序集,该操作会触发强名签名校验失败,也不符合西门子TIA库的授权规则
- 若两个版本的库依赖非托管本地组件,需要将对应版本的非托管文件同步存放到版本子目录,在初始化对应版本的实例前,通过
SetDllDirectoryAPI切换非托管dll的搜索路径,避免加载错版本的本地组件。
内容的提问来源于stack exchange,提问作者lolabenavides
相关产品推荐
相关产品推荐

