【C++动态库】动态库隐式与显式加载 | 为什么要动态加载动态库 | LoadLibra ...

打印 上一主题 下一主题

主题 888|帖子 888|积分 2664


目录
1、概述
2、dll动态库的隐式加载与动态加载
2.1、dll库的隐式加载
2.2、dll库的显式加载
3、为什么要使用动态加载dll动态库的方式?什么时候必要使用动态加载?
3.1、调用体系dll库中未公开的接口
3.2、调用控件库中的注册接口向体系中注册该控件
3.3、底层的业务模块做成动态启动方式,上层产物可以根据自己的业务必要选择想启动的业务模块
4、LoadLibrary动态加载dll动态库失败的场景
4.1、自制的安装包程序中遇到的LoadLibrary加载dll库失败问题
4.2、主程序底层模块调用LoadLibrary加载dll库失败问题
5、 LoadLibaray加载失败的可能缘故原由
6、参考开源操作体系ReactOS中的regsvr32.exe程序的实现源码,找到了办理LoadLibrary加载dll库失败的办法
6.1、ReactOS开源操作体系简介
6.2、使用Source Insight打开ReactOS源码,找到regsvr32.exe程序的代码
7、到微软MSDN上检察LOAD_WITH_ALTERED_SEARCH_PATH参数的寄义
8、dll动态库加载失败的其他缘故原由
9、最后

C++软件非常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)
https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战专栏(专栏文章已更新460多篇,持续更新中...)
https://blog.csdn.net/chenlycly/article/details/140824370C++ 软件开发从入门到精通(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)
https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/category_2276111.html       有时我们必要在代码中调用LoadLibrary去动态加载某个dll库,然后从dll中获取某些函数地址去调用这些函数,但个别情况下会出现LoadLibrary加载dll库失败的问题。今天我们就来讲一讲动态库动态加载的相关内容。
1、概述

       有时我们会将一些功能封装成一个dll动态库,实现对代码的模块化封装,既方便程序按模块去管理,也方便日后调试与维护。此外,为了方便第三方厂商接入我们体系进行二次开发,也会将相关业务封装成SDK动态库供他们使用。
       一般C++程序中会使用到多个dll库,程序在发布时要将这些dll库打包到安装包中一起发布,安装时会将这些dll放置到程序的安装目录中(有个别库可能要放到体系路径中)。这些dll主要包罗程序依赖的业务库,第三方开源库以及一些C/C++运行时库以及UCRT体系通用运行时库等。关于C/C++运行时库以及UCRT体系通用运行时库的详细说明,可以检察我之前的文章:
C/C++运行时库和UCRT体系通用运行时库总结及问题实例分享(程序打包时要带上这些运行时库)
https://blog.csdn.net/chenlycly/article/details/139094024
   此外,程序在运行时除了会加载其依赖的业务库、C/C++运行时库以及UCRT体系通用运行时库,还会加载其依赖的体系库,这些体系库是操作体系提供的,操作体系会保证其兼容性。
  2、dll动态库的隐式加载与动态加载

       程序启动时,体系会给程序进程分配指定大小的进程空间(虚拟内存空间),体系先将exe主程序依赖的多个dll库加载到进程空间中,然后再将exe主程序文件加载到进程空间中,然后进入main函数,程序开始运行。如果启动过程有dll库加载失败(非动态加载,会弹出报错提示框),则程序启动终止,程序启动失败!
      程序启动时主要会加载以下3类dll动态库:
   1)程序依赖的dll业务库:包罗上层UI库、业务组件库、音视频编解码库、协议库、网络库以及第三方开源库等。
  2)运行时库:包含msvcp100.dll、msvcr100.dll、msvcp140.dll、vcruntime140.dll等C/C++运行时库,api-ms-win-core-file-l1-2-0.dll、api-ms-win-core-handle-l1-1-0.dll等UCRT体系通用运行时库。使用不同版本Visual Studio开发的程序,依赖的运行时库的版本是不同的。如果多个模块使用了多个版本的Visual Studio开发,则要将这些版本对应的运行时库都带上。好比Visual Studio 2010对应的运行时库是msvcp100.dll和msvcr100.dll,Visual Studio 2017对应的运行时库则是msvcp140.dll、vcruntime140.dll。
  3)体系动态库:包罗多个操作体系的动态库(体系库),好比user32.dll、kernel32.dll、ntdll.dll、ws2_32.dll、shell32等。
         程序中引用dll动态库(调用dll中的接口),在dll动态库加载时可分隐式加载和动态显式加载。
       本文主要讲解dll动态库加载与使用方面的内容,如果要了解C++动态库编程的多个细节问题,可以检察我之前写的C++动态库编程的专题文章
