VC++ 解决dll库动态库加载失败问题(调用LoadLibrary加载失败)(附源码)

news/2024/3/28 22:06:09/文章来源:https://blog.csdn.net/chenlycly/article/details/129200442

目录

1、动态加载dll库去调用库中的函数

1.1、调用系统dll库中未公开的接口

1.2、调用控件库中的注册接口向系统中注册该控件

2、LoadLibrary动态加载dll库失败的场景

2.1、自制安装包中遇到的LoadLibrary加载dll库失败问题

2.2、主程序底层模块调用LoadLibrary加载dll库失败的问题

3、受系统regsvr32的启发,试图寻找LoadLibrary加载dll库失败的解决办法

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

4.1、ReactOS开源操作系统简介

4.2、使用Source Insight打开ReactOS源码,找到regsvr32.exe程序的代码

5、到微软MSDN上查看LOAD_WITH_ALTERED_SEARCH_PATH参数的含义


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931        有时我们在代码中需要调用LoadLibrary去动态加载某个dll,然后从dll中获取某些函数地址去调用这些函数,但某些情况下会出现LoadLibrary加载库失败的问题。今天我们就来讲一讲如何解决dll库加载失败的问题。

1、动态加载dll库去调用库中的函数

        一般我们在调用dll库中的接口时,只需要包含dll库的API头文件并将dll库对应的lib库引入到工程中,就可以在代码中直接调用dll库的导出接口或者使用dll库的导出类了。这就是在编译期就确定的静态引入的方式。除了这种方式,还支持动态加载dll库去调用库中接口的方式。在某些个别的场景下,我们确实需要在代码中使用动态加载dll库的方式。

        对于静态调用dll库接口的方式,dll库在主程序启动时就被加载进主程序的进程空间中了(主程序在启动时会先将其依赖的底层dll库先加载到进程空间中,然后exe主程序才会启动起来)。

        对于动态加载的dll库,只有代码执行到LoadLibrary或者LoadLibarayEx函数调用时才会将dll库加载到进程空间中,可能在函数调用完成后,就将dll库从当前进程空间中卸载掉(调用FreeLibrary)。

1.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函数位于Shell32.dll库中。

作为一名Windows开发人员,会使用微软MSDN,是基本的要求,在MSDN上可以看到微软官方对窗口消息、系统API函数等的详细说明。遇到问题时,我们应该第一时间到MSDN上查看相关的说明。

        当时我们使用的还是Visual Studio 6.0(VC6),在VC6中也找不到SHOpenFolderAndSelectItems函数的声明,所以后来决定使用LoadLibrary动态加载系统库Shell32.dll,然后再调用GetProcAddress接口到dll中去获取SHOpenFolderAndSelectItems函数的函数指针(函数首地址),然后去调用该函数地址即可。

如果代码运行在Windows 2000的系统上,系统库Shell32.dll中是找不到SHOpenFolderAndSelectItems函数的,调用GetProcAddress会返回NULL,这样也就没法使用SHOpenFolderAndSelectItems函数了,此时可以简单的调用ShellExecute API函数打开所在文件夹,但不能选中目标文件了。

         动态加载库Shell32.dll去调用接口SHOpenFolderAndSelectItems的代码如下:

