欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 资讯 > Qt/C++案例 记录创建并加载动态链接库修改exe/dll类型文件的版本信息的示例

Qt/C++案例 记录创建并加载动态链接库修改exe/dll类型文件的版本信息的示例

2025/1/3 11:17:08 来源:https://blog.csdn.net/qq_35554617/article/details/144789805  浏览:    关键词:Qt/C++案例 记录创建并加载动态链接库修改exe/dll类型文件的版本信息的示例

目录导读

    • 简要
    • 创建动态库
      • rcedit 开源项目介绍
      • 拆分 类结构和公共函数
      • 定义函数接口
      • 部分函数实现示例:
      • 使用DependenciesGui 查看WINAPI函数接口
    • 加载动态库
      • 创建UI界面
      • QT 动态加载DLL
      • 源码和运行程序

简要

又到年末了,迷迷糊糊感觉啥都没干,又感觉啥都做了,
最近客户又提了个需求,说是要分发软件,修改不同软件版本号,
领导一研究直接发了一个github的开源项目给我参考:
electron / rcedit
是一个通过命令行来修改软件的版本号和其他资源数据的工具源码(命令行工具,用于编辑Windows上的exe文件资源。),
但是总不能直接把这控制台程序直接发给客户用吧,
于是我想着能不能在原有基础上加个界面:
正好熟悉下
用VS2017创建动态链接库封装rcedit 项目中的函数方法,
再通过QT创建界面,
并且动态加载DLL调用WINAPI函数接口,
读取修改EXE/DLL的版本信息。
这篇文章,将所有操作做个总结汇总,毕竟平时创建动态链接库并通过调用函数的情况也比较少。

创建动态库

rcedit 开源项目介绍

rcedit 命令行工具,用于编辑Windows上的exe文件资源。
在这里插入图片描述
编译也比较简单,直接下载下来,使用qmake直接编译就通过,
生成项目结构如下图示:
在这里插入图片描述
主要只有rescle.ccrescle.hmain.cc三个文件

  • 拆分 类结构和公共函数

创建一个rceditePackage的动态链接库项目,拆分rescle.h中的类结构并创建文件,再将对应的代码复制进去,公共部分内容移植到global.h文件中就行,代码量不多,整体结构也规范,直接复制过去就行。
如下图示:
在这里插入图片描述

  • 定义函数接口

创建一个rescleapi.h文件,创建RESCLEAPI_API 宏定义,和使用extern "C" {}使函数名称在生成的时候保持不变,
以及Source.def文件的创建都是与一般的动态链接库创建操作一般无二。

#ifdef RESCLEAPI_EXPORTS
#define RESCLEAPI_API __declspec(dllexport)
#else
#define RESCLEAPI_API __declspec(dllimport)
#endif#ifdef __cplusplus
extern "C" {
#endifRESCLEAPI_API const wchar_t* GetErrorStr();RESCLEAPI_API bool Get_Version_String(wchar_t* filepath, wchar_t* key, PTCHAR& Val);RESCLEAPI_API bool Get_Version_String_Batch(std::wstring filepath, std::map<std::wstring, std::wstring>& Mapval);RESCLEAPI_API bool Set_Version_String_Batch(std::wstring filepath, std::map<std::wstring, std::wstring> Mapval);RESCLEAPI_API bool Set_Version_String(wchar_t* _filepath, wchar_t* _key, wchar_t* _val);RESCLEAPI_API bool Set_File_Version(wchar_t* _filepath, wchar_t* _fileversion);RESCLEAPI_API bool Set_Product_Version(wchar_t* _filepath, wchar_t* _productversion);RESCLEAPI_API bool Set_Icon(wchar_t* _filepath, wchar_t* _pathicon);RESCLEAPI_API bool Set_Requested_Execution_Level(wchar_t* _filepath, wchar_t* _level);RESCLEAPI_API bool Application_Manifest(wchar_t* _filepath, wchar_t* _manifest);RESCLEAPI_API bool Set_Resource_String(wchar_t* _filepath, wchar_t* _key, wchar_t* _val);RESCLEAPI_API bool Set_Rcdata(wchar_t* _filepath, wchar_t* _key, wchar_t* _pathToResource);RESCLEAPI_API bool Get_Resource_String(wchar_t* _filepath, wchar_t* _key, PTCHAR& Val);#ifdef __cplusplus
}
#endif