【C++动态库编程】C++名称改编、标准C接口、extern “C”、函数调用约定以及def文件详解
https://blog.csdn.net/chenlycly/article/details/132520200
   该文详细介绍了C++动态库编程中的关键概念与若干细节,包罗导入导出声明、C++函数名称改编、extern "C"的作用、导出标准C接口、函数调用约定以及def文件的使用。通过详细实例,论述了如何处置惩罚C++与C或其他语言之间的跨语言调用问题,以及如何确保接口标准C导出,制止链接错误。
  2.1、dll库的隐式加载

       所谓隐式加载,就是在程序中使用#pragma预编译指令引入dll库对应的.lib导入库:
  1. #pragma comment( lib, "libcurl.lib")
复制代码
大概在VS的工程配置中配置.lib导入库:

       对于隐式加载的库,在引入lib导入库之后,并包含dll库的头文件,就可以直接在代码中调用dll库的API接口了。
   代码编译时,会用到动态库API头文件,如果找不到调用的函数定义,则会报错;链接时,会链接.lib库中的符号,如果找不到函数符号,也会报错。
         隐式加载的dll库,在程序启动时就加载了(上面已经讲了程序启动时dll库的加载流程,主程序启动之前会将依赖的dll库先加载到程序的进程空间中),如果dll库加载失败,则立即终止exe主程序的启动流程,程序启动失败。
2.2、dll库的显式加载

       dll库的显式加载,是调用LoadLibrary大概LoadLibraryEx去动态地加载dll库。
       对于显式加载的库,必要调用GetProcAddress接口去获取dll库中API接口的函数地址(代码段地址),然后通过该地址去调用API函数。好比如下的一段代码:
  1. BOOL AutoRegsvr32( LPCTSTR lpszDllPath )
  2. {
  3.         if ( lpszDllPath == NULL )
  4.         {
  5.                 return;
  6.         }
  7.         // 1、使用LoadLibraryEx加载动态库
  8.         HINSTANCE hInstLib = LoadLibraryEx( lpszDllPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH );
  9.         if ( NULL == hInstLib )
  10.         {
  11.                 return FALSE;
  12.         }
  13.     // 2、调用GetProcAddress获取要调用的函数地址
  14.         typedef HRESULT (*DllRegisterServer)(void);
  15.         DllRegisterServer dllRegisterServer = (DllRegisterServer)GetProcAddress( hInstLib, "DllRegisterServer" );
  16.         if ( dllRegisterServer != NULL )
  17.         {
  18.         // 3、使用获取的函数地址去调用函数
  19.                 HRESULT hr = dllRegisterServer();
  20.         }
  21.         else
  22.         {
  23.                 FreeLibrary(hInstLib);
  24.                 return FALSE;
  25.         }
  26.         FreeLibrary(hInstLib);
  27.         return TRUE;
  28. }
复制代码
       显式加载的dll库,不会在程序启动时加载,而是在代码实行到LoadLibrary大概LoadLibraryEx函数的调用时才会动态的加载。如果dll库加载失败,也不会导致程序启动失败。本文主要讨论动态加载动态库的相关内容。

        在这里,给大家重点推荐一下我的几个热门脱销专栏,欢迎订阅:(博客主页还有其他专栏,可以去检察)
专栏1:该精品技术专栏的订阅量已达到530多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!欢迎订阅!)
C++软件调试与非常排查从入门到精通系列文章汇总
https://blog.csdn.net/chenlycly/article/details/125529931
   本专栏根据多年C++软件非常排查的项目实践,体系地总结了引发C++软件非常的常见缘故原由以及排查C++软件非常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出详细的项目问题实战分析实例(很有实战参考价值),领导大家徐徐把握C++软件调试与非常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发职员!
  考察一个开发职员的水平,一是看其编码及计划能力,二是要看其软件调试能力!以是软件调试能力(排查软件非常的能力)很重要,必须器重起来!能办理一般人办理不了的问题,既能提升个人能力及价值,也能表现对团队及公司的贡献!
  专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
  专栏2:(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达170多个,专栏文章已经更新到460多篇,持续更新中...)
