65.9K
CodeProject 正在变化。 阅读更多。
Home

在 VCF 中使用资源

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (6投票s)

2006 年 4 月 19 日

11分钟阅读

viewsIcon

40251

downloadIcon

446

一篇关于使用 VCF 为您的应用程序添加和使用资源的教程。

引言

大多数 Win32 应用程序都使用某种形式的资源,例如对话框、字符串表、图像(位图)、图标等。本文将向您展示如何在 VCF 程序中使用资源。

VCF 将资源视为一个二进制数据块。这些数据可以被当作原始字节流、字符串或图像进行处理。对 VCF 称之为“可视化窗体文件”的支持也存在,但数据本身仍然是纯文本。

VCF 通过一个名为 ResourceBundle 的类来提供对资源的访问,该类封装了实际获取资源数据的底层细节。每个应用程序或动态链接库都有一个 `ResourceBundle`。`ResourceBundle` 的基类仅将资源作为原始二进制数据或文本处理,而 GraphicsResourceBundle 则增加了对图像资源的支持。

ResourceBundle 使用其对等类来实现其功能。对等类特定于每个平台,它通过调用 `FindResource` 和 `LoadString` 等函数来实现提取资源数据的细节。

传统上,您会将资源数据存储在零个或多个 `.rc` 脚本和可能的外部文件中(用于图像等)。然后,这些文件会被编译并链接到最终的可执行文件中。VCF `ResourceBundle` 完全能够访问以这种方式构建的资源,但是,它也可以处理以 Bundle 形式存储的资源。Bundles 是定义各种资源文件位置的目录结构,它们不直接绑定到可执行文件,而是与程序一起重新分发。这个想法借鉴了 OS X 的 Bundle 概念,并增加了一个稍微更灵活的目录结构。因此,在 VCF 中,您可以选择如何实现资源存储,但从开发者的角度来看,您不必关心——`ResourceBundle` 会首先尝试使用本机 API(如 `LoadString`、`FindResource` 等)进行检查,如果无法通过这种方式找到资源,它将尝试使用 Bundle 机制加载资源。

Bundles 的布局可能如下所示

App Directory/
 MyApp.exe
 Resources/
   MyApp.strings
       img1.png
       img2.bmp
       notes.txt
       readme.txt

您可以拥有更复杂的布局,还可以支持本地化资源。您只需在“Resources”目录下的每个区域设置创建一个子目录,使用标准的 ISO 语言和国家名称。在每个目录中,您放置相应本地化的资源文件。例如,这将支持美式英语、意大利语和波兰语

App Directory/
 MyApp.exe
 Resources/
       en_US/
         MyApp.strings
         img1.png
         img2.bmp
         notes.txt
         readme.txt
       it_IT/
         MyApp.strings
         img1.png
         img2.bmp
         notes.txt
         readme.txt
       pl_PL/
         MyApp.strings
         img1.png
         img2.bmp
         notes.txt
         readme.txt

当您请求资源时,VCF 将检查当前线程的区域设置,然后检查是否存在本地化资源目录。

用法

使用 `ResourceBundle` 非常简单。首先,您需要获得对它的访问权限,因为您不直接创建实例,而是由框架管理的。要访问它,只需调用

System::getResourceBundle

或者,如果您有一个使用 `ApplicationKit` 的应用程序

Application::getRuningInstance()->getResourceBundle()

一旦您有了 `ResourceBundle`,就可以调用以下函数

如果您正在使用 `GraphicsKit` 或 `ApplicationKit`,还可以调用 GraphicsResourceBundle::getImage() 来访问图像。图像可以存储在框架当前理解的任何格式中。默认格式是 JPEG 和 PNG,在 Windows 下还支持 BMP。

访问原始资源数据

您可以通过调用 `ResourceBundle` 实例的 `getResource()` 函数来访问原始资源数据。这将返回一个 `Resource` 类的实例,该类允许您访问原始数据。`Resource` 类具有返回资源字节数、数据指针和资源名称的函数。您可以通过以下方式访问它

Resource* resource = 
   System::getResourceBundle()->getResource( "some-resource" );
if ( NULL != resource ) {
       void* dataPtr = resource->getData();
       String name = resource->getName();
       uint32 dataSize = resource->getDataSize();
}

