当我们开发Windows应用程序时,通常会涉及到使用资源(Resource)的情况。资源可以包括图标、位图、字符串等,它们以二进制形式嵌入到可执行文件中。在某些情况下,我们可能需要从可执行文件中提取自定义资源并保存为独立的文件。在这篇博客文章中,我们将讨论如何使用C++和WinAPI实现这个目标。

简介

首先,让我们考虑一个场景:我们有一个 Windows 应用程序,其中包含了一个自定义的二进制资源比如默认的配置文件,我们希望将这个资源提取出来并保存为一个独立的文件以用于初始化程序配置项。为了实现这个目标,我们可以使用Windows API提供的相关函数,来完成对资源的释放工作。

关键函数概述

GetModuleHandle

该函数用于获取指定模块的句柄。模块可以是一个可执行文件(例如 .exe 文件)或一个动态链接库(例如 .dll 文件)。该函数返回指定模块的实例句柄,以便在后续的操作中使用。

以下是GetModuleHandle 函数的一般形式:

HMODULE GetModuleHandle(
  LPCTSTR lpModuleName
);

参数说明:

  • lpModuleName:指定要获取句柄的模块的名称。如果为NULL,则返回调用线程的可执行模块句柄。

在许多情况下,GetModuleHandle 主要用于获取当前进程的模块句柄,以便在后续的操作中使用该句柄。模块句柄通常用于在进程中查找资源、定位函数地址等目的。

FindResource

该函数用于定位并返回指定模块(通常是 .exe 或 .dll 文件)中的资源。资源可以是诸如位图、图标、对话框模板、字符串等等的数据。

以下是FindResource 函数的一般形式:

HRSRC FindResource(
  HMODULE hModule,
  LPCTSTR lpName,
  LPCTSTR lpType
);

参数说明:

  • hModule:指定包含资源的模块的句柄。如果为NULL,则表示使用当前可执行模块的句柄。
  • lpName:指定资源的名称或标识符。可以是字符串或整数标识符。
  • lpType:指定资源的类型。通常是一个字符串,如 "RT_BITMAP" 表示位图资源。

如果找到,则返回指向资源的句柄(HRSRC)。这个句柄可以用于后续的资源加载和操作,函数的第二个参数经常配合MAKEINTRESOURCE一起使用,MAKEINTRESOURCE 是一个宏(macro),用于将整数标识符(ID)转换为字符串指针。在 Windows 编程中,通常用于标识资源的 ID。

#define MAKEINTRESOURCE(i) ((LPCTSTR)((DWORD)((WORD)(i))))

这个宏接受一个整数参数i,然后将其转换为字符串指针。在资源标识符上下文中,通常将整数标识符转换为字符串是为了在使用相关资源函数时传递正确的参数。

举个例子,如果有一个字符串资源的标识符是IDR_MYSTRING,则可以使用MAKEINTRESOURCE 将其转换为字符串:

LPCTSTR pszResourceName = MAKEINTRESOURCE(IDR_MYSTRING);

在这里,pszResourceName 将指向字符串 "IDR_MYSTRING"。

在前面提到的FindResource 中,通常将MAKEINTRESOURCE(IDR_MYSTRING) 作为lpName 参数传递给FindResource。这是因为FindResource 函数期望资源名称是字符串类型,而IDR_MYSTRING 可能是一个整数标识符。通过使用MAKEINTRESOURCE,则可以将整数标识符转换为字符串,以便正确地在资源中查找。

SizeofResource

该函数用于获取指定资源的大小。它返回资源的字节数,可以用于确定加载资源所需的内存大小。

以下是SizeofResource 函数的一般形式:

DWORD SizeofResource(
  HMODULE hModule,
  HRSRC   hResInfo
);

参数说明:

  • hModule:指定包含资源的模块的句柄。如果为NULL,则表示使用当前可执行模块的句柄。
  • hResInfo:指定资源的句柄,通常由FindResource 返回。

SizeofResource 返回资源的大小,以字节为单位。这个函数在加载资源之前可以用来分配足够的内存空间。

LoadResource

该函数用于加载指定资源的数据。该函数返回一个全局内存块的句柄,该内存块包含了资源的实际数据,你可以通过LockResource 函数获取该内存块的指针来访问资源数据。

以下是LoadResource 函数的一般形式:

HGLOBAL LoadResource(
  HMODULE hModule,
  HRSRC   hResInfo
);

参数说明:

  • hModule:指定包含资源的模块的句柄。如果为NULL,则表示使用当前可执行模块的句柄。
  • hResInfo:指定资源的句柄,通常由FindResource 返回。

LoadResource 用于将资源数据加载到全局内存块中,并返回该内存块的句柄。在加载资源后,可以使用LockResource 函数获取指向资源数据的指针。

LockResource

