简 述: 获取 windows 下当前代码模块的 dll 的版本号信息;或者指定路径下的 exe / dll 的版本号。

[TOC]


本文初发于 “偕臧的小站“,同步转载于此。


  💻: win10 21H2 📎 Visual Studio 2019


方法一

  使用 GetFileVersionInfoSizeGetFileVersionInfoVerQueryValue 三个函数来获取版本信息。通过参考其它思路,后整合手写一份自己所需要的代码,不过这方式缺陷是获取的为当前 exe 的安装路径,需要是已知,手动传入此参数。

#include <string>
#include <tchar.h>
#include <iostream>
#pragma comment(lib, "version.lib")
// 获取当前项目的版本号
inline std::string getVersion()
{
	std::string ret;

	// 获取程序的自身路径
	TCHAR productPath[MAX_PATH] = { 0 };
	if (GetModuleFileName(nullptr, productPath, sizeof(productPath) / sizeof(char)) == 0)
		return ret;

	// 能否获取版本号
	uint32_t size = 0;
	size = GetFileVersionInfoSize(productPath, NULL);
	if (size == 0)
		return ret;

	// 加载版本信息
	BYTE* pData = new BYTE[size];
	if (!GetFileVersionInfo(productPath, NULL, size, (LPVOID)pData)) {
		//TRACE("GetFileVersionInfo failed with error %d\n", GetLastError());
		return ret;
	}

	// 获取产品名和版本
	LPVOID lpBuffer;
	UINT uLength;
	DWORD dwVerMS;
	DWORD dwVerLS;

	// "040904b0" 是对应 rc 中语言的 ID
	if (VerQueryValue((LPVOID)pData, _T("\\"), &lpBuffer, &uLength)) {
		dwVerMS = ((VS_FIXEDFILEINFO*)lpBuffer)->dwProductVersionMS;
		dwVerLS = ((VS_FIXEDFILEINFO*)lpBuffer)->dwProductVersionLS;
	} else {
		//TRACE("Can't obtain ProductName and ProductVersion from resources\n");
		return ret;
	}

	::VerQueryValue((LPCVOID)pData, _T("\\StringFileInfo\\040904b0\\ProductName"), &lpBuffer, &uLength);

	std::cout << "================>" << (LPCTSTR)lpBuffer << "   " << (int)uLength << std::endl;


	ret = std::to_string(dwVerMS >> 16) + "." + std::to_string(dwVerMS & 0xFFFF) + "."
		+ std::to_string(dwVerLS >> 16) + "." + std::to_string(dwVerLS & 0xFFFF);
	return ret;
}

  关于最后打印本版信息两行代码,进行位运算可见下图,最后打印为 10.1.0.2001,基础知识遗忘可回顾 位操作


方法二『荐』

  考虑到安装程序时可能为自定义安装路径,那么路径就未知了;方式一就不可用了。

  此时采用 VirtualQuery(GetSelfModuleHandle, &mbi, sizeof(mbi)) 则可以很确保获取当前代码运行的 dll 的相关句柄,然后进而获取期路径、模块名等信息;故而更推荐此方法;最后可添加 _WIN32 宏判断确保仅在 windows 下运行。

//-------------------------.h 声明--------------------------
// 获取当前调用 dll 版本号
#include <mutex>
#ifdef _WIN32
inline std::once_flag g_dllFlag;
inline bool LogGetModVer(std::wstring& sv, std::wstring& error);
#endif

//-------------------------.cpp 定义--------------------------
#ifdef _WIN32
inline HMODULE GetSelfModuleHandle()  // 获取自身模块句柄
{
	MEMORY_BASIC_INFORMATION mbi;
	return ((::VirtualQuery(GetSelfModuleHandle, &mbi, sizeof(mbi)) != 0) ? (HMODULE)mbi.AllocationBase : NULL);  // 取当前函数的函数地址,用地址找模块
}

bool LogGetModVer(std::wstring& sv, std::wstring& error)
{
	wchar_t DllDir[512];

	HMODULE hModule = GetSelfModuleHandle();
	if (hModule == NULL) {
		error = L"GetSelfModuleHandle() failed! Not found self module handle.";
		return false;
	}

    // 获取模块名称和路径
	GetModuleFileName(hModule, DllDir, 512);
	std::wstring sFilePath(DllDir);

	if (sFilePath.empty())
	{
		error = L"sFilePath is empty.";
		return false;
	}

	... // dllPtr = 加载的 dll 模块指针

	if (nullptr != dllPtr)
	{
        ...

		if (!dllPtr->OpenFile(sFilePath.c_str(), true)) {
			error = L"dllPtr open failed.";
			return false;
		}
        
        // 通过自己封装的 dllPtr 类型来获取当前具体版本
	}

	error = L"__GetModVer faild.";
	return false;
}
#endif

//-------------------------调用示例--------------------------
#ifdef _WIN32
		std::call_once(g_dllFlag, [=]() {  // 背景可能是多线程下,仅运行一次即可
			std::wstring sv;
			std::wstring error;

			if (LogGetModVer(sv, error))
			{
				std::wstring sVer;
				if (!sv.empty()) {
					sVer = std::wstring(L"模块版本号: ") + sv.c_str();
				} else {
					sVer = error;
				}
                
                // ... sVer 输出到日志
			}

		});
#endif

系列地址

QtExamples

欢迎 star ⭐ 和 fork 🍴 这个系列的 C++ / QT / DTK 学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”