框架将首先在可执行文件的资源中查找,然后转而使用 Bundle API 查找资源。当它搜索可执行文件的资源时,它会在找到第一个名称匹配所请求资源名称的资源处停止。任何资源都可以匹配,搜索不考虑资源类型。这意味着您的资源可以是 BMP、某种形式的 `RCDATA`,或者其他任何东西。在 Windows 上,搜索是通过 `EnumResourceTypes` API 函数执行的。

只要不是字符串表中的字符串且名称不重复,.RC 脚本中指定的任何资源都可以通过这种方式访问。

将资源存储在 Bundle 中

Bundle 中的资源可以通过这种方式访问,只要用于标识资源的名称是文件名即可。例如

Resource* resource = 
     System::getResourceBundle()->getResource( "stuff.dat" );
if ( NULL != resource ) {
       void* dataPtr = resource->getData();
       String name = resource->getName();
       uint32 dataSize = resource->getDataSize();
}

假设文件 `stuff.dat` 存在于正确的目录中,返回的资源将是文件中的数据。一些可能的目录结构是

App Directory/
   MyApp.exe
   Resources/
        stuff.dat

App Directory/
   Contents/
       MyApp.exe
       Resources/
           stuff.dat

App Directory/
    Contents/
       (OS Name)/
           MyApp.exe
                Resources/
                    stuff.dat

VCF 可以处理所有这些模式。最后两个与 OSX 100% 兼容。第一个是最简单的方法。

需要注意的一点是:将资源同时存储在可执行文件中(使用 RC 脚本)并在 Bundle 目录树中存储为文件是完全可接受的。只要资源名称唯一,就不会出现冲突。如果名称不唯一,则优先匹配与 Bundle 资源相比的本机可执行文件中的资源。

访问文本资源

要访问资源中的字符串,我们将这样做(假设我们只使用 `FoundationKit`)

#define RES_NAME        "test"
String resStr = System::getResourceBundle()->getString( RES_NAME );
System::println( resStr );

如前所述,您可以将资源存储在多种方式中,可以是框架读取的普通文件,或者,如果您在 Windows 上,则可以通过使用 `.rc` 脚本。让我们先看看如何在资源脚本中表示数据,然后看看如何使用 Bundles 和纯文件来实现。

将文本存储在 RC 文件中

将数据存储在 RC 脚本中最简单的方法是使用 `RCDATA` 资源类型。前面提到的字符串将这样存储

test RCDATA
BEGIN
"A Test String"
END

其中“test”是资源名称,“RCDATA”是资源类型,“BEGIN”和“END”用于标记资源数据的开始和结束位置。您可以将字符串放在引号中。如果将其编译并链接到可执行文件中,VCF 将能够在运行时加载这些数据。

另一种方法是使用字符串表。这通常是 Win32 或 MFC 应用程序中存储字符串的方式。字符串表条目表示如下

STRINGTABLE DISCARDABLE
BEGIN
   IDS_STRING1             "A Test String"
END

与 `RCDATA` 不同,字符串的 ID 值是一个整数,该值必须直接在 `rc` 脚本中定义、从另一个文件(如头文件)包含,或者直接使用 ID 号。在我们的例子中,如果我们用数字替换“IDS_STRING1”,那么我们就无需担心包含资源头文件或定义符号。所以我们的资源文件变成

STRINGTABLE DISCARDABLE
BEGIN
   1             "A Test String"
END

在找不到资源名称的其他匹配项时,框架可以使用基于字符串表的资源。在这种情况下,Windows 代码将假定资源名称是数字,并尝试将其转换为整数,然后调用 Win32 `LoadString()` 函数,该函数将从字符串表中提取字符串。所以如果我们改变代码

#define RES_NAME        "1"
String resStr = System::getResourceBundle()->getString( RES_NAME );
System::println( resStr );

resStr 将从可执行文件的字符串表中被赋值为“A Test String”。

将文本存储在 .strings 文件中

