方案 1:从字符串资源和资产文件生成 PRI 文件

在此方案中,我们使用包资源索引 (PRI) API 让新应用代表我们的自定义生成系统。 请记住,此自定义生成系统的目的是为目标 UWP 应用创建 PRI 文件。 因此,作为此演练的一部分,我们将创建一些示例资源文件(包含字符串和其他类型的资源)来代表该目标 UWP 应用的资源。

新建项目

首先在 Microsoft Visual Studio 中创建新项目。 创建 Visual C++ Windows 控制台应用程序项目,然后将其命名为 CBSConsoleApp(“自定义生成系统控制台应用”的简称)。

解决方案平台下拉列表选择 x64

标题、静态库和 dll

MrmResourceIndexer.h 标头文件(该文件安装到 %ProgramFiles(x86)%\Windows Kits\10\Include\<WindowsTargetPlatformVersion>\um\)中已声明 PRI API。 打开文件 CBSConsoleApp.cpp 并将标头与需要的其他标头一同包括在内。

#include <string>
#include <windows.h>
#include <MrmResourceIndexer.h>

API 是在 MrmSupport.dll 中实现的,可通过链接到静态库 MrmSupport.lib 进行访问。 打开项目的属性,单击链接器>输入,编辑 AdditionalDependencies 并添加 MrmSupport.lib

生成解决方案,然后再从 C:\Program Files (x86)\Windows Kits\10\bin\<WindowsTargetPlatformVersion>\x64\MrmSupport.dll 复制到生成输出文件夹(可能为 C:\Users\%USERNAME%\source\repos\CBSConsoleApp\x64\Debug\)。

将下面的 helper 函数添加到 CBSConsoleApp.cpp,因为我们需要它。

inline void ThrowIfFailed(HRESULT hr)
{
	if (FAILED(hr))
	{
		// Set a breakpoint on this line to catch Win32 API errors.
		throw new std::exception();
	}
}

main() 函数中,添加对初始化和取消初始化 COM 的调用。

int main()
{
	::ThrowIfFailed(::CoInitializeEx(nullptr, COINIT_MULTITHREADED));
	
	// More code will go here.
	
	::CoUninitialize();
}

属于目标 UWP 应用的资源文件

现在我们需要一些示例资源文件(包含字符串和其他类型的资源)来代表目标 UWP 应用的资源。 当然,这些内容可以位于文件系统上的任意位置。 但是在本演练中,将它们置于 CBSConsoleApp 的项目文件夹中非常方便,所有内容均在同一位置。 只需将这些资源文件添加到文件系统;不要将其添加到 CBSConsoleApp 项目。

在包含 CBSConsoleApp.vcxproj 的同一文件夹内,添加一个名为 UWPAppProjectRootFolder 的新建子文件夹。 在该新建子文件夹内创建这些示例资源文件。

\UWPAppProjectRootFolder\sample-image.png

此文件可能包含任何 PNG 图像。

\UWPAppProjectRootFolder\resources.resw

<?xml version="1.0"?>
<root>
	<data name="LocalizedString1">
		<value>LocalizedString1-neutral</value>
	</data>
	<data name="LocalizedString2">
		<value>LocalizedString2-neutral</value>
	</data>
	<data name="NeutralOnlyString">
		<value>NeutralOnlyString-neutral</value>
	</data>
</root>

\UWPAppProjectRootFolder\de-DE\resources.resw

<?xml version="1.0"?>
<root>
	<data name="LocalizedString2">
		<value>LocalizedString2-de-DE</value>
	</data>
</root>

\UWPAppProjectRootFolder\en-US\resources.resw

<?xml version="1.0"?>
<root>
	<data name="LocalizedString1">
		<value>LocalizedString1-en-US</value>
	</data>
	<data name="EnOnlyString">
		<value>EnOnlyString-en-US</value>
	</data>
</root>

为资源编制索引并创建 PRI 文件

main() 函数中,调用初始化 COM 之前,声明某些我们需要的字符串,并创建将在其中生成 PRI 文件输出文件夹。

std::wstring projectRootFolderUWPApp{ L"UWPAppProjectRootFolder" };
std::wstring generatedPRIsFolder{ projectRootFolderUWPApp + L"\\Generated PRIs" };
std::wstring filePathPRI{ generatedPRIsFolder + L"\\resources.pri" };
std::wstring filePathPRIDumpBasic{ generatedPRIsFolder + L"\\resources-pri-dump-basic.xml" };

::CreateDirectory(generatedPRIsFolder.c_str(), nullptr);

调用初始化 COM 后立即声明资源索引器图柄,然后调用 MrmCreateResourceIndexer 以创建资源索引器。

MrmResourceIndexerHandle indexer;
::ThrowIfFailed(::MrmCreateResourceIndexer(
	L"OurUWPApp",
	projectRootFolderUWPApp.c_str(),
	MrmPlatformVersion::MrmPlatformVersion_Windows10_0_0_0,
	L"language-en_scale-100_contrast-standard",
	&indexer));

以下是对传递给 MrmCreateResourceIndexer 的参数的说明。

  • 目标 UWP 应用的包系列名称,以后从此资源索引器生成 PRI 文件时将用作资源映射名称。
  • 目标 UWP 应用的项目根。 换言之,即资源文件的路径。 指定此路径,以便可以在后续对同一个资源索引器的 API 调用中指定相对于该根的路径。
  • 希望面向的 Windows 版本。
  • 默认资源限定符列表。
  • 指向资源索引器图柄的指针,以便函数可以对其进行设置。