// 打开文件夹并选中对应的文件
BOOL OpenFolderAndSelectFile(CString strFilePath)
{LPITEMIDLIST pidl;LPCITEMIDLIST cpidl;LPSHELLFOLDER pDesktopFolder;ULONG chEaten;HRESULT hr;WCHAR wfilePath[MAX_PATH + 1] = { 0 };//初始化了COM库::CoInitialize( NULL );if (SUCCEEDED(SHGetDesktopFolder(&pDesktopFolder))){IShellFolder::ParseDisplayName要传入宽字节LPWSTR lpWStr = NULL;
#ifdef _UNICODE_tcscpy(wfilePath, strFilePath);lpWStr = wfilePath;
#elseMultiByteToWideChar(CP_ACP, 0, (LPCSTR)strFilePath, -1, wfilePath, MAX_PATH);lpWStr = wfilePath;
#endifhr = pDesktopFolder->ParseDisplayName(NULL, 0, lpWStr, &chEaten, &pidl, NULL);if (FAILED(hr)){pDesktopFolder->Release();//::CoUninitialize();return FALSE;}cpidl = pidl;// SHOpenFolderAndSelectItems是非公开的API函数,需要从shell32.dll获取// 该函数只有XP及以上的系统才支持,Win2000和98是不支持的HMODULE hShell32DLL = ::LoadLibrary(_T("shell32.dll"));ASSERT(hShell32DLL != NULL);if (hShell32DLL != NULL){typedef HRESULT(WINAPI *pSelFun)(LPCITEMIDLIST pidlFolder, UINT cidl, LPCITEMIDLIST  *apidl, DWORD dwFlags);pSelFun pFun = (pSelFun)::GetProcAddress(hShell32DLL, "SHOpenFolderAndSelectItems");ASSERT(pFun != NULL);if (pFun != NULL){hr = pFun(cpidl, 0, NULL, 0); // 第二个参数cidl置为0,表示是选中文件if (FAILED(hr)){::FreeLibrary(hShell32DLL);pDesktopFolder->Release();::CoUninitialize();return FALSE;}}::FreeLibrary(hShell32DLL);}else{pDesktopFolder->Release();::CoUninitialize();return FALSE;}// 释放pDesktopFolderpDesktopFolder->Release();}else{::CoUninitialize();return FALSE;}::CoUninitialize();return TRUE;
}

对系统函数SHOpenFolderAndSelectItems的调用封装到函数OpenFolderAndSelectFile中,可以根据OpenFolderAndSelectFile函数的返回值判断有没有成功执行到系统函数SHOpenFolderAndSelectItems,如果返回FALSE,则表示没成功调用SHOpenFolderAndSelectItems,则调用ShellExecute去打开所在文件夹,但不会选中目标文件了。

ShellExecute( NULL, _T("open"), _T("explorer.exe"), strFilePath, NULL, SW_SHOWNORMAL );

        关于打开文件所在文件夹的详细细节说明,参见之前写的文章:

VC++实现打开文件和打开所在文件夹的功能(附源码)https://blog.csdn.net/chenlycly/article/details/123591092

1.2、调用控件库中的注册接口向系统中注册该控件

       对于控件库的注册,我们可以手动在cmd窗口中使用regsvr32命令去注册,如下所示:

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

但有时需要在代码中去自动注册控件库,比如在用代码实现的程序Setup安装包程序中,需要通过代码去注册控件库。

该安装包是我们自己写代码实现的,不是使用InstallShield、InnoSetup打包工具,自己实现安装包程序要灵活很多,可以定制安装包的UI界面,可以定制安装包执行的操作。

        一般对于需要向系统注册的控件库,内部都会实现DllRegisterServer接口,我们只要获取到这个接口,就可以向系统注册了。只需要在代码中调用LoadLibrary将控件库加载起来,然后调用GetProcAddress函数去获取DllRegisterServer函数地址,然后去call这个函数即可。注册控件库的代码如下:

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 );
}

2、LoadLibrary动态加载dll库失败的场景

2.1、自制安装包中遇到的LoadLibrary加载dll库失败问题

        以前我们遇到过自制安装包在注册控件库时出现动态加载控件库dll失败的问题。我们是调用LoadLibrary去加载的,传入的dll库的路径也是要加载的dll库的全路径,比如D:\Program Files\XXXXXX\ImageOle.dll,这个路径在出问题的电脑上确实是存在的,并且dll文件也是在这个路径下面的,为什么LoadLibrary还会加载失败呢?

       于是修改代码,在调用LoadLibrary之后调用GetLastError去获取LoadLibrary失败的错误码,根据错误码的含义得知,是找不到指定的文件。传入的dll全路径是对的,文件也是有的,为啥还找不到文件呢?这就有点诡异了!这个问题只在个别的电脑上会出现。

2.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

