英文:
Assembly loaded at runtime throwing manifest mismatch exception
问题
我需要在C#应用程序中动态定义外部程序集的来源(引用已添加到项目,但将"Copy Local"设置为false)。
如果我在App.config文件中添加引用的路径,一切都能正常工作:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Siemens.Engineering" culture="neutral" publicKeyToken="d29ec89bac048f84"/>
<codeBase version="18.0.0.0" href="FILE://C:\Program Files\Siemens\Automation\Portal V18\PublicAPI\V18\Siemens.Engineering.dll"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
这个方法可以正常工作,我可以无问题地使用这个库。
然而,我需要动态加载引用(位置可能会在运行时更改,或者我可能需要使用不同但兼容的版本)。为了做到这一点,我从App.config中删除了引用,并注册了一个回调到AppDomain.CurrentDomain.AssemblyResolve事件(当程序集解析失败时触发)。然后,在这个回调中,我动态加载程序集。在下面的示例中,我加载的是与App.config中的引用完全相同的引用:
static void Main(string[] args)
{
var loadedReferences = AppDomain.CurrentDomain.GetAssemblies();
AppDomain.CurrentDomain.AssemblyResolve += MyResolver;
doStuff();
}
private static Assembly MyResolver(object sender, ResolveEventArgs args)
{
String assemblyName = "C:\\Program Files\\Siemens\\Automation\\Portal V18\\PublicAPI\\V18\\Siemens.Engineering.dll";
Assembly thisA = Assembly.LoadFrom(assemblyName);
return thisA;
}
我可以在调试器中看到正确的程序集被加载。然而,当我尝试调用该库中的任何方法时,我会收到以下关于不匹配清单的错误:
FileLoadException: Could not load file or assembly 'Siemens.Engineering.Contract, Version=1800.100.4201.1, Culture=neutral, PublicKeyToken=37a18b206f7724a6' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
FileLoadException: The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
我真的不明白,因为我正在加载相同的DLL。任何提示都将不胜感激!
英文:
I need to dynamically define the source for an external assembly in a C# application (the reference is added to the project, but with copy local set to false).
If I add the path for the reference in the App.config file everything works fine:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Siemens.Engineering" culture="neutral" publicKeyToken="d29ec89bac048f84"/>
<codeBase version="18.0.0.0" href="FILE://C:\Program Files\Siemens\Automation\Portal V18\PublicAPI\V18\Siemens.Engineering.dll"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
This works properly and I can user the library without issue.
However, I need to load the reference dynamically (the location might change at runtime or I might need to use different but compatible version). In order to do this I delete the reference from App.config and register a callback to the AppDomain.CurrentDomain.AssemblyResolve event (which triggers when a assembly resolution fails). Then, in this callback, I dynamically load the assembly. In the example below I am loading the same exact reference as the one I had in App.config:
static void Main(string[] args)
{
var loadedReferences = AppDomain.CurrentDomain.GetAssemblies();
AppDomain.CurrentDomain.AssemblyResolve += MyResolver;
doStuff();
}
private static Assembly MyResolver(object sender, ResolveEventArgs args)
{
String assemblyName = "C:\\Program Files\\Siemens\\Automation\\Portal V18\\PublicAPI\\V18\\Siemens.Engineering.dll";
Assembly thisA = Assembly.LoadFrom(assemblyName);
return thisA;
}
I can see in the debugger that the proper assembly is being loaded. However, when I try to call any method from that library I get the following error about a mismatched manifest:
> FileLoadException: Could not load file or assembly 'Siemens.Engineering.Contract, Version=1800.100.4201.1, Culture=neutral, PublicKeyToken=37a18b206f7724a6' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
>
> FileLoadException: The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
I really don't understand it, given that I am loading the same DLL. Any hints appreciated!
答案1
得分: 0
正如有人指出的那样,问题在于当装配件加载失败并触发OnResolve时,我没有检查实际的装配件是哪一个,并且总是加载相同的装配件。解决方案是简单地检查事件参数中冒犯装配件的名称,并相应地采取措施。
有人在评论中提到,所以我将添加我的实现。在我的特殊情况下,我有一个允许用户选择要加载的dll版本并注册AssemblyResolve回调的方法:
public static void RegisterSiemensAssemblies(string tiaPortalVersion)
{
// ** 解析西门子Openness库的装配件路径
if (String.IsNullOrEmpty(tiaPortalVersion))
{
TiaWrapperLib.Utils.Resolver.SetAssemblyPaths("15.0", "15.0.0.0"); // 默认加载Tia Portal v15的DLL,Openness v15
}
else
{
if (tiaPortalVersion == "18.0")
TiaWrapperLib.Utils.Resolver.SetAssemblyPaths("18.0", "18.0.0.0"); // 如果TIA Portal 18,则加载Openness v18
else if (tiaPortalVersion == "14.0" || tiaPortalVersion == "15.0" || tiaPortalVersion == "15.1" || tiaPortalVersion == "16.0" || tiaPortalVersion == "17.0")
TiaWrapperLib.Utils.Resolver.SetAssemblyPaths(tiaPortalVersion, "15.0.0.0"); // IF其他TIA Portal加载Openness v15
else
TiaWrapperLib.Utils.Resolver.SetAssemblyPaths("15.0", "15.0.0.0"); // 如果版本无效,则从TIA Portal v15加载Openness v15
}
AppDomain.CurrentDomain.AssemblyResolve += TiaWrapperLib.Utils.Resolver.OnResolve; // 添加回调以在找不到DLL时实时解析西门子装配件
}
然后回调函数设置正确的装配件:
public static Assembly OnResolve(object sender, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name);
string path = "";
// 如果不解析西门子装配件
if (!assemblyName.Name.EndsWith("Siemens.Engineering")
&& !assemblyName.Name.EndsWith("Siemens.Engineering.Hmi"))
return null;
// 初始化最新版本的装配件路径
if (AssemblyPath == null || AssemblyPathHmi == null)
{
var tiaVersion = GetEngineeringVersions().Last();
var opennessVersion = GetOpennessVersions(tiaVersion).Last();
SetAssemblyPaths(tiaVersion, opennessVersion);
}
if (assemblyName.Name.EndsWith("Siemens.Engineering"))
path = AssemblyPath;
if (assemblyName.Name.EndsWith("Siemens.Engineering.Hmi"))
path = AssemblyPathHmi;
if (string.IsNullOrEmpty(path) || !File.Exists(path))
{
throw new FileLoadException($"无法从路径'{path}'加载装配件'{assemblyName}'。");
}
return Assembly.LoadFrom(path);
}
请注意,我只提供了代码的翻译,不包括问题或其他内容。
英文:
As it was pointed out to me, the problem is that when an assembly failed to load and triggered the OnResolve I wasn't checking which actual assembly it was, and was always loading the same one. The solution is to simply check the name of the offending assembly in the event argument and act accordingly.
Someone asked in the comments so I'll add my implementation. In my particular case I have a method which allows the user to select which version of the dll to load and registers the AssemblyResolve callback:
public static void RegisterSiemensAssemblies(string tiaPortalVersion)
{
// ** Resolve Siemens Openness assemblies for TiaWrapperLib library
if (String.IsNullOrEmpty(tiaPortalVersion))
{
TiaWrapperLib.Utils.Resolver.SetAssemblyPaths("15.0", "15.0.0.0"); // By default load DLLs for Tia Portal v15, Openness v15
}
else
{
if (tiaPortalVersion == "18.0")
TiaWrapperLib.Utils.Resolver.SetAssemblyPaths("18.0", "18.0.0.0"); // If TIA Portal 18 load Openness v18
else if (tiaPortalVersion == "14.0" || tiaPortalVersion == "15.0" || tiaPortalVersion == "15.1" || tiaPortalVersion == "16.0" || tiaPortalVersion == "17.0")
TiaWrapperLib.Utils.Resolver.SetAssemblyPaths(tiaPortalVersion, "15.0.0.0"); // IF any other TIA Portal load Openness v15
else
TiaWrapperLib.Utils.Resolver.SetAssemblyPaths("15.0", "15.0.0.0"); // If invalid version load Openness v15 from TIA Portal v15
}
AppDomain.CurrentDomain.AssemblyResolve += TiaWrapperLib.Utils.Resolver.OnResolve; // Add callback to resolve Siemens assemblies in real time when the DLLs aren't found
}
And then the callback sets the proper assemblies:
public static Assembly OnResolve(object sender, ResolveEventArgs args)
{
//var executingAssembly = Assembly.GetExecutingAssembly();
var assemblyName = new AssemblyName(args.Name);
string path = "";
// If not resolving Siemens assemblies
if (!assemblyName.Name.EndsWith("Siemens.Engineering")
&& !assemblyName.Name.EndsWith("Siemens.Engineering.Hmi"))
return null;
// Initialize assembly paths for latest versions
if (AssemblyPath == null || AssemblyPathHmi == null)
{
var tiaVersion = GetEngineeringVersions().Last();
var opennessVersion = GetOpennessVersions(tiaVersion).Last();
SetAssemblyPaths(tiaVersion, opennessVersion);
}
if (assemblyName.Name.EndsWith("Siemens.Engineering"))
path = AssemblyPath;
if (assemblyName.Name.EndsWith("Siemens.Engineering.Hmi"))
path = AssemblyPathHmi;
if (string.IsNullOrEmpty(path) || !File.Exists(path))
{
throw new FileLoadException($"Could not load assembly '{assemblyName}' from path '{path}'.");
}
return Assembly.LoadFrom(path);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论