c++ 分目录加载dll依赖,解决dll杂乱问题

本文介绍了如何解决DLL动态链接库的管理问题,通过分包引用和延迟加载提升程序的启动效率和管理便捷性。首先,通过设定编译位置和添加DLL搜索目录,实现了DLL的分包管理。接着,详细讲解了延迟加载DLL的优势和实现方式,包括在C++和C#项目中的应用,以及如何处理Qt库的延迟加载问题。此外,还展示了C#项目中如何使用延迟加载和设置DLL搜索路径,确保分包依赖的正确加载。
摘要由CSDN通过智能技术生成

原文链接:https://blog.csdn.net/loveyou388i/article/details/123472237

DLL动态链接库分包引用及延迟加载

1.为什么要分包

最近项目中有应用到比较多的项目dll和第三方dll,之前是都放在exe的平级目录下的,当dll多到一定程度时,会非常的乱。有一些库已经没有用到了,但由于第三方库直接没有归类,也不知道那些库是必须引用的,因此也没有清理。

时间久了,整个目录下会显得杂乱无章,命名规范互相不同。

由于项目用到了C#和C++两种语言,因此dll还存在不同的类型。

虽然DLL乱不影响整体软件使用,但对于强迫症来说还是相当难受的。

为了方便管理,研究出了一种dll分包依赖的管理技术,解决依赖的dll存在杂乱的问题。

2.如何分包

1.确定编译位置

首先将项目中各模块的编译位置指定到需要分包的结构,例如本项目中的分包结构,就是在libs/x64下的分包结构,将core暴露在最外层。
在这里插入图片描述

2.添加引用库目录

指定dll搜索目录,即运行目录下的所有子目录,并使用windows中的API将这些子目录都加到dll搜索路径列表中。

将使用到kernel32.dll中的SetDllDirectoryAddDllDirectorySetDefaultDllDirectories

SetDllDirectory用于指定设置系统搜索路径
AddDllDirectory用于提交用户定义的系统所有路径
关键在于SetDefaultDllDirectories,用于指定需要所有路径的类型,这里填

SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR 
| LOAD_LIBRARY_SEARCH_DEFAULT_DIRS       
| LOAD_LIBRARY_SEARCH_SYSTEM32 
| LOAD_LIBRARY_SEARCH_USER_DIRS);//其他类型见枚举

   
   
  • 1
  • 2
  • 3
  • 4

我们将这些指定的代码封装到公共类中,也就是在core.dll里面,这也是为什么要将core.dll暴露在最外层

在程序运行时,还没有调用这些设置依赖路径的函数之前,是只能找到根目录下的dll的,除非把设置依赖的代码写在main函数入口,否则必须要将core.dll放到根目录下。

3.延迟加载dll

在此之前,需要介绍一下dll的加载方式

1. dll隐式加载

一个dll的使用需要依赖头文件和Lib文件,直接以普通的接口的方式来调用dll的函数,这种方式就是隐式加载。dll的隐式加载默认会在 mian 之前进行dll的加载工作。通过 depends 工具可以查看到dll的依赖. 隐式加载的dll的路径搜索一般是的顺序是:
1.可执行的文件目录
2.系统目录(c:/windows/system32)
3.Path环境变量的目录
4.LoadLibrary 函数参数中指定的 dll文件所在目录

2. dll显示加载

对于很多的插件化加载方式使用的是显示加载的方式,即dll的加载是通过 loadLibrary 实现,函数调用通过 GetProcAddress 来实现

项目采用Qt搭建界面,因此原本的设计中必不可免的会在启动项目中引用的Qt依赖库。

如果将Qt库放到分包的位置(不在根目录下),程序启动不起来,找不到需要依赖的Qt库。(隐式加载要求在启动前就先加载依赖项)

因此,需要使用延迟加载。

● 延迟加载的优点

  1. 一般的大型平台依赖的dll可能会有几百个之多,如果都在程序启动的时候去加载这些dll,将导致启动程序非常慢。使用延迟加载可以在第一次调用dll函数的时候去加载这些库,这样可以大大提高程序的启动效率,最合理的利用内存。
  2. 隐式加载的DLL一般情况下只能在当前exe所在的目录下(其他目录会污染系统环境),所有的dll在同一个目录是非常不利于管理大型程序的。

● 延迟加载技术

  1. 延迟载入是针对隐式链接DLL的
  2. 一个导出了字段(如全局变量)的DLL是无法延迟载入的
  3. 系统的DLL一般都是无法延迟载入的,如Kernel32.dll
  4. main 函数或者dllmain中的函数的第一句不要直接使用延迟载入函数(因为没有设置延迟载入的dll路径,也有可能进入死循环)

使用了延迟加载,则程序只有在运行到需要依赖的dll时,才会去加载dll,这样就可以在程序还没运行到依赖之前,把依赖路径添加进搜索列表中,从而达到启动页的依赖项也能够放到分包位置的目的。