下一步是将资源添加到刚刚创建的资源索引器中。 resources.resw 是一个包含目标 UWP 应用中性字符串的资源文件 (.resw)。 如果想查看其内容,请向上滚动(在本主题中)。 de-DE\resources.resw 包含德语字符串,en-US\resources.resw 包含英语字符串。 若要将资源文件内的字符串资源添加到资源索引器,请调用 MrmIndexResourceContainerAutoQualifiers。 第三步,向包含资源索引器中性图像资源的文件调用 MrmIndexFile 函数。

::ThrowIfFailed(::MrmIndexResourceContainerAutoQualifiers(indexer, L"resources.resw"));
::ThrowIfFailed(::MrmIndexResourceContainerAutoQualifiers(indexer, L"de-DE\\resources.resw"));
::ThrowIfFailed(::MrmIndexResourceContainerAutoQualifiers(indexer, L"en-US\\resources.resw"));
::ThrowIfFailed(::MrmIndexFile(indexer, L"ms-resource:///Files/sample-image.png", L"sample-image.png", L""));

调用 MrmIndexFile 时,值 L"ms-resource:///Files/sample-image.png" 是资源 uri。 第一个路径段是“Files”,之后从这个资源索引器生成 PRI 文件时,它将用作资源映射子树名称。

在简要介绍了有关资源文件的资源索引器之后,是时候让它通过调用 MrmCreateResourceFile 函数在磁盘上生成 PRI 文件了。

::ThrowIfFailed(::MrmCreateResourceFile(indexer, MrmPackagingModeStandaloneFile, MrmPackagingOptionsNone, generatedPRIsFolder.c_str()));

此时,在一个名为 Generated PRIs 的文件夹中已创建了名为 resources.pri 的 PRI 文件。 现在我们已经完成了资源索引器,调用 MrmDestroyIndexerAndMessages 来破坏其图柄并释放其分配的任何计算机资源。

::ThrowIfFailed(::MrmDestroyIndexerAndMessages(indexer));

由于 PRI 文件是二进制的,如果将二进制 PRI 文件转储为等效 XML,就更易于查看刚才生成的内容。 调用 MrmDumpPriFile 即可实现该操作。

::ThrowIfFailed(::MrmDumpPriFile(filePathPRI.c_str(), nullptr, MrmDumpType::MrmDumpType_Basic, filePathPRIDumpBasic.c_str()));

以下是对传递给 MrmDumpPriFile 的参数的说明。

  • 要转储的 PRI 文件的路径。 此调用中未使用资源索引器(刚刚将其破坏),所以需要指定完整的文件路径。
  • 无架构文件。 稍后将在主题中讨论什么是架构。
  • 仅基本信息。
  • 要创建的 XML 文件的路径。

下面是转储到此处 XML 的 PRI 文件包含的内容。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<PriInfo>
	<ResourceMap name="OurUWPApp" version="1.0" primary="true">
		<Qualifiers>
			<Language>en-US,de-DE</Language>
		</Qualifiers>
		<ResourceMapSubtree name="Files">
			<NamedResource name="sample-image.png" uri="ms-resource://OurUWPApp/Files/sample-image.png">
				<Candidate type="Path">
					<Value>sample-image.png</Value>
				</Candidate>
			</NamedResource>
		</ResourceMapSubtree>
		<ResourceMapSubtree name="resources">
			<NamedResource name="EnOnlyString" uri="ms-resource://OurUWPApp/resources/EnOnlyString">
				<Candidate qualifiers="Language-en-US" isDefault="true" type="String">
					<Value>EnOnlyString-en-US</Value>
				</Candidate>
			</NamedResource>
			<NamedResource name="LocalizedString1" uri="ms-resource://OurUWPApp/resources/LocalizedString1">
				<Candidate qualifiers="Language-en-US" isDefault="true" type="String">
					<Value>LocalizedString1-en-US</Value>
				</Candidate>
				<Candidate type="String">
					<Value>LocalizedString1-neutral</Value>
				</Candidate>
			</NamedResource>
			<NamedResource name="LocalizedString2" uri="ms-resource://OurUWPApp/resources/LocalizedString2">
				<Candidate qualifiers="Language-de-DE" type="String">
					<Value>LocalizedString2-de-DE</Value>
				</Candidate>
				<Candidate type="String">
					<Value>LocalizedString2-neutral</Value>
				</Candidate>
			</NamedResource>
			<NamedResource name="NeutralOnlyString" uri="ms-resource://OurUWPApp/resources/NeutralOnlyString">
				<Candidate type="String">
					<Value>NeutralOnlyString-neutral</Value>
				</Candidate>
			</NamedResource>
		</ResourceMapSubtree>
	</ResourceMap>
</PriInfo>

信息以资源映射开始,它是用目标 UWP 应用的包系列名称命名的。 资源映射包含两个资源映射子树:一个用于已编制索引的文件资源,另一个用于字符串资源。 请注意包系列名称是如何插入到所有资源 URI 中的。

第一个字符串资源是来自 en-US\resources.reswEnOnlyString,它仅有一个候选项(与 language-en-US 限定符匹配)。 接下来是来自 resources.reswen-US\resources.reswLocalizedString1。 因此,它有两个候选项:一个与 language-en-US 匹配,另一个回退与任何上下文匹配的中性候选项。 同样,LocalizedString2 有两个候选项:language-de-DE 和中性。 最后,NeutralOnlyString 仅以中性形式存在。 将其命名为该名称以明确不应对它进行本地化。

总结

在此方案中,我们介绍了如何使用包资源索引 (PRI) API 创建资源索引器。 我们将字符串资源和资产文件添加到资源索引器。 然后,使用资源索引器生成二进制 PRI 文件。 最后,我们以 XML 形式转储二进制 PRI 文件,以便可以确认其中含有预期的信息。

重要的 API