当前位置: 首页 > article >正文

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" {
#endif

	RESCLEAPI_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:
    //! 初始化系统api
    void 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())));
                }
            }
        }
        else
            QMessageBox::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


http://www.kler.cn/a/459164.html

相关文章:

  • Element plus 的 upload 组件实现自定义上传
  • OpenGL ES 04 图片数据是怎么写入到对应纹理单元的
  • HTML5 标签输入框(Tag Input)详解
  • ruoyi 分页 查询超出后还有数据; Mybatis-Plus 分页 超出后还有数据
  • [python SQLAlchemy数据库操作入门]-16.CTE:简化你的复杂查询
  • python常用库之数据验证库pydantic
  • 云手机:Instagram 矩阵搭建方案
  • MySQL什么情况下会加间隙锁?
  • 使用 AI Cursor 编程实现一个小产品 Chrome 扩展插件 MVP 功能
  • 阿里云 ECS 服务器绑定多个公网IP
  • 程序员转行室内设计师(软装设计流程)
  • 打卡算法题:155. 最小栈 --- 从193ms 到 4 ms的优化
  • linux装git
  • 基于 kubesphere + cube-studio搭建一站式云原生机器学习平台 国产纯中文 实操记录
  • 【遗传算法简介】
  • 太速科技-519-基于ZU19EG的4路100G光纤的PCIe 加速计算卡
  • 沪深捉妖记(一)探寻妖股的特征
  • 什么是网络安全(Cybersecurity)?
  • 1341:【例题】一笔画问题
  • 天河超算,使用Python自动ssh
  • 爬虫究竟是合法还是违法的?
  • 深度求索发布DeepSeek:高效、低成本的开源大语言模型
  • 讯飞星火智能生成PPTAPi接口说明文档 python示例demo
  • wget基本使用
  • Python爬虫教程——7个爬虫小案例(附源码)_爬虫实例
  • 如何优化Python网络爬虫的数据清洗流程,以提升数据质量并有效应对网站反爬虫机制?