问题

最近在为某第三方MFC项目写C++/CLI工程插件时遇到了如下一个问题:

MFC的工程不允许把.Net的依赖程序集放到执行程序的目录(防止影响其稳定性),依赖库只能放到非执行程序子目录的其他目录中。但无论是调用

// 使用windows API 需要 #include <windows.h>
SetDllDirectory(L"PathToDll");
// 使用.Net API的CLI写法
System::Domain::CurrentDomain->AppendPrivatePath(L"PathToDll");

或者加到PATH环境变量中,均无法加载依赖的第三方引用库。

问题处理方案

最终通过使用AssemblyResolve事件来手动加载程序集解决了该问题,参见参考链接1.

解答

这个问题的出现是因为不了解.Net的程序集的搜索与加载机制。C++/CLI的dll加载.Net依赖库时是完全按照.Net机制进行的,而.Net对程序集的加载顺序与搜索,有其自己的机制,在其官方文档中有详细描述(参考链接2)。简单来说按如下顺序。

加载顺序

  1. 根据App.Config或者发布者策略文件或者机器配置文件(这三个文件语法相同,位置不一样)中的决定依赖的程序集的版本号信息。
  2. 检查以前是否加载过该程序集,如果有直接加载,如果有失败记录直接失败。
  3. 针对强签名的程序集检查GAC中是否包含,有就直接加载。
  4. 根据已经读取的配置信息开始进行程序集的探测。

程序集的探测

  1. 如果配置文件没有对程序集的相关描述,且程序集的加载请求是通过Assembly.LoadFrom("path")发起的,则会直接根据指定的路径加载。
  2. 如果配置文件配置了<codebase>节点则会直接按指定的相对路径或URL加载。找不到则直接失败。官方文档
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="AssemblyRef"/>
<codeBase href="Ref\AssemblyRef.dll"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
  1. 如果有<probing>节点的话,则会通过子目录查找加载,注意只允许相对目录或URL,这里其实和AppendPrivatePath或者AppDomainSetup.PrivateBinPath都是一回事。官方文档
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Ref1"/>
</assemblyBinding>
</runtime>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

注意在probling的过程中,有其相应的规则。受基目录,语言,程序集名称,和<probing>节点配置(注意这里不止App.Config还有另外两个配置文件)相关。