存储字符串的最后一种方法是使用 `.strings` 文件和前面提到的 Bundle 目录布局。此文件始终命名为您的可执行文件名加上“`.strings`”扩展名。因此,如果您的程序名为“FooBar.exe”,则关联的 `.strings` 文件将名为“`FooBar.strings`”。`.strings` 文件是一个纯文本文件,包含任意数量的条目,其中一个条目是用双引号括起来的键或 ID 名称,后跟“=”字符,然后是条目值字符串,同样用双引号括起来。例如,我们的 `FooBar.strings` 文件内容可能是

"test" = "A Test String"

我们之前使用的相同代码也可以与此配合使用,从而可以省去 RC 脚本中的代码。这种方法的优点是易于本地化。

ID(或资源名称)和值的文本都可以包含您想要的任何字符。支持的转义字符是

  • \n
  • \r
  • \t
  • \\
  • \"
  • \'
  • \?

十六进制、八进制和 Unicode 转义序列也以以下形式支持

  • \x 后跟正好三个十六进制数字,例如 \x0FD。这些可以根据需要进行 0 填充。
  • \XXX 三个八进制数字,例如 \123。
  • \U 后跟正好四个十六进制数字,例如 \U043E。这些可以根据需要进行 0 填充。

访问图像资源

图像资源的访问方式与二进制或文本资源几乎相同。唯一的区别是您需要链接到 `GraphicsKit`(或 `ApplicationKit`)才能访问这些功能。图像资源通过 `GraphicsResourceBundle` 类提供。`GraphicsResourceBundle` 的访问方式如下(它也是一个单例)

GraphicsResourceBundle* bundle = 
      GraphicsToolkit::getResourceBundle();

这会返回可执行文件的资源包,就像 `System::getResourceBundle()` 所做的一样。

一旦有了资源包,您就可以获取图像资源

Image* image = bundle->getImage( "someImageName" );

这将返回一个图像的新实例(您需要在稍后负责释放它),或者如果按指定名称找不到资源,则返回 `NULL`。

与文本和二进制数据一样,图像可以存储在可执行文件中,也可以作为外部文件。让我们先看看如何将它们存储在 RC 脚本中。

将图像存储在 RC 文件中

如果您将图像存储在 RC 文件中,则仅限于位图(`.BMP`)、图标(`.ICO`)或光标(`.CUR`)类型。要将这些添加到您的脚本中,请添加如下一行

play    BITMAP  "play.bmp"

第一项是资源名称,下一项是资源类型(`BITMAP`、`ICON` 或 `CURSOR`),最后一项是相对于 RC 脚本所在位置的文件名,用于编译到可执行文件中。

将图像存储在外部

存储图像的另一种方法是使用前面提到的 Bundle 目录布局将它们作为外部文件存储。

从动态或共享库访问资源

到目前为止,我们一直假设资源包的来源是可执行文件。但是,将资源绑定到 DLL 是完全可能的,特别是如果 DLL 是某种插件。为此,您需要像往常一样创建您的 DLL。您可以像往常一样将资源添加到 DLL 的 RC 脚本中。然后,在您的 DLL 的启动代码中,您需要创建一个 `LibraryApplication` 类的单个实例。这将代表已加载的 DLL 并提供对 DLL 资源的访问。创建实例后,您需要给它一个名称,然后注册 `LibraryApplication` 实例。从那里开始,您可以通过调用 `LibraryApplication::getRegisteredLibraryApplication()` 并传入适当的库名称来访问此实例。例如

extern "C" __decspec(__dllexport) void _vpl_init(OSHandleID handle)
{
   LibraryApplication* libApp = new VCF::LibraryApplication();

   libApp->getPeer()->setHandleID( handle );
   libApp->setName( "MyLib" );
   LibraryApplication::registerLibrary( libApp );
}

extern "C" __decspec(__dllexport) void _vpl_terminate(OSHandleID handle)
{
   LibraryApplication* libApp = 
     LibraryApplication::getRegisteredLibraryApplication( "MyLib" );
   if ( NULL != libApp ) {
           LibraryApplication::unRegisterLibrary( libApp );
           libApp->free();
   }
}

我们导出了两个函数,一个在 DLL 通过 `Library` 类加载时由框架调用,另一个在 DLL 卸载时由框架调用。如果您愿意,也可以将代码放在 `DllMain` 中。

第一个函数 `_vpl_init()` 创建 `LibraryApplication` 实例,设置库的对等句柄,并设置库的名称。第二个函数 `_vpl_terminate()` 检索库实例,取消注册它,然后删除它。