对于Qt而言,还有另一个问题。就是Qt的Qt5Core.dll是有静态类的,因此无法延迟加载。

对此,我们只需要新建一个项目,项目中依赖Qt库。而启动程序中,延迟加载这个项目,就可以达到同一目的。

如何使用延迟加载

1.在VS项目中使用延迟加载

  1. 建立常规的C++库和可执行程序
  2. 添加延迟加载链接 如VS中为了延迟加载Dll,还需要在解决方案的该项目“属性”->“配置属性”->“链接器”->“输入”->“延迟加载的Dll”,需要注意的是扩展名是 dll 不是 lib
    在这里插入图片描述

2.在CMake工程中使用延迟加载

1.在需要被延迟加载的dll中,引用delayimp.lib依赖

## 延迟加载宏
MACRO(ADD_DELAYLOAD_FLAGS flagsVar)
  SET(dlls "${ARGN}")
  FOREACH(dll ${dlls})
    SET(${flagsVar} "${${flagsVar}} /DELAYLOAD:${dll}.dll")
  ENDFOREACH()
ENDMACRO()

延迟加载方案使用,需要注意的是,使用延迟加载需要依赖静态库delayimp.lib

即在被延迟加载的库的CMakeLists文件中,target_link_libraries(${ lib_name} delayimp.lib)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.在需要延迟加载的项目的CMakeList.txt中

##然后在需要延迟加载的入口库的CMakeList文件中声明
ADD_DELAYLOAD_FLAGS(CMAKE_EXE_LINKER_FLAGS sample) ##sample需要延迟加载的dll名,不带后缀

 
 
  • 1
  • 2

3.示例
gdal库延迟加载fileGDBAPI.dll(在某些环境下,明确知道不需要使用某个库的时候可以使用延迟加载)

## 在最外层的makefile.vc中找到类似的下面这行,可以支持多个延迟加载的dll
$(GDAL_DLL): $(LIB_DEPENDS)
	link /nologo /dll $(OGR_INCLUDE) $(BASE_INCLUDE) $(LIBOBJ) \
		$(EXTERNAL_LIBS) gcore\Version.res \
		 /out:$(GDAL_DLL) /implib:gdal_i.lib $(LINKER_FLAGS)  /delayload:FileGDBAPI.dll /delayload:expat.dll

 
 
  • 1
  • 2
  • 3
  • 4
  • 5

4.C#项目中使用分包依赖

C#项目中对dll的依赖也可以使用分包的结构,主要分几种情况

1.纯C#项目

