C/C++ 实现动态资源文件释放

简介: 当我们开发Windows应用程序时,通常会涉及到使用资源(Resource)的情况。资源可以包括图标、位图、字符串等,它们以二进制形式嵌入到可执行文件中。在某些情况下,我们可能需要从可执行文件中提取自定义资源并保存为独立的文件。在这篇博客文章中,我们将讨论如何使用C++和WinAPI实现这个目标。首先,让我们考虑一个场景:我们有一个 Windows 应用程序,其中包含了一个自定义的二进制资源比如默认的配置文件,我们希望将这个资源提取出来并保存为一个独立的文件以用于初始化程序配置项。为了实现这个目标,我们可以使用`Windows API`提供的相关函数,来完成对资源的释放工作。

当我们开发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 进行资源操作有所帮助。

目录
相关文章
|
1月前
|
监控 Java API
【C/C++ 线程池设计思路】如何在C++跨平台应用中精准调节线程池:一个动态适应策略的实践指南
【C/C++ 线程池设计思路】如何在C++跨平台应用中精准调节线程池:一个动态适应策略的实践指南
112 0
|
5月前
|
安全 编译器 程序员
25 C++ - 动态对象创建
25 C++ - 动态对象创建
29 0
|
6月前
|
编译器 Linux C语言
使用new/delete动态管理内存【C/C++内存分布】
使用new/delete动态管理内存【C/C++内存分布】
41 0
|
7月前
|
存储 编译器 C++
c++ 有趣的动态转换之 delete 崩溃探究兼谈基类虚析构的重要性
c++ 有趣的动态转换之 delete 崩溃探究兼谈基类虚析构的重要性
|
9月前
|
C语言 C++ iOS开发
iOS中C++静态全局变量的动态初始化时序
一个由于C++初始化失败导致Realm初始化失败的Crash
138 1
|
存储 C++ 开发者
你还不进来看看C++类与对象【7】 —— 动态多态底层原理剖析&&(纯)虚析构解决父类指针不能释放子类属性问题嘛
你还不进来看看C++类与对象【7】 —— 动态多态底层原理剖析&&(纯)虚析构解决父类指针不能释放子类属性问题嘛
110 0
你还不进来看看C++类与对象【7】 —— 动态多态底层原理剖析&&(纯)虚析构解决父类指针不能释放子类属性问题嘛
|
存储 程序员 C语言
如何进行C++动态转换
如何进行C++动态转换
如何进行C++动态转换
|
存储 编译器 C++
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(二)
虚表是编译器的实现,而非C++的语言标准。上一章我们学习了多态的概念,本章我们深入探讨一下多态的原理。文章开头先说虚表指针,观察编译器的查表行为。首次观察我们先从监视窗口观察美化后的虚表 _vfptr,再透过内存窗口观察真实的 _vfptr。我们还会探讨为什么对象也能切片却不能实现多态的问题。对于虚表到底存在哪?我们会带着大家通过一些打印虚表的方式进行比对!铺垫完虚表的知识后,会讲解运行时决议与编译时决议,穿插动静态的知识点。文章的最后我们会探讨单继承与多继承的虚表,多继承中的虚表神奇的切片指针偏移问题,这块难度较大,后续我们会考虑专门讲解一下,顺带着把钻石虚拟继承给讲了
264 1
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(二)
|
存储 编译器 C++
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(一)
虚表是编译器的实现,而非C++的语言标准。上一章我们学习了多态的概念,本章我们深入探讨一下多态的原理。文章开头先说虚表指针,观察编译器的查表行为。首次观察我们先从监视窗口观察美化后的虚表 _vfptr,再透过内存窗口观察真实的 _vfptr。我们还会探讨为什么对象也能切片却不能实现多态的问题。对于虚表到底存在哪?我们会带着大家通过一些打印虚表的方式进行比对!铺垫完虚表的知识后,会讲解运行时决议与编译时决议,穿插动静态的知识点。文章的最后我们会探讨单继承与多继承的虚表,多继承中的虚表神奇的切片指针偏移问题,这块难度较大,后续我们会考虑专门讲解一下,顺带着把钻石虚拟继承给讲了
423 0
【C++要笑着学】虚函数表(VBTL) | 观察虚表指针 | 运行时决议与编译时决议 | 动态绑定与静态绑定 | 静态多态与动态多态 | 单继承与多继承关系的虚表(一)
|
22小时前
|
存储 Java C++
【C++类和对象】探索static成员、友元以及内部类
【C++类和对象】探索static成员、友元以及内部类