要从库应用程序或 DLL 访问资源,只需获取您感兴趣的 `LibraryApplication` 实例,然后调用 `LibraryApplication::getResourceBundle()`,例如

LibraryApplication* libApp = 
  LibraryApplication::getRegisteredLibraryApplication( "MyLib" );
Image* img = 
  libApp->getResourceBundle()->getImage( "donald-duck" );

程序信息作为资源

VCF 中可用的最后一种资源类型是程序信息。基本上,它是一系列描述可执行文件的各种字符串,例如可执行文件的名称、版本、作者、版权等。您可以通过调用 `ResourceBundle::getProgramInfo()` 函数来访问此信息。如果没有任何 `ProgramInfo` 可用,则 `ResourceBundle::getProgramInfo()` 将返回 `NULL`。例如

ProgramInfo* info = System::getResourceBundle()->getProgramInfo();

if ( NULL != info ) {
       System::println( info->getProgramName() );
       System::println( info->getAuthor() );
       System::println( info->getCopyright() );
}

至少在 Windows 上,程序信息可以通过两种方式提供。第一种方法是使用嵌入到 RC 脚本中的 `VS_VERSION_INFO` 资源类型中已有的信息。例如

VS_VERSION_INFO VERSIONINFO
 FILEVERSION 1,0,0,1
 PRODUCTVERSION 1,0,0,1
 FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x40004L
 FILETYPE 0x1L
 FILESUBTYPE 0x0L
BEGIN
   BLOCK "StringFileInfo"
   BEGIN
       BLOCK "040904b0"
       BEGIN
           VALUE "Comments", "\0"
           VALUE "CompanyName", "Nose Picker's Anonymous Inc.\0"
           VALUE "FileDescription", "VersInfo\0"
           VALUE "FileVersion", "1, 0, 0, 1\0"
           VALUE "InternalName", "VersInfo\0"
           VALUE "LegalCopyright", "Copyright ? 2006\0"
           VALUE "LegalTrademarks", "\0"
           VALUE "OriginalFilename", "VersInfo.exe\0"
           VALUE "PrivateBuild", "\0"
           VALUE "ProductName", "Nose Picker's Anonymous Inc. VersInfo\0"
           VALUE "ProductVersion", "1, 0, 0, 1\0"
           VALUE "SpecialBuild", "\0"
       END
   END
   BLOCK "VarFileInfo"
   BEGIN
       VALUE "Translation", 0x409, 1200
   END
END

这些信息可以被提取并提供给 `ProgramInfo` 实例。

提供此信息的第二种方法是使用外部文件。该文件放置在与程序“Resources”目录相同的级别,名为“Info.xml”或“Info.plist”。其思想是以基于 XML 的格式存储类似信息,然后由 `ResourceBundle` 读取。文件格式如下所示

<?xml version="1.0" encoding="UTF-8"?>
<plist >
   <dict >
       <key >
            Executable
       </key>
       <string >
            VersInfo.exe
       </string>
       <key >
            ProgramVersion
       </key>
       <string >
            1.0
       </string>
       <key >
            FileVersion
       </key>
       <string >
            1.0
       </string>
       <key >
            ProductName
       </key>
       <string >
            VersInfo
       </string>
       <key >
           Copyright
       </key>
       <string >
           Copyright ? 2006
       </string>
       <key >
           Author
       </key>
       <string >
           Jim Crafton
       </string>
       <key >
           Company
       </key>
       <string >
           Nose Picker's Anonymous Inc.
       </string>
   </dict>
</plist>

关于构建示例的说明

您需要安装最新版本的 VCF(至少 0-9-0 或更高版本),并且需要确保您已构建 VCF 的静态库(而不是 DLL 版本)。示例配置为静态链接到 VCF。有关构建 VCF 的更多文档,请参阅:Building the VCF,在 VCF 在线文档中。

结论

我们已经涵盖了所有基本的资源操作,并展示了如何提取图像、文件、原始二进制数据和文本。

欢迎就框架提出问题,您可以将其发布在此处或在我们的 论坛中。如果您对如何改进这些方面有任何建议,我们非常乐意听取!

© . All rights reserved.