3、受系统regsvr32的启发,试图寻找LoadLibrary加载dll库失败的解决办法

         接着上面说到的问题,自制安装包程序中注册控件dll失败,于是在出问题的电脑上尝试手动使用regsvr32命令去注册,以注册ImageOle.dll控件为例,注册命令如下:

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

regsvr32命令在注册控件时,应该也会去加载控件dll库的,为啥该命令可以将dll库加载起来呢?该命令是调用哪个API函数去加载dll库的呢?

        regsvr32命令的相关代码是在Windows系统库中的,是没法看到Windows系统的源码的!突然想到,可以查看仿Windows的ReactOS开源操作系统的源码,ReactOS桌面操作系统的源码是开源的,并且是和Windows是类似的,可以查看ReactOS中regsvr32命令的相关实现代码,看看能否模仿相关的代码去处理。

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

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

4.1、ReactOS开源操作系统简介

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

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

4.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 );
}

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_73581.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

秒杀测试案例 Java Redis Mysql

基于redis和MySQL乐观锁实现秒杀优惠券场景,一人一单。MySQL乐观锁改良控制不出现超卖和少卖问题,使用redisson分布式锁在用户维度加锁控制一人一单。 源码:https://github.com/hanhanhanxu/SeckillTest 文中图片看不清的地方可以鼠标右键-&…

谷歌外推留痕,谷歌搜索留痕快速收录怎么做出来的?

本文主要分享谷歌搜索留痕的收录效果是怎么做的,让你对谷歌留痕技术有一个全面的了解。 本文由光算创作,有可能会被修改和剽窃,我们佛系对待这样的行为吧。 谷歌搜索留痕快速收录怎么做出来的? 答案是:通过谷歌蜘蛛…

C语言-结构体对齐

详细说明参考博客 (1条消息) C语言结构体对齐,超详细,超易懂_haozigegie的博客-CSDN博客 (1条消息) #pragma pack详解_OuJiang2021的博客-CSDN博客_#pragma pack 以下个人理解总结 出现结构体对齐考虑的根本原因就是:【数据存取执行效率】…

Openwrt中动态IPV6 防火墙的正确设置方法

环境:光猫桥接公网IPV6 问题:动态IPV6地址不知道怎么设置防火墙 解决办法:模糊匹配前缀,特定后缀 背景:将家中光猫桥接后,获得了公网的IPV6地址,可以从外部用IPV6访问家中的设备,但I…

【AI写作】 机器人流程自动化 介绍 - Robotic Process Automation (RPA) Introduction

写一篇文章介绍RPA技术,未来的发展。使用markdown格式,有3级索引,超过3000字。 某位大师说过的: 任何行业、任何部门都有大量的场景,涉及重复、有规则逻辑的工作,都可以用 RPA 开发一个软件机器人帮助完成。 文章目录 机器人过程自动化(RPA)简介RPA的定义RPA的好处Robo…

【centos7下部署mongodb】

一.安装环境 CentOS7MongoDB4.0.13正式版。 二.下载MongoDB 1.1 官网下载地址:https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.13.tgz 1.2 将压缩包通过xftp上传到服务器/opt目录,然后解压、改名 三. 配置环境变量及配置文件 3.1配置系…

有限差分法求解不可压NS方程

网上关于有限差分法解NS方程的程序实现不尽完备,这里是一些补充注解 现有的优秀资料 理论向 【1】如何从物理意义上理解NS方程? - 知乎 【2】NS方程数值解法:投影法的简单应用 - 知乎 【3】[计算流体力学] NS 方程的速度压力法差分格式_…

pytorch1.2.0+python3.6

一、说明 pytorch1.2.0python3.6CUDA10.0cudnn7.4.1.5 二、步骤 在conda中创建一个新的虚拟环境 查看一下自己的所有环境 激活虚拟环境 conda activate torch1.2.0 关于cuda和cudnn 1、查看自己电脑系统是10.2版本 http://链接:https://pan.baidu.com/s/1v5cN6…

自学前端,你必须要掌握的3种定时任务