用于获取指定资源的数据指针。它接受一个全局内存块的句柄,该内存块通常由LoadResource 函数返回,然后返回一个指向资源数据的指针。

以下是LockResource 函数的一般形式:

LPVOID LockResource(
  HGLOBAL hResData
);

参数说明:

  • hResData:指定资源数据的全局内存块句柄,通常由LoadResource 函数返回。

LockResource 用于锁定指定资源的全局内存块,并返回指向资源数据的指针。请注意,这个函数实际上并不执行拷贝,而是返回指向内存块的指针,因此对返回指针的任何修改都会直接影响到内存块本身。

FreeResource

用于释放由LoadResource 函数加载的资源。这个函数通常用于释放不再需要的资源,以防止资源泄漏。

以下是FreeResource 函数的一般形式:

BOOL FreeResource(
  HGLOBAL hResData
);

参数说明:

  • hResData:指定要释放的全局内存块句柄,通常由LoadResource 函数返回。

FreeResource 用于释放之前由LoadResource 加载的资源。请注意,这个函数通常在资源的生命周期结束时调用,以确保释放资源占用的内存。但在实际应用中,现代 Windows 应用通常不需要显式调用FreeResource,因为 Windows 会在程序退出时自动释放资源。

在实际的应用程序中,FindResource 可以与LoadResourceLockResource 等函数一起使用,用于加载和操作资源数据。当数据资源被加载到内存之后则可以直接通过fwrite函数将其直接写出到磁盘中,以此来实现释放资源的目的。

代码功能实现

首先新建一个控制台程序以作为本次的测试环境,接着准备好我们需要写出的数据,这里就准备一个lyshark.ini配置文件,在项目中右键选择添加并添加资源,此时会弹出如下图所示的提示信息;

此时会弹出添加资源菜单,通过点击导入按钮并输入资源类型为LYSHARK点击确定保存这个更改,如下图所示;

此时我们在主程序中引入#include "resource.h"包含资源头文件,并修改FindResource中的特定位置使其指向我们导入的配置文件,在释放时同样需要保持fopen("map\\lyshark.ini", "wb+")配置文件的格式。

这段资源释放的完整代码如下所示;

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
#include <WinUser.h>
#include "resource.h"

BOOL UseCustomResource()
{
	// 定位我们的自定义资源
	HMODULE hModule = GetModuleHandle(NULL);
	if (hModule == NULL)
	{
		std::cerr << "错误:获取模块句柄失败。" << std::endl;
		return FALSE;
	}

	HRSRC hRsrc = FindResource(hModule, MAKEINTRESOURCE(IDR_LYSHARK1), TEXT("LYSHARK"));
	if (hRsrc == NULL)
	{
		std::cerr << "错误:无法找到资源。" << std::endl;
		return FALSE;
	}

	// 获取资源大小
	DWORD dwSize = SizeofResource(hModule, hRsrc);
	if (dwSize == 0)
	{
		std::cerr << "错误:无效的资源大小。" << std::endl;
		return FALSE;
	}

	// 加载资源
	HGLOBAL hGlobal = LoadResource(hModule, hRsrc);
	if (hGlobal == NULL)
	{
		std::cerr << "错误:无法加载资源。" << std::endl;
		return FALSE;
	}

	// 锁定资源
	LPVOID lpVoid = LockResource(hGlobal);
	if (lpVoid == NULL)
	{
		std::cerr << "错误:无法锁定资源。" << std::endl;
		FreeResource(hGlobal);  // 在返回前释放资源
		return FALSE;
	}

	// 如果不存在,创建一个“map”目录
	if (!CreateDirectory("map", NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
	{
		std::cerr << "错误:无法创建目录。" << std::endl;
		FreeResource(hGlobal);
		return FALSE;
	}

	// 将资源写入文件
	FILE* fp = fopen("map\\lyshark.ini", "wb+");
	if (fp == NULL)
	{
		std::cerr << "错误:无法创建或打开文件。" << std::endl;
		FreeResource(hGlobal);
		return FALSE;
	}

	fwrite(lpVoid, sizeof(char), dwSize, fp);
	fclose(fp);

	// 释放资源
	FreeResource(hGlobal);

	return TRUE;
}

int main(int argc, char* argv[])
{
	BOOL ref = UseCustomResource();
	std::cout << "释放状态: " << ref << std::endl;

	system("pause");
	return 0;
}

以管理员模式运行上述程序,并等待,此时会释放一个目录并包含一个配置文件,如下图所示的输出结果;

结语

通过以上的代码实现,我们成功地将自定义资源提取并保存为一个独立的文件。这种技术在一些特殊情况下可能会很有用,例如需要动态加载或替换资源的情况。希望这篇博客对你理解如何使用 C++ 和 Windows API 进行资源操作有所帮助。