C#中同样可以调用Windows API,达到添加搜索路径的目的

    public static class DllLoader
    {
        #region C Interface
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool SetDllDirectory(string lpPathName);
    <span class="token punctuation">[</span><span class="token class-name">DllImport</span><span class="token punctuation">(</span><span class="token string">"kernel32.dll"</span><span class="token punctuation">,</span> CharSet <span class="token operator">=</span> CharSet<span class="token punctuation">.</span>Auto<span class="token punctuation">,</span> SetLastError <span class="token operator">=</span> <span class="token keyword">true</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
    <span class="token keyword">static</span> <span class="token keyword">extern</span> <span class="token keyword">bool</span> <span class="token function">AddDllDirectory</span><span class="token punctuation">(</span><span class="token keyword">string</span> lpPathName<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token punctuation">[</span><span class="token class-name">DllImport</span><span class="token punctuation">(</span><span class="token string">"kernel32.dll"</span><span class="token punctuation">,</span> CallingConvention <span class="token operator">=</span> CallingConvention<span class="token punctuation">.</span>StdCall<span class="token punctuation">)</span><span class="token punctuation">]</span>
    <span class="token punctuation">[</span><span class="token keyword">return</span><span class="token punctuation">:</span> <span class="token function">MarshalAs</span><span class="token punctuation">(</span>UnmanagedType<span class="token punctuation">.</span>Bool<span class="token punctuation">)</span><span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">extern</span> <span class="token keyword">bool</span> <span class="token function">SetDefaultDllDirectories</span><span class="token punctuation">(</span><span class="token keyword">int</span> flags<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token preprocessor property">#<span class="token directive keyword">endregion</span></span>

    <span class="token preprocessor property">#<span class="token directive keyword">region</span> private Interface</span>
    <span class="token keyword">static</span> <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">_addDirectory</span><span class="token punctuation">(</span><span class="token keyword">string</span> lpPathName<span class="token punctuation">)</span>
    <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">var</span> root <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DirectoryInfo</span><span class="token punctuation">(</span>lpPathName<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>root<span class="token punctuation">.</span>Exists<span class="token punctuation">)</span>
        <span class="token punctuation">{<!-- --></span>
            <span class="token keyword">bool</span> isOk <span class="token operator">=</span> <span class="token function">AddDllDirectory</span><span class="token punctuation">(</span>lpPathName<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">var</span> subDirs <span class="token operator">=</span> root<span class="token punctuation">.</span><span class="token function">GetDirectories</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token keyword">var</span> dir <span class="token keyword">in</span> subDirs<span class="token punctuation">)</span>
            <span class="token punctuation">{<!-- --></span>
                <span class="token function">_addDirectory</span><span class="token punctuation">(</span>dir<span class="token punctuation">.</span>FullName<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">static</span> <span class="token keyword">private</span> <span class="token keyword">bool</span> <span class="token function">_setDefaultDllDirectories</span><span class="token punctuation">(</span><span class="token keyword">int</span> flags<span class="token punctuation">)</span>
    <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">return</span> <span class="token function">SetDefaultDllDirectories</span><span class="token punctuation">(</span>flags<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token class-name">System<span class="token punctuation">.</span>Reflection<span class="token punctuation">.</span>Assembly</span> <span class="token function">_CurrentDomain_AssemblyResolve</span><span class="token punctuation">(</span><span class="token keyword">object</span> sender<span class="token punctuation">,</span> <span class="token class-name">ResolveEventArgs</span> args<span class="token punctuation">)</span>
    <span class="token punctuation">{<!-- --></span>
        <span class="token keyword">var</span> dll_path <span class="token operator">=</span> args<span class="token punctuation">.</span>Name<span class="token punctuation">.</span><span class="token function">Split</span><span class="token punctuation">(</span><span class="token string">','</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
        <span class="token comment">//设置查找路径;</span>
        <span class="token comment">//获取当前exe路径;</span>
        <span class="token keyword">string</span> workPath <span class="token operator">=</span> AppDomain<span class="token punctuation">.</span>CurrentDomain<span class="token punctuation">.</span>BaseDirectory<span class="token punctuation">;</span>
        <span class="token comment">//搜索EXE路径自身文件夹和子文件夹中的DLL路径;</span>
        <span class="token keyword">var</span> paths <span class="token operator">=</span> Directory<span class="token punctuation">.</span><span class="token function">GetFiles</span><span class="token punctuation">(</span>workPath<span class="token punctuation">,</span> dll_path<span class="token punctuation">,</span> SearchOption<span class="token punctuation">.</span>AllDirectories<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>paths<span class="token punctuation">.</span>Length <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>
        <span class="token punctuation">{<!-- --></span>
            <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token comment">//可以简单使用此方法来设置搜索的DLL路径,主要是处理dllimport的dll</span>
        <span class="token function">AddDllDirectory</span><span class="token punctuation">(</span>paths<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> System<span class="token punctuation">.</span>Reflection<span class="token punctuation">.</span>Assembly<span class="token punctuation">.</span><span class="token function">LoadFrom</span><span class="token punctuation">(</span>paths<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token preprocessor property">#<span class="token directive keyword">endregion</span></span>

    <span class="token keyword">static</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">AddDirectory</span><span class="token punctuation">(</span><span class="token keyword">string</span> lpPathName<span class="token punctuation">)</span>
    <span class="token punctuation">{<!-- --></span>
        <span class="token comment">//当NET库加载失败时调用此委托;</span>
        AppDomain<span class="token punctuation">.</span>CurrentDomain<span class="token punctuation">.</span>AssemblyResolve <span class="token operator">-</span><span class="token operator">=</span> _CurrentDomain_AssemblyResolve<span class="token punctuation">;</span>
        AppDomain<span class="token punctuation">.</span>CurrentDomain<span class="token punctuation">.</span>AssemblyResolve <span class="token operator">+</span><span class="token operator">=</span> _CurrentDomain_AssemblyResolve<span class="token punctuation">;</span>

        <span class="token comment">//Cpp函数,对应枚举值 7680</span>
        <span class="token comment">//SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS</span>
        <span class="token comment">//                  | LOAD_LIBRARY_SEARCH_SYSTEM32 | LOAD_LIBRARY_SEARCH_USER_DIRS);</span>
        <span class="token keyword">bool</span> isOk <span class="token operator">=</span> <span class="token function">_setDefaultDllDirectories</span><span class="token punctuation">(</span><span class="token number">7680</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>Directory<span class="token punctuation">.</span><span class="token function">Exists</span><span class="token punctuation">(</span>lpPathName<span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">{<!-- --></span>
            lpPathName <span class="token operator">=</span> Directory<span class="token punctuation">.</span><span class="token function">GetCurrentDirectory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>

        <span class="token function">_addDirectory</span><span class="token punctuation">(</span>lpPathName<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

<span class="token punctuation">}</span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

2.C++与C#混合项目

C#中写和直接调用C++接口,即调用上文中提到的SGLibrary.AddDllSearchPath,效果一样。
并且需要把core_csharp.dll(c++转换成C#的dll),放到根目录下,程序才能启动。

或者在程序启动项的App.config下,加上搜索目录"swig",能把exe运行目录下swig目录包含进去

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="swig"/>
    </assemblyBinding>
  </runtime>
</configuration>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如果子目录的深度不高,子目录不多,也可以将所有分包的结构都写到privatePath中,这样不需要依赖任何代码,都能实现分包。

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值