0


VC++通过查看ReactOS开源代码,解决完整路径dll加载失败问题(附源码)

   在某些情况下我们需要调用LoadLibrary动态地加载dll库,去获取dll库中的接口。但有时会出现加载失败的情况,即使传入的是dll库的全路径,也会加载失败,我们在项目中已经不止一次遇到这样的问题了。今天我们就来看一下这个问题该如何解决。

1、问题实例

   我们通过代码去制作安装包,不再使用inno setup、installshield等打包工具,安装包中的文件拷贝、读写注册表、注册控件等操作都是很通过代码去完成。在某次测试时发现,某个控件注册失败了,注册控件的代码如下所示:
void RegCtrl( LPCTSTR lpszDllPath )
{
    if ( lpszDllPath == NULL )
    {
        return;
    }

    CString strLog;
    strLog.Format( _T("[RegCtrl] lpszDllPath: %s."), lpszDllPath );
    WriteLog( strLog );

    // 1、先将库动态加载起来
    HINSTANCE hInstance = LoadLibrary( lpszDllPath )
    if ( NULL == hInstance )
    {
        strLog.Format( _T("[RegCtrl] load dll failed, GetLastError: %d."), GetLastError() );
        WriteLog( strLog );
    }

    // 2、获取库中的DllRegisterServer函数接口,调用该接口去完成控件的注册
    typedef HRESULT (*DllRegisterServerFunc)(void);
    DllRegisterServerFunc dllRegisterServerFun = (DllRegisterServerFunc)GetProcAddress( hInstance, "DllRegisterServer" );
    if ( dllRegisterServerFun != NULL )
    {
        HRESULT hr = dllRegisterServerFun();
        strLog.Format( _T("[RegCtrl] DllRegisterServer return: %d"), hr );
        WriteLog( strLog );
    }
    else
    {
        strLog.Format( _T("[RegCtrl] Get DllRegisterServer address failed. GetLastError: %d"), GetLastError() );
        WriteLog( strLog );
    }

    FreeLibrary( hInstance );
}

代码中通过dll文件的完整路径去加载控件dll,然后获取控件dll中的DllRegisterServer接口,调用DllRegisterServer接口完成控件的注册。

    根据打印出来的日志,在调用LoadLibrary去加载控件dll库时就失败了,我们给LoadLibrary接口传入的是控件dll的完整路径,到这个路径中也是能找到这个控件dll文件的。这个就有点搞不懂了,明明传入的是控件dll完整路径,并且控件dll文件在路径中也是存在的,为啥还会出现dll库加载失败的情况呢?这个有点太诡异了!

2、到cmd.exe中用regsvr32去手动注册控件

   安装包程序无法加载该dll库,我们想到可以尝试到cmd窗口中,使用regsvr32去手动注册这个dll控件试试。打开cmd窗口,手动将dll文件拖入到cmd窗口中获取dll文件的路径,按下回车键去注册该控件:(此处以飞秋的ImageOle.dll控件为例来说明)

regsvr32 "C:\Program Files\feiq\GifDll\ImageOle.dll"

手动注册是能正常注册成功的。使用regsvr32去手动注册时,调用的是系统的regsvr32.exe程序,regsvr32.exe程序内部应该也需要去加载这个控件dll文件的,regsvr32.exe程序能成功将库加载起来的。难道regsvr32.exe程序加载库的方式,和我们安装包程序使用的方式不一样?

3、到ReactOS开源代码中去查看regsvr32的实现,找到解决问题的线索

   之前下载过开源操作系统**ReactOS**的源码,ReactOS中的系统库内部实现和Windows是很相像的,提供的系统API接口基本是一模一样的。我们时常会去查看ReactOS中API函数及底层库的内部实现,去了解Windows系统的内部实现,以辅助排查我们在开发过程中遇到的问题。

3.1、ReactOS开源操作系统简介

   ReactOS是一款基于 Windows NT 架构的类似于Windows XP系统的免费开源操作系统,旨在实现和 NT 与 Windows 操作系统二进制下的完全应用程序和驱动设备的兼容性,通过使用类似构架和提供完全公共接口。
    ReactOS一直在持续维护中,可以到reactos官网上找到ReactOS源码的下载地址,使用svn将ReactOS源码下载下来。

  ** ReactOS开源代码对于我们Windows软件开发人员来说非常有用,我们可以去查看API函数的内部实现,可以去查看系统exe的内部实现,可以去查看ReactOS系统内部任意模块的实现代码。ReactOS是比较接近Windows系统的,可以通过查看ReactOS的代码去大概地了解Windows系统的内部实现,对我们排查Windows软件的问题是很有好处的!**

3.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因为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,即如下所示:
HINSTANCE hInstance = LoadLibraryEx( lpszDllPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH )
if ( NULL == hInstance )
{
    strLog.Format( _T("[RegCtrl] load dll failed, GetLastError: %d."), GetLastError() );
    WriteLog( strLog );
}

改成上述代码后,就没再出现库加载失败的问题了,上述代码果然很有用啊!

   后来其他模块中也遇到库加载失败的问题,也更换成上述代码,结果后面再也没有出过问题了。这个通过库的绝对路径去加载库失败的问题,不是必现的,只是在个别机器上才会出问题。

4、到微软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库的。


本文转载自: https://blog.csdn.net/chenlycly/article/details/124360104
版权归原作者 dvlinker 所有, 如有侵权,请联系我们删除。

“VC++通过查看ReactOS开源代码,解决完整路径dll加载失败问题(附源码)”的评论:

还没有评论