需要注意的是添加 UNICODE 宏定义

#ifndef UNICODE
#define UNICODE
#endif

同时为了保证在于Qt数据交互的时候,不会混乱,定义UTF-8编码格式

// 设置 utf-8 编码格式
#if defined(_MSC_VER) && (_MSC_VER >= 1600)
# pragma execution_character_set("utf-8")
#endif
  • 部分函数实现示例:

根据 rcedit 中的 main.cc文件的命令行参数调用,封装成WINAPI函数,如:

  1. --get-version-string <key> 获取版本信息对应值
    定义为Get_Version_String函数:
// 获取版本信息 键值 对应值
bool Get_Version_String( wchar_t* filepath,   wchar_t* key,  PTCHAR& Val)
{ErrorStr = L"";ResourceUpdater updater;if (!updater.Load(filepath)){ErrorStr = (std::wstring(L"Unable to load file: ") + std::wstring(filepath)).c_str();return  false;}const wchar_t* result = updater.GetVersionString(key);if (!result){ErrorStr =L"Unable to get version string";return false;}Val = (PTCHAR)result;return true;
}
  1. --set-version-string <key> <value> 设置软件版本键值对应信息
    定义为Set_Version_String函数:
bool Set_Version_String( wchar_t* _filepath,  wchar_t* _key,  wchar_t* _val)
{ResourceUpdater updater;if (!updater.Load(_filepath)){ErrorStr = (std::wstring(L"Unable to load file: ") + std::wstring(_filepath)).c_str();return  false;}if (!updater.SetVersionString(_key, _val)){ErrorStr = L"Unable to change version string";return  false;}if (!updater.Commit()){ ErrorStr = L"Unable to commit changes";return  false;}return true;
}
  1. 扩展,批量调用
    定义Set_Version_String_Batch函数,批量修改版本信息,
    其中文件版本和产品版本信息需要特殊处理。