C/C++实战进阶(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/category_11931267.html
   以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与论述!专栏涉及了C/C++范畴多个方面的内容,包罗C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法、C++11及以上新特性(不但看开源代码会用到,日常编码中也会用到部门新特性,口试时也会涉及到)、常用C++开源库的介绍与使用、代码分享(调用体系API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(排查软件非常的手段与方法、分析C++软件非常的基础知识、常用软件分析工具使用、实战问题分析案例等)、计划模式、网络基础知识与网络问题分析进阶内容等。
  专栏3:  
C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/article/details/131405795
   常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去奥妙地分析和办理日常工作中遇到的问题,很有实战参考价值!
  专栏4:   
VC++常用功能开发汇总(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/article/details/124272585
   将10多年C++开发实践中常用的功能,以高质量的代码显现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地办理软件开发过程中遇到的问题。
  专栏5: 
C++ 软件开发从入门到精通(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/category_12695902.html
   根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。
  
3、为什么要使用动态加载dll动态库的方式?什么时候必要使用动态加载?

       为什么要动态加载dll动态库以及什么时候使用动态加载方式,我们通过几个场景来说明一下。
3.1、调用体系dll库中未公开的接口

       有时我们可以必要调用Windows体系库中未公开的接口,有可能这个接口Windows官方未公开,也有可能这个接口在Windows某个版本之后才支持,即某个版本之后才提供该接口。
   好比近来有个CSDN上一个刚毕业的小伙找到我,问了一个类似的问题,他们项目使用的IDE开发环境是VS2015,他为了实现UI界面临触摸屏消息的支持,必要调用触摸屏相关的某个API函数,但这个函数在VS2017以上的版本才支持,在VS2015中找不到这个函数的定义,没法使用。他最开始想把项目从VS2015升级到VS2019,但升级后出现了很多编译错误,应该是新版本的编译器和老版本的有差异导致的。因为刚毕业,没多少开发经验,很难行止理升级后的那些编译错误。
         于是他找到我,问在这种情况下是否有办法使用到这个新API函数。后来建议他采用动态加载dll库的方式,因为这个API函数在较新的操作体系上才支持,动态加载时恰好可以判断一下目的dll库是否有这个函数。因为程序要支持全部版本的操作体系,在一些老的操作体系中好比XP体系(当前估计基本没人用了,但我们这边有个测试同事的测试呆板上还在用XP体系)是不支持一些新的API函数的。
       我们再举一个详细的例子,我们10多年前在开发一个打开文件所在文件夹的功能,要实现打开所在文件夹并选中该文件的效果:

​经搜索得知,已经有API函数SHOpenFolderAndSelectItems直接支持这个功能了,但这个函数只有Windows XP体系才支持,其时还有部门用户在用Windows 2000体系,这个体系是不支持这个函数的。
        如何知道目的函数支持的最低操作体系版本?如何知道目的函数位于哪个体系dll库中呢?实在很简朴,以“函数名 msdn”为关键字到网页上搜索,找到微软MSDN上对该函数的说明,将页面滚动到页面底部,就可以看到函数的相关信息。以SHOpenFolderAndSelectItems函数为例,该函数的信息描述如下:

​首先是该函数最低支持操作体系是Windows XP,即之前的版本是不支持的,好比Windows 2000体系中是不支持的,即Windows 2000体系的体系dll库中没有这个接口。接着可以看到,SHOpenFolderAndSelectItems函数的声明位于shlobj_core.h头文件中,函数实现位于Shell32.dll库中。
   作为一名Windows开发职员,要学会使用微软的MSDN,是基本的要求,在MSDN上可以看到微软官方对窗口消息、体系API函数等的详细说明。遇到问题时,我们应该第一时间到MSDN上检察相关的说明。
         其时我们使用的还是Visual Studio 6.0(VC6),在VC6中也找不到SHOpenFolderAndSelectItems函数的声明(将SHOpenFolderAndSelectItems输入到代码中,会找不到该函数的声明,编译会报错),以是后来决定使用LoadLibrary动态加载体系库Shell32.dll,然后再调用GetProcAddress接口到dll中去获取SHOpenFolderAndSelectItems函数的函数指针(函数首地址),然后去调用该函数地址即可。
   如果代码运行在Windows 2000的体系上,体系库Shell32.dll中是找不到SHOpenFolderAndSelectItems函数的,调用GetProcAddress会返回NULL,这样也就没法使用SHOpenFolderAndSelectItems函数了,此时可以简朴的调用ShellExecute API函数打开所在文件夹,但不能选中目的文件了。
           动态加载库Shell32.dll去调用接口SHOpenFolderAndSelectItems的代码如下:
  1. // 打开文件夹并选中对应的文件
  2. BOOL OpenFolderAndSelectFile(CString strFilePath)
  3. {
  4.     LPITEMIDLIST pidl;
  5.     LPCITEMIDLIST cpidl;
  6.     LPSHELLFOLDER pDesktopFolder;
  7.     ULONG chEaten;
  8.     HRESULT hr;
  9.     WCHAR wfilePath[MAX_PATH + 1] = { 0 };
  10.     //初始化了COM库
  11.     ::CoInitialize( NULL );
  12.     if (SUCCEEDED(SHGetDesktopFolder(&pDesktopFolder)))
  13.     {
  14.     IShellFolder::ParseDisplayName要传入宽字节
  15.         LPWSTR lpWStr = NULL;
  16. #ifdef _UNICODE
  17.         _tcscpy(wfilePath, strFilePath);
  18.         lpWStr = wfilePath;
  19. #else
  20.         MultiByteToWideChar(CP_ACP, 0, (LPCSTR)strFilePath, -1, wfilePath, MAX_PATH);
  21.         lpWStr = wfilePath;
  22. #endif
  23.         hr = pDesktopFolder->ParseDisplayName(NULL, 0, lpWStr, &chEaten, &pidl, NULL);
  24.         if (FAILED(hr))
  25.         {
  26.             pDesktopFolder->Release();
  27.             //::CoUninitialize();
  28.             return FALSE;
  29.         }
  30.         cpidl = pidl;
  31.         // SHOpenFolderAndSelectItems是非公开的API函数,需要从shell32.dll获取
  32.         // 该函数只有XP及以上的系统才支持,Win2000和98是不支持的
  33.         HMODULE hShell32DLL = ::LoadLibrary(_T("shell32.dll"));
  34.         ASSERT(hShell32DLL != NULL);
  35.         if (hShell32DLL != NULL)
  36.         {
  37.             typedef HRESULT(WINAPI *pSelFun)(LPCITEMIDLIST pidlFolder, UINT cidl, LPCITEMIDLIST  *apidl, DWORD dwFlags);
  38.             pSelFun pFun = (pSelFun)::GetProcAddress(hShell32DLL, "SHOpenFolderAndSelectItems");
  39.             ASSERT(pFun != NULL);
  40.             if (pFun != NULL)
  41.             {
  42.                 hr = pFun(cpidl, 0, NULL, 0); // 第二个参数cidl置为0,表示是选中文件
  43.                 if (FAILED(hr))
  44.                 {
  45.                     ::FreeLibrary(hShell32DLL);
  46.                     pDesktopFolder->Release();
  47.                     ::CoUninitialize();
  48.                     return FALSE;
  49.                 }
  50.             }
  51.             ::FreeLibrary(hShell32DLL);
  52.         }
  53.         else
  54.         {
  55.             pDesktopFolder->Release();
  56.             ::CoUninitialize();
  57.             return FALSE;
  58.         }
  59.         // 释放pDesktopFolder
  60.         pDesktopFolder->Release();
  61.     }
  62.     else
  63.     {
  64.         ::CoUninitialize();
  65.         return FALSE;
  66.     }
  67.     ::CoUninitialize();
  68.     return TRUE;
  69. }
复制代码
       对体系函数SHOpenFolderAndSelectItems的调用封装到函数OpenFolderAndSelectFile中,可以根据OpenFolderAndSelectFile函数的返回值判断有没有乐成实行到体系函数SHOpenFolderAndSelectItems,如果返回FALSE,则表现没乐成调用SHOpenFolderAndSelectItems,则调用ShellExecute去打开所在文件夹,但不会选中目的文件。
  1. ShellExecute( NULL, _T("open"), _T("explorer.exe"), strFilePath, NULL, SW_SHOWNORMAL );
复制代码
        关于打开文件所在文件夹的详细细节说明,可以拜见我之前写的文章:
VC++实现打开文件和打开所在文件夹的功能(附源码)
https://blog.csdn.net/chenlycly/article/details/123591092
3.2、调用控件库中的注册接口向体系中注册该控件

        对于控件库的注册,我们可以手动在cmd窗口中使用regsvr32下令去注册,如下所示:
  1. regsvr32 "D:\Program Files\feiq\GifDll\ImageOle.dll"
复制代码

​但有时必要在代码中去主动注册控件库,好比在用代码实现的程序Setup安装包程序中,必要通过代码去注册程序中包含的控件库。
   该安装包是自己写代码实现的,不是使用InstallShield、InnoSetup打包工具制作的,自己通过代码实现安装包要灵活很多,可以定制安装包的UI界面,可以定制安装包实行的操作。
          一般对于必要向体系注册的控件库,内部都会实现DllRegisterServer接口,我们只要获取到这个接口,就可以向体系注册了。在代码中调用LoadLibrary将控件库加载起来,然后调用GetProcAddress函数去获取DllRegisterServer函数地址,然后去call这个函数即可。注册控件库的代码如下
  1. void RegCtrl( LPCTSTR lpszDllPath )
  2. {
  3.     if ( lpszDllPath == NULL )
  4.     {
  5.         return;
  6.     }
  7.  
  8.     CString strLog;
  9.     strLog.Format( _T("[RegCtrl] lpszDllPath: %s."), lpszDllPath );
  10.     WriteLog( strLog );
  11.  
  12.     // 1、先将库动态加载起来
  13.     HINSTANCE hInstance = LoadLibrary( lpszDllPath )
  14.     if ( NULL == hInstance )
  15.     {
  16.         strLog.Format( _T("[RegCtrl] load dll failed, GetLastError: %d."), GetLastError() );
  17.         WriteLog( strLog );
  18.     }
  19.  
  20.     // 2、获取库中的DllRegisterServer函数接口,调用该接口去完成控件的注册
  21.     typedef HRESULT (*DllRegisterServerFunc)(void);
  22.     DllRegisterServerFunc dllRegisterServerFun = (DllRegisterServerFunc)GetProcAddress( hInstance, "DllRegisterServer" );
  23.     if ( dllRegisterServerFun != NULL )
  24.     {
  25.         HRESULT hr = dllRegisterServerFun();
  26.         strLog.Format( _T("[RegCtrl] DllRegisterServer return: %d"), hr );
  27.         WriteLog( strLog );
  28.     }
  29.     else
  30.     {
  31.         strLog.Format( _T("[RegCtrl] Get DllRegisterServer address failed. GetLastError: %d"), GetLastError() );
  32.         WriteLog( strLog );
  33.     }
  34.  
  35.     FreeLibrary( hInstance );
  36. }
复制代码
3.3、底层的业务模块做成动态启动方式,上层产物可以根据自己的业务必要选择想启动的业务模块

       对于底层的业务组件层,在计划时可以按业务范例分别成多个dll去实现,在启动时使用动态启动的情势。上层产物可以根据自己必要的功能去启动对应的dll业务模块,不必要的模块则可以不启动(通过调用接口告诉底层要启动哪些模块),这样程序就不用一上来就加载全部的模块,更加灵活,更节流资源(较少程序对内存等资源的占用)。
   程序在加载dll库时,将dll库的二进制文件加载到程序的进程空间中,dll库二进制文件会占用程序的虚拟地址空间。二进制文件中存放的二进制代码,以是二进制文件占用的是程序代码段内存。此外,加载起来的模块,模块中的部门变量可能在加载大概初始化时就分配内存了,会占用数据段内存。以是只管加载少的模块,可以节约内存空间。
  4、LoadLibrary动态加载dll动态库失败的场景

       我们几年前在项目中遇到过好频频LoadLibrary动态加载库失败的情况了(固然加载失败的概率很小,是偶发的,但一旦出现,会明显影响产物的使用),明显指定的绝对路径中的dll文件是存在的,文件也是正常的,但就是会加载失败。我们实验调用GetLastError接口去获取LastError值也没搞清楚加载失败的缘故原由。下面列举几个项目中遇到的问题场景。
4.1、自制的安装包程序中遇到的LoadLibrary加载dll库失败问题

       我们在自制的安装包程序中必要向体系注册控件(控件做成了dll),相关代码如下:(传入要注册控件的完整路径)
  1. void RegCtrl( LPCTSTR lpszDllPath )
  2. {
  3.     if ( lpszDllPath == NULL )
  4.     {
  5.         return;
  6.     }
  7.  
  8.     CString strLog;
  9.     strLog.Format( _T("[RegCtrl] lpszDllPath: %s."), lpszDllPath );
  10.     WriteLog( strLog );
  11.  
  12.     // 1、先将库动态加载起来
  13.     HINSTANCE hInstance = LoadLibrary( lpszDllPath )
  14.     if ( NULL == hInstance )
  15.     {
  16.         strLog.Format( _T("[RegCtrl] load dll failed, GetLastError: %d."), GetLastError() );
  17.         WriteLog( strLog );
  18.     }
  19.  
  20.     // 2、获取库中的DllRegisterServer函数接口,调用该接口去完成控件的注册
  21.     typedef HRESULT (*DllRegisterServerFunc)(void);
  22.     DllRegisterServerFunc dllRegisterServerFun = (DllRegisterServerFunc)GetProcAddress( hInstance, "DllRegisterServer" );
  23.     if ( dllRegisterServerFun != NULL )
  24.     {
  25.         HRESULT hr = dllRegisterServerFun();
  26.         strLog.Format( _T("[RegCtrl] DllRegisterServer return: %d"), hr );
  27.         WriteLog( strLog );
  28.     }
  29.     else
  30.     {
  31.         strLog.Format( _T("[RegCtrl] Get DllRegisterServer address failed. GetLastError: %d"), GetLastError() );
  32.         WriteLog( strLog );
  33.     }
  34.  
  35.     FreeLibrary( hInstance );
  36. }
复制代码
控件内部会实现DllRegisterServer接口,我们只要获取到这个接口,就可以向体系注册了。在代码中调用LoadLibrary将控件库加载起来,然后调用GetProcAddress函数去获取DllRegisterServer函数地址,然后去call这个函数即可。
       调用体系API函数LoadLibrary时,传入的控件文件的完整路径,好比D:\Program Files\XXXXXX\ImageOle.dll,这个路径和文件都存在的,但为什么LoadLibrary还会加载失败呢?
       于是修改代码,在调用LoadLibrary之后调用GetLastError去获取LoadLibrary失败的错误码,根据错误码(错误码为2)的寄义得知,是体系找不到指定的文件。传入的dll全路径是对的,文件也是有的,为啥还找不到文件呢?这就有点诡异了!这个问题只在个别的电脑上会出现。
4.2、主程序底层模块调用LoadLibrary加载dll库失败问题

       主程序的底层模块实现了组件化启动的方式,上层可以根据其必要的模块功能,可以有选择性的启动一些必要的底层业务模块,不用启动全部的业务模块,这样显得更加灵活,更节约资源。
       某天在某台电脑上出现业务不响应的问题,从软件的运行日志中找不到某个业务dll模块输出的日志,于是用Process Explorer检察我们程序加载的dll库列表:

​发现底层的某个动态加载的dll库没有加载到进程空间中。
       后来我们在LoadLibrary动态加载库的代码处添加了打印,将要加载的dll库的路径打印出来,并将LoadLibrary的LastError值打印出来。运行添加打印的程序后,复现了征象,取出日志看到,要加载的dll文件是全路径,LoadLibrary实行完后的LastError值表现找不到文件。这和上面的例子类似的,要加载的dll文件的路径都是精确的,但就是加载失败。
       关于如何使用Process Explorer工具,可以拜见我之前写的文章:
使用Process Explorer和Dependency Walker定位dll库动态启动失败的问题
https://blog.csdn.net/chenlycly/article/details/125216591C++软件开发值得推荐的十大高效软件分析工具
https://blog.csdn.net/chenlycly/article/details/127608247
5、 LoadLibaray加载失败的可能缘故原由

       好像其时出问题的呆板都是Win7体系,据技术群里的大佬说,是Win7纯净版体系的LoadLibaray内部实现有bug,有概率会出现加载失败的情况,必要安装一个补丁才能办理!
       但这个方法不太可行,因为不能到客户呆板上去安装补丁,也没有精神到全部的win7体系中去安装补丁。这个问题比较严重,严重影响软件的使用,我们还是要找到办理问题的根本办法才行,从代码层去办理,而不是让用户去参与!
       我们之前打仗过仿Windows XP体系的开源操作体系ReactOS,其时在研究Windows体系非常的处置惩罚流程时详细地看过相关的源码,这次也想实验到ReactOS的源码中找到办理的办法!
6、参考开源操作体系ReactOS中的regsvr32.exe程序的实现源码,找到了办理LoadLibrary加载dll库失败的办法

        之前下载过开源操作体系ReactOS的源码,ReactOS中的体系库内部实现和Windows是很相像的,提供的体系API接口几乎是一模一样的。我们时常会去检察ReactOS中API函数及底层库的内部实现,去了解Windows体系的内部实现,以辅助我们排查遇到的问题。
6.1、ReactOS开源操作体系简介


​       ReactOS是一款基于 Windows NT 架构的类似于Windows XP体系的免费开源操作体系,旨在实现和Windows操作体系二进制下的完全应用程序和驱动装备的兼容性,通过使用类似构架和提供完全公共接口。ReactOS不停在持续维护中,可以到reactos官网上找到ReactOS源码的下载地址,使用svn将ReactOS源码下载下来。
       ReactOS开源代码对于我们Windows软件开发职员来说非常有效,我们可以去检察API函数的内部实现,可以去检察体系exe的内部实现,可以去检察ReactOS体系内部恣意模块的实当代码。ReactOS是比较接近Windows体系的,可以通过检察ReactOS的代码去大概地了解Windows体系的内部实现,对我们排查Windows软件的问题是很有好处的!
6.2、使用Source Insight打开ReactOS源码,找到regsvr32.exe程序的代码

        ReactOS源码中没有Visual Studio工程文件,无法使用Visual Studio打开检察源代码,可以Source Insight去检察源码。至于如何使用Source Insight检察编辑源码,可以参看我之前写的一篇关于Source Insight的文章:
使用Source Insight检察编辑源代码
https://blog.csdn.net/chenlycly/article/details/124347857       为什么会想到去检察ReactOS中的regsvr32.exe程序的源码实现呢?因为上面我们说到的一个问题场景,在安装包中去动态加载dll控件库去注册控件,LoadLibrary加载失败,但我们在cmd下令行中使用regsvr32程序去手动注册,是可以加载起来的:(此处以注册飞秋软件中的图片插入控件ImageOle.dll为例

​于是想去看看regsvr32.exe程序的内部实现,看看它加载dll库的代码是什么样子的!恰好我们看过ReactOS开源体系的源码,实在现和Windows体系很类似的,于是去ReactOS中检察regsvr32.exe源码实现。
       因为regsvr32是一个独立的exe,不是一个函数,以是必要找到该程序对应的.c源文件。于是实验到文件列表中以regsvr32为关键字进行搜索,找到了regsvr32.c文件。在该文件中找到_tWinMain函数,在该main函数中看到了加载dll库文件的代码,如下所示:

​​代码中是调用LoadLibraryEx接口去加载dll库的,传入的参数为LOAD_WITH_ALTERED_SEARCH_PATH,而且也是获取dll控件库中的DllRegisterServer去进行注册的。
       regsvr32.exe使用这种方式去加载库文件应该是有它的原理的,于是我们也参照它的做法,把加载dll库的代码改成调用LoadLibraryEx,传入LOAD_WITH_ALTERED_SEARCH_PATH,即如下所示:
  1. HINSTANCE hInstance = LoadLibraryEx( lpszDllPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH )
  2. if ( NULL == hInstance )
  3. {
  4.     strLog.Format( _T("[RegCtrl] load dll failed, GetLastError: %d."), GetLastError() );
  5.     WriteLog( strLog );
  6. }
复制代码
改成上述代码后,就没再出现库加载失败的问题了,参考ReactOS中regsvr32.exe源码确实很有效
   后来在其他模块代码中也遇到dll库加载失败的问题,也更换成上述代码,背面再也没有出干涉题了。
  7、到微软MSDN上检察LOAD_WITH_ALTERED_SEARCH_PATH参数的寄义

        为了搞清楚LOAD_WITH_ALTERED_SEARCH_PATH参数的寄义,我们到微软MSDN上检察LoadLibraryEx API函数的说明页面,找到了LOAD_WITH_ALTERED_SEARCH_PATH参数的说明:
   LOAD_WITH_ALTERED_SEARCH_PATH:(0x00000008)
  If this value is used and lpFileName specifies an absolute path, the system uses the alternate file search strategy discussed in the Remarks section to find associated executable modules that the specified module causes to be loaded. If this value is used and lpFileName specifies a relative path, the behavior is undefined.
If this value is not used, or if lpFileName does not specify a path, the system uses the standard search strategy discussed in the Remarks section to find associated executable modules that the specified module causes to be loaded.
  This value cannot be combined with any LOAD_LIBRARY_SEARCH flag.
  从上述描述文字得知,如果设置了LOAD_WITH_ALTERED_SEARCH_PATH参数,则体系会使用the alternate file search strategy搜索策略,那这个搜索策略到底是什么样的呢?
        在LoadLibraryEx函数的说明页面继续向下看,看到了"Dynamic-Link Library Search Order"超链接,这是动态毗连库加载顺序的详细说明页面。从页面中我们看到了,如果没设置LOAD_WITH_ALTERED_SEARCH_PATH参数,则使用Standard Search Order for Desktop Applications标准搜索顺序:
   1、The directory from which the application loaded.
2、The system directory. Use the GetSystemDirectory function to get the path of this directory.
3、The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
4、The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
5、The current directory.
6、The directories that are listed in the PATH environment variable. Note that this does not include the per-application path specified by the App Paths registry key. The App Paths key is not used when computing the DLL search path.
          如果设置了LOAD_WITH_ALTERED_SEARCH_PATH参数,则体系会使用Alternate Search Order for Desktop Applications搜索顺序:
   1、The directory specified by lpFileName.
2、The system directory. Use the GetSystemDirectory function to get the path of this directory.
3、The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
4、The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
5、The current directory.
6、The directories that are listed in the PATH environment variable. Note that this does not include the per-application path specified by the App Paths registry key. The App Paths key is not used when computing the DLL search path.    
  以是我们终极找到了答案,当我们设置LOAD_WITH_ALTERED_SEARCH_PATH参数时,就会使用Alternate Search Order for Desktop Applications,会优先使用设置下来的完整路径去加载dll库。
8、dll动态库加载失败的其他缘故原由

       本文主要讲使用LoadLibrary加载dll动态库失败问题,dll动态库是没问题的,在问题场景中也是能启动的,好比手动在cmd窗口中使用regsvr32去注册控件时加载dll库都是能加载来的。
      dll动态库加载失败还有其他的缘故原由,好比当前dll依赖的其他动态库在当前体系中找不到(好比安装目录中缺少业务库大概C/C++运行时库):

大概当前dll接口调用下层的dll库的接口在下层dll中找不到(下层dll库的版本不对):

​这两种缘故原由在日常项目中也比较常见,限于篇幅,我就不详细展开了,可以去检察我之前写的关于dll动态库加载失败导致程序启动报错以及dll库加载失败的常见缘故原由分析的专题文章
【C++动态库】DLL动态库加载失败导致程序启动报错以及DLL库加载失败的常见缘故原由分析与总结
https://blog.csdn.net/chenlycly/article/details/142714236
9、最后

       本文详细讲解了C++动态库编程相关的内容,包罗动态库隐式与显式加载、为什么要动态加载动态库、LoadLibrary加载失败、用LoadLibraryEx替代LoadLibrary、参考开源操作体系ReactOS源码等,有一定的参考价值。
       此外,建议大家去了解一下ReactOS开源操作体系,通过检察ReactOS的代码去大概地了解Windows体系的内部实现,对排查Windows软件问题是很有好处的。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

滴水恩情

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表