官方举例如下:

  • 程序集: myAssembly
  • 基目录: http://www.code.microsoft.com
  • [<probing>节点配置: bin
  • 语言: de

    那么运行时会从以下四个URL尝试搜索加载:
  • http://www.code.microsoft.com/de/myAssembly.dll
  • http://www.code.microsoft.com/de/myAssembly/myAssembly.dll
  • http://www.code.microsoft.com/bin/de/myAssembly.dll
  • http://www.code.microsoft.com/bin/de/myAssembly/myAssembly.dll

注意事项

  1. 可以使用probing工具进行检查尝试的probling的路径Fuslogvw.exe (Assembly Binding Log Viewer) - .NET Framework | Microsoft Learn(需要管理员权限启动,下面会给出一个例子)。
  2. 只有强签名的程序集才有版本号检查。
  3. 子目录内的应用程序集不会主动搜索加载,需要在包括在privatePath的内。
  4. 不管<codebase>还是<probing>节点的描述都不允许脱离执行应用程序根目录,以下一个Fuslogvw.exe工具提供的日志说明了这一点。配置文件中使用<probing privatePath="..\Ref1"/>来指定了appbase目录外的Ref1目录作为搜索目录。但拼接出来的路径警告: 不是探测位置。最终加载失败。
*** 程序集联编程序日志项 (1/13/2024 @ 12:06:12 PM) ***

操作失败。
绑定结果: hr = 0x80070002。系统找不到指定的文件。 程序集管理器加载位置: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
在可执行文件下运行 C:\Users\zhang\Desktop\myproject\AssemblyLoadDemo\output\console\DemoConsoleApp.exe
--- 详细的错误日志如下。 === 预绑定状态信息 ===
日志: DisplayName = AssemblyRef, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
(Fully-specified)
日志: Appbase = file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/console/
日志: 初始 PrivatePath = NULL
日志: 动态基 = NULL
日志: 缓存基 = NULL
日志: AppName = DemoConsoleApp.exe
调用程序集: DemoConsoleApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null。
===
日志: 此绑定从 default 加载上下文开始。
日志: 在配置文件中找到专用路径提示: ..\Ref1。
日志: 正在使用应用程序配置文件: C:\Users\zhang\Desktop\myproject\AssemblyLoadDemo\output\console\DemoConsoleApp.exe.Config
日志: 使用主机配置文件:
日志: 使用 C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config 的计算机配置文件。
日志: 此时没有为引用应用策略(私有、自定义、分部或基于位置的程序集绑定)。
警告: 不是探测位置 file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/Ref1/AssemblyRef.DLL,因为该位置在 appbase 范围以外。
警告: 不是探测位置 file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/Ref1/AssemblyRef/AssemblyRef.DLL,因为该位置在 appbase 范围以外。
警告: 不是探测位置 file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/Ref1/AssemblyRef.EXE,因为该位置在 appbase 范围以外。
警告: 不是探测位置 file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/Ref1/AssemblyRef/AssemblyRef.EXE,因为该位置在 appbase 范围以外。
日志: 尝试下载新的 URL file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/console/AssemblyRef.DLL。
日志: 尝试下载新的 URL file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/console/AssemblyRef/AssemblyRef.DLL。
日志: 尝试下载新的 URL file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/console/AssemblyRef.EXE。
日志: 尝试下载新的 URL file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/console/AssemblyRef/AssemblyRef.EXE。
日志: 已尝试所有探测 URLs 但全部失败。 *** 程序集联编程序日志项 (1/13/2024 @ 12:06:12 PM) *** 操作失败。
绑定结果: hr = 0x80070002。系统找不到指定的文件。 程序集管理器加载位置: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
在可执行文件下运行 C:\Users\zhang\Desktop\myproject\AssemblyLoadDemo\output\console\DemoConsoleApp.exe
--- 详细的错误日志如下。 === 预绑定状态信息 ===
日志: DisplayName = AssemblyRef, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
(Fully-specified)
日志: Appbase = file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/console/
日志: 初始 PrivatePath = NULL
日志: 动态基 = NULL
日志: 缓存基 = NULL
日志: AppName = DemoConsoleApp.exe
调用程序集: DemoConsoleApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null。
===
日志: 此绑定从 default 加载上下文开始。
日志: 在配置文件中找到专用路径提示: ..\Ref1。
日志: 正在使用应用程序配置文件: C:\Users\zhang\Desktop\myproject\AssemblyLoadDemo\output\console\DemoConsoleApp.exe.Config
日志: 使用主机配置文件:
日志: 使用 C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config 的计算机配置文件。
日志: 此时没有为引用应用策略(私有、自定义、分部或基于位置的程序集绑定)。
警告: 不是探测位置 file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/Ref1/AssemblyRef.DLL,因为该位置在 appbase 范围以外。
警告: 不是探测位置 file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/Ref1/AssemblyRef/AssemblyRef.DLL,因为该位置在 appbase 范围以外。
警告: 不是探测位置 file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/Ref1/AssemblyRef.EXE,因为该位置在 appbase 范围以外。
警告: 不是探测位置 file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/Ref1/AssemblyRef/AssemblyRef.EXE,因为该位置在 appbase 范围以外。
日志: 尝试下载新的 URL file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/console/AssemblyRef.DLL。
日志: 尝试下载新的 URL file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/console/AssemblyRef/AssemblyRef.DLL。
日志: 尝试下载新的 URL file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/console/AssemblyRef.EXE。
日志: 尝试下载新的 URL file:///C:/Users/zhang/Desktop/myproject/AssemblyLoadDemo/output/console/AssemblyRef/AssemblyRef.EXE。
日志: 已尝试所有探测 URLs 但全部失败。
  1. 如果程序集有多个不同的版本号的版本的话,使用<codebase>而不是<probing>

参考链接:

  1. DLL redirection for c++/cli which load C# DLL (help) (microsoft.com)
  2. How the Runtime Locates Assemblies - .NET Framework | Microsoft Learn

从C++CLI工程的依赖库引用问题看.Net加载程序集机制的更多相关文章

  1. DICOM:DICOM三大开源库对比分析之“数据加载”

    背景: 上一篇博文DICOM:DICOM万能编辑工具之Sante DICOM Editor介绍了DICOM万能编辑工具,在日常使用过程中发现,“只要Sante DICOM Editor打不开的数据,基 ...

  2. .Net Core 通过依赖注入和动态加载程序集实现宿程序和接口实现类库完全解构

    网上很多.Net Core依赖注入的例子代码,例如再宿主程序中要这样写: services.AddTransient<Interface1, Class1>(); 其中Interface1 ...

  3. 从0搭建Vue3组件库(七):使用 glup 打包组件库并实现按需加载

    使用 glup 打包组件库并实现按需加载 当我们使用 Vite 库模式打包的时候,vite 会将样式文件全部打包到同一个文件中,这样的话我们每次都要全量引入所有样式文件做不到按需引入的效果.所以打包的 ...

  4. Django 04 模板标签(if、for、url、with、autoeacape、模板继承于引用、静态文件加载)

    Django 04 模板标签(if.for.url.with.autoeacape.模板继承于引用.静态文件加载) 一.if.for.url.with.autoescape urlpatterns = ...

  5. 仿ElementUI构建自己的Vue组件库用babel-plugin-component按需加载组件及自定义SASS主题

    最近使用ElementUI做项目的时候用Babel的插件babel-plugin-component做按需加载,使得组件打包的JS和CSS包体积大大缩小,加载速度也大大提升,所有想模仿做一个组件库也来 ...

  6. PHP 依赖注入,从此不再考虑加载顺序

    说这个话题之前先讲一个比较高端的思想--'依赖倒置原则' "依赖倒置是一种软件设计思想,在传统软件中,上层代码依赖于下层代码,当下层代码有所改动时,上层代码也要相应进行改动,因此维护成本较高 ...

  7. 前端公共库cdn服务推荐//提高加载速度/节省流量

    前端公共库cdn服务推荐,使用可以提高js库加载速度同时也可以节省自己空间的流量,CDN加速公共库虽好,不过一定要使用靠谱的前端cdn服务提供方. 以下整理出比较靠谱的国内cdn加速服务器.排名不分先 ...

  8. [Asp.Net Core] Blazor WebAssembly - 工程向 - 如何在欢迎页面里, 预先加载wasm所需的文件

    前言, Blazor Assembly 需要最少 1.9M 的下载量.  ( Blazor WebAssembly 船新项目下载量测试 , 仅供参考. ) 随着程序越来越复杂, 引用的东西越来越多,  ...

  9. vue 组件按需引用,vue-router懒加载,vue打包优化,加载动画

    当打包构建应用时,Javascript 包会变得非常大,影响页面加载.如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了. 结合 Vue 的 异步 ...

  10. img引用网络图片资源无法加载问题解决

    近期在自己项目中遇到引用一些网络图片资源,显示无法加载,但是在浏览器打开图片路径又可以显示的问题 解决办法: 在图片显示的界面把meta referrer标签改为never <meta name ...

随机推荐

  1. 在Docker下一键安装部署免费开源的问答社区!

    在Docker下一键安装部署免费开源的问答社区!   1.准备一台VPS主机,没有的话,[搞一台] 2.一键安装部署Docker wget https://raw.githubusercontent. ...

  2. Jmeter连接数据库sql语句操作,查询后取值做变量

    第一步 :导入jar包 第二步 :创建JDBC Reques 第三步 :创建JDBC Connection Configuration  第四步:在request中输入数据进行操作 Query Typ ...

  3. 本地化部署自建类ChatGPT服务远程访问

    Text generation web UI可为类ChatGPT的大型语言模型提供能够快速上手的网页界面,不仅可以提高使用效率,还可满足私有化部署,或对模型进行自定义.目前,该Web UI已经支持了许 ...

  4. Windows下VC++编译器32位memcpy、memmove函数汇编代码详解

    整理者:赤勇玄心行天道 QQ号:280604597 微信号:qq280604597 QQ群:511046632 博客:www.cnblogs.com/gaoyaguo  blog.csdn.net/c ...

  5. CompletableFuture异步优化代码

    CompletableFuture异步编排优化代码 我们在项目开发中,有可能遇到一个接口需要调用N个服务的接口.比如用户请求获取订单信息,需要调用用户信息.商品信息.物流信息等接口,最后再汇总数据统一 ...

  6. 【matplotlib 实战】--热力图

    热力图,是一种通过对色块着色来显示数据的统计图表.它通过使用颜色编码来表示数据的值,并在二维平面上呈现出来.热力图通常用于显示大量数据点的密度.热点区域和趋势. 绘图时,一般较大的值由较深的颜色表示, ...

  7. SpringBoot自动配置原理解析

    1: 什么是SpringBoot自动配置 首先介绍一下什么是SpringBoot,SpringBoost是基于Spring框架开发出来的功能更强大的Java程序开发框架,其最主要的特点是:能使程序开发 ...

  8. 使用 Jenkins + Github + dokcer-compose 部署项目-实战篇

    使用 Jenkins + Github + dokcer-compose 部署项目-实战篇 需要声明的一点是,此处实现的项目自动构建原理是 Github+Jenkins 的 webhook,因此得保证 ...

  9. 【数字图像处理】Matlab实现-图像增强-彩色图像增强(彩虹编码,热金属编码)

    伪彩色处理 伪彩色增强:把一幅黑白域图像的不同灰度级映射为一幅彩色图像的技术手段. 伪彩色增强有很多种方法,而下面的增强方法则是使用的空间域灰度级-彩色变换法. 空间域灰度级-彩色变换法:可以将灰度图 ...

  10. 聊聊RNN与Attention

    RNN系列: 聊聊RNN&LSTM 聊聊RNN与seq2seq attention mechanism,称为注意力机制.基于Attention机制,seq2seq可以像我们人类一样,将&quo ...