//! 批量修改版本信息
bool Set_Version_String_Batch( std::wstring filepath,  std::map<std::wstring, std::wstring> Mapval)
{bool IsAllSuccess = true;ResourceUpdater updater;if (!updater.Load(filepath.c_str())){ErrorStr = (std::wstring(L"Unable to load file: ") + std::wstring(filepath)).c_str();return  false;}for (const auto& keyVal : Mapval){const wchar_t* key = keyVal.first.c_str();const wchar_t* value = keyVal.second.c_str();//! 文件版本和产品版本单独处理if (std::wstring(key) == std::wstring(RU_VS_FILE_VERSION)){unsigned short v1, v2, v3, v4;if (!parse_version_string(value, &v1, &v2, &v3, &v4)){ErrorStr = (std::wstring(L"Unable to parse version string for FileVersion:") + std::wstring(value)).c_str();continue;}if (!updater.SetFileVersion(v1, v2, v3, v4)){ErrorStr = (std::wstring(L"Unable to change file version:") + std::wstring(value)).c_str();continue;}}else if (std::wstring(key) == std::wstring(RU_VS_PRODUCT_VERSION)){unsigned short v1, v2, v3, v4;if (!parse_version_string(value, &v1, &v2, &v3, &v4)){ErrorStr = (std::wstring(L"Unable to parse version string for ProductVersion:") + std::wstring(value)).c_str();continue;}if (!updater.SetProductVersion(v1, v2, v3, v4)){ErrorStr = (std::wstring(L"Unable to change product version:") + std::wstring(value)).c_str();continue;}}if (!updater.SetVersionString(key, value)){ErrorStr = (std::wstring(ErrorStr) + std::wstring(L"Unable to change version string: ") + keyVal.first + std::wstring(L" \r\n ")).c_str();IsAllSuccess = false;continue;}}if (!updater.Commit()){IsAllSuccess = false;ErrorStr = L"Unable to commit changes!";}return IsAllSuccess;
}

这些代码在main.cc 文件中都有具体实现,这里只是简单整理了一下
右键生成,至此,动态链接库创建完成,

  • 使用DependenciesGui 查看WINAPI函数接口

通过 使用Dependencies 下载Dependencies_x86_Release 查看dll中的函数接口,Dependencies能在win10环境下查看DLL调用.
注意:使用x86编译的dll,需要使用Dependencies_x86_Release查看,否则找不到对应函数接口,我甚至因此浪费半天时间不断调整RESCLEAPI_API 的宏定义定义,来判断接口dll中没有显示函数接口的问题。
动态链接库如下图示:
在这里插入图片描述

加载动态库

这里加载是通过LoadLibraryExWGetProcAddress调用函数动态加载的,
如果引入头文件和Lib文件,编译的时候会有各种冲突问题,
为了省事直接动态加载DLL了。

  • 创建UI界面

界面只是简单的添加了几个输入框和一个提交按钮,
用Qt来实现界面就比较简单。
如下图示:
在这里插入图片描述
自从我发现QFrame控件可以添加阴影后,
那是经常用 QGraphicsDropShadowEffect 来添加一个阴影效果,
因为看起来确实感觉要好看点了,

//! 创建阴影QGraphicsDropShadowEffect *shadowEffect = new QGraphicsDropShadowEffect(ui->frame_windows);shadowEffect->setOffset(0);shadowEffect->setBlurRadius(10);shadowEffect->setColor(QColor::fromRgb(0,0,0,60));ui->frame_windows->setGraphicsEffect(shadowEffect);
  • QT 动态加载DLL

界面完成了,接下来就是在Qt环境下动态加载DLL:
首先定义函数类型,声明函数变量:

//! 是否为空的判断
#define isnotnull(_VAL_) (_VAL_!=NULL && _VAL_!=nullptr)//! 定义函数类型
typedef const wchar_t* (WINAPI *GetErrorStr_W)();
typedef bool (WINAPI *Get_Version_String_W)(wchar_t* filepath, wchar_t* key, PTCHAR& Val);
typedef bool (WINAPI *Get_Version_String_Batch_W)(std::wstring filepath, std::map<std::wstring, std::wstring>& Mapval);
typedef bool (WINAPI *Set_Version_String_Batch_W)(std::wstring filepath, std::map<std::wstring, std::wstring> Mapval);//! 定义交互的函数接口
class Lib_RescleApi
{
public:Lib_RescleApi();GetErrorStr_W pfGetErrorStr=nullptr;Get_Version_String_W pfGet_Version_String=nullptr;Get_Version_String_Batch_W pfGet_Version_String_Batch=nullptr;Set_Version_String_Batch_W pfSet_Version_String_Batch=nullptr;private://! 初始化系统apivoid INITWINAPI();
};//! 定义版本信息对应的键值,
//! 公司名称
#define RU_VS_COMPANY_NAME      L"CompanyName"
//! 文件描述
#define RU_VS_FILE_DESCRIPTION  L"FileDescription"
//! 文件版本
#define RU_VS_FILE_VERSION      L"FileVersion"
//! 内部名称
#define RU_VS_INTERNAL_NAME     L"InternalName"
//! 版权信息
#define RU_VS_LEGAL_COPYRIGHT   L"LegalCopyright"
//! 商标信息
#define RU_VS_LEGAL_TRADEMARKS  L"LegalTrademarks"
//! 原始文件名
#define RU_VS_ORIGINAL_FILENAME L"OriginalFilename"
//! 私有构建信息
#define RU_VS_PRIVATE_BUILD     L"PrivateBuild"
//! 产品名称
#define RU_VS_PRODUCT_NAME      L"ProductName"
//! 产品版本
#define RU_VS_PRODUCT_VERSION   L"ProductVersion"
//! 特殊构建信息
#define RU_VS_SPECIAL_BUILD     L"SpecialBuild"
//! 注释
#define RU_VS_COMMENTS          L"Comments"

然后初始化类结构时加载rceditPackage.dll,绑定函数地址指针


Lib_RescleApi::Lib_RescleApi()
{INITWINAPI();
}void Lib_RescleApi::INITWINAPI()
{HMODULE H= NULL;QString dllpath=QApplication::applicationDirPath()+"/rceditPackage.dll";const wchar_t* wszLibraryName=utf8_to_wchar(dllpath.toStdString().c_str());qDebug()<<"DLL: "<<QString::fromWCharArray(wszLibraryName);if ((H = GetModuleHandleW(wszLibraryName)) != NULL)goto out;qDebug()<<"LoadLibraryExW -->";H = LoadLibraryExW(wszLibraryName, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);if(H==NULL)qDebug("Unable to load '%S.dll'", wszLibraryName);qDebug()<<"Load Function -->";pfGetErrorStr              = (GetErrorStr_W)              GetProcAddress(H,"GetErrorStr");pfGet_Version_String       = (Get_Version_String_W)       GetProcAddress(H,"Get_Version_String");pfGet_Version_String_Batch = (Get_Version_String_Batch_W) GetProcAddress(H,"Get_Version_String_Batch");pfSet_Version_String_Batch = (Set_Version_String_Batch_W) GetProcAddress(H,"Set_Version_String_Batch");if(pfGetErrorStr==NULL||pfGetErrorStr==nullptr)qDebug()<<"Not Load GetErrorStr Function!";if(pfGet_Version_String==NULL||pfGet_Version_String==nullptr)qDebug()<<"Not Load Get_Version_String Function!";if(pfGet_Version_String_Batch==NULL||pfGet_Version_String_Batch==nullptr)qDebug()<<"Not Load Get_Version_String_Batch Function!";if(pfSet_Version_String_Batch==NULL||pfSet_Version_String_Batch==nullptr)qDebug()<<"Not Load Set_Version_String_Batch Function!";
out:
//    free((LPWSTR)wszLibraryName);sfree(wszLibraryName);return;
}

加载后,在界面中简单调用:

 QString filepath = QFileDialog::getOpenFileName(this, "选择文件","*","*(*)");if(filepath!=""){ui->lineEdit_Filepath->setText(filepath);//! 当前文件的有效键值//! std::map<std::wstring,std::wstring> KayValMap;KayValMap.clear();KayValMap.insert(std::make_pair(RU_VS_COMPANY_NAME,L""));KayValMap.insert(std::make_pair(RU_VS_FILE_DESCRIPTION,L""));KayValMap.insert(std::make_pair(RU_VS_FILE_VERSION,L""));KayValMap.insert(std::make_pair(RU_VS_INTERNAL_NAME,L""));KayValMap.insert(std::make_pair(RU_VS_LEGAL_COPYRIGHT,L""));KayValMap.insert(std::make_pair(RU_VS_LEGAL_TRADEMARKS,L""));KayValMap.insert(std::make_pair(RU_VS_ORIGINAL_FILENAME,L""));KayValMap.insert(std::make_pair(RU_VS_PRIVATE_BUILD,L""));KayValMap.insert(std::make_pair(RU_VS_PRODUCT_NAME,L""));KayValMap.insert(std::make_pair(RU_VS_PRODUCT_VERSION,L""));KayValMap.insert(std::make_pair(RU_VS_SPECIAL_BUILD,L""));KayValMap.insert(std::make_pair(RU_VS_COMMENTS,L""));if(isnotnull(Api->pfGet_Version_String_Batch)){//! Lib_RescleApi* Api=new Lib_RescleApi();if(!Api->pfGet_Version_String_Batch(filepath.toStdWString(),KayValMap)){qDebug().noquote()<<QString("部分函数[Get_Version_String_Batch]执行失败! \r\n 原因:\r\n %1").arg(QString::fromWCharArray(Api->pfGetErrorStr()));if(KayValMap.size()==0){QMessageBox::warning(this,"提示",QString("函数[Get_Version_String_Batch]执行失败! \r\n 原因:\r\n %1").arg(QString::fromWCharArray(Api->pfGetErrorStr())));}}}elseQMessageBox::warning(this,"提示","未能加载rceditPackage.dll文件中的[Get_Version_String_Batch]函数!");QList<QLineEdit*> LineEditALL= ui->frame->findChildren<QLineEdit*>();foreach (QLineEdit* lineEdit, LineEditALL) {if(isnotnull(lineEdit)){if(lineEdit->property("Key").isValid()){if(KayValMap.find(lineEdit->property("Key").toString().toStdWString())!=KayValMap.end() && KayValMap.size()>0){lineEdit->setText(QString::fromWCharArray(KayValMap[lineEdit->property("Key").toString().toStdWString()].c_str()));lineEdit->setEnabled(true);}else{lineEdit->setText("");lineEdit->setEnabled(false);}}}}}

整体示例技术难度并不高,主要还是费时,也是为了学习Visual Studio生成的动态链接库与Qt开发之间的交互问题,所以整了个示例,平时也是直接移植到Qt静态链接库中调用了。

  • 源码和运行程序

整个示例源码查看代码仓中的rcedit-qtview项目
或者前往GitCode查看
https://gitcode.com/qq_35554617/rcedit-qtview/overview
exe可执行程序:
https://gitcode.com/qq_35554617/rcedit-qtview/releases/val1.1.1.2

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com