当你看到这篇博客的时候,一定会和狗哥结下不解之缘,因为狗哥的博客里不仅仅有代码,还有很多代码之外的东西,如果你可以看到最底部,看到投票环节,我相信你一定感觉到了,狗哥的真诚,狗…

DateTimeParseException

前端请求为字符串的时间格式2023-02-16 19:19:51,服务端用LocalDateTime类型接收时报解析异常java.time.format.DateTimeParseException: Text 2023-02-16 19:19:51 could not be parsed at index 10方法一:JsonFormat(shape Shape.STRING, pattern &q…

Redis 主从复制-服务器搭建【薪火相传/哨兵模式】

Redis 安装参考文章:Centos7 安装并启动 Redis-6.2.6 注意:本篇文章操作,不能在 静态IP地址 下操作,必须是 动态IP地址,否则最后主从服务器配置不成功! 管道符查看所有redis进程:ps -ef|grep re…

Linux->父子进程初识和进程状态

目录 前言: 1. 父子进程创建 2. 进程状态 R(running)状态: S(sleep)状态: D(disk sleep)状态: T(stopped)状态: X(dead)和Z(zombie)状态: 孤儿进程: 前言: 本篇主要讲解关…

同事每天早下班,原来是用了这8个开发工具

引言 工欲善其事必先利其器,说的就是工匠要想更加高效的做事情,就得先将工具变得锋利。那么对于程序员来说同样也是如此,如果要想每天早点下班,就必须借助于一些开发工具来提高自己的工作效率,今天慕枫就给大家总结一…

365智能云打印怎么样?365小票无线订单打印机好用吗?

365智能云打印怎么样?365智能云打印是有赞官方首推的订单小票打印机,荣获2016年有赞最佳硬件服务商。可以实现远程云打印,无需连接电脑,只需通过GPRS流量或者WIFI即可连接,不受地理位置和距离限制。365小票无线订单打印…

关于高并发场景和进程线程协程的一些总结

1、IO复用和线程池哪个好?应用场景? IO复用就是一个线程处理多个客户端连接。如果自己实现的话,就是要不断轮询每个客户端连接,看看有没有事件发生(数据到达),即使可以用非阻塞的read函数&…

Python编写GUI界面,实现小说下载器

嗨害大家好鸭&#xff01;我是小熊猫~思路一、数据来源分析二. 代码实现步骤代码实现一、单章小说下载二、整本小说下载三、多线程采集四、采集排行榜所有小说五、搜索小说功能六、GUI界面<center>**&#x1f447;问题解答 源码获取 技术交流 抱团学习请联系&#x1f…

金仓数据库安装

一、麒麟操作系统安装金仓数据库 操作系统 DISTRIB_IDKylin DISTRIB_RELEASEV10 DISTRIB_CODENAMEjuniper 按照安装文档的步骤安装&#xff0c;记得记住设置的数据库的用户名、密码 二、window安装连接数据库的工具软件 三、jdbc连接数据库 &#xff08;1&#xff09;连接工…

错误记录:py2neo.errors.ProtocolError: Cannot decode response content as JSON

py2neo.errors.ProtocolError: Cannot decode response content as JSON 原因&#xff1a;目前不清楚 解决方法&#xff1a;进入\lib\site-packages\py2neo\database.py中更改graph_name为neo4j 程序正常运行

Linux命令及CPU占用过高的定位分析思路

一、vim命令不要使用vim打开大文件&#xff0c;vim会一次性读取所有内容到内存&#xff0c;容易造成宿主机内存溢出。 打开文件前&#xff0c;可以使用du -h命令查看文件大小。一般&#xff0c;100MB以下为宜。1、普通模式j 向下30j 向下移动30行k 向上h 向左l 向右0 到行首^ 到…

分阶段构建golang运行环境Dockerfile镜像

在开始这项工作之前大家可以先去看一下docker官方给出关于空镜像scratch的说明&#xff0c;采用官方简单的一句话就是&#xff1a;scratch是一个明确的空图像&#xff0c;特别是对于“从头开始”构建图像。分阶段构建镜像就会用到scratch这个空镜像&#xff0c;这样的好处是可以…