Windows 应用中的程序包标识符概述
程序包标识符是跨空间和时间的唯一标识符。 正如 DNA 唯一标识你一样,程序包标识符也可唯一标识包。
一个包具有一组相关联的位(文件等)。 没有两个包具有相同的标识符,并且对与包关联的位的任何更改都需要不同的标识符。
什么是程序包标识符?
程序包标识符是一种唯一标识包的逻辑构造。 该标识符由 5 部分构成:
- 名称:这是应用开发人员选择的名称。 Microsoft Store 要求应用商店内所有应用开发人员的所有应用名称具有唯一性,但在一般生态系统中,不保证名称是唯一的。
- 版本:包的版本号。 应用开发人员可以选择任意版本号,但必须确保版本号随更新而增大。
- 体系结构:包所面向的处理器体系结构。 可面向不同的处理器体系结构生成相同的应用,每个生成都驻留在自己的包中。
- 资源 ID:应用开发人员选择用于唯一标识资源包的字符串,例如不同的语言或不同的显示比例。 资源包通常与体系结构无关。 对于捆绑包,资源 ID 始终为
~
。 - 发布者:应用开发人员的使用者名称,由其签名证书标识。 理论上,这对于每个应用开发人员都是唯一的,因为声誉良好的证书颁发机构使用唯一的真实名称和身份来填充证书的使用者名称字段。
此构造有时称为由 5 部分构成的元组。
注意
未签名的包 (1) 仍然需要发布者,(2) 发布者必须包含“未签名”标记 (OID.2.25.311729368913984317654407730594956997722=1),(3)“未签名”标记必须是“发布者”字符串中的最后一个字段,(4) 未签名的包没有证书或签名。
程序包标识符字段限制
字段 | 数据类型 | 限制 | 注释 |
---|---|---|---|
名称 | 包字符串 | 最小值:3 最大值:50 |
每个验证 API 允许的值(请参阅包字符串) |
版本 | DotQuad | 最小值:0.0.0.0 最大值:65535.65535.65535.65535 |
字符串形式采用点分十进制表示法“Major.Minor.Build.Revision” |
体系结构 | 枚举 | 最小值:无 最大值:无 |
允许的值为“neutral”、“x86”、“x64”、“arm”、“arm64”、“x86a64” |
ResourceId | 包字符串 | 最小值:0 最大值:30 |
每个验证 API 允许的值(请参阅包字符串) |
Publisher | String | 最小值:1 最大值:8192 |
X.509 允许的值 |
PublisherId | String | 最小值:13 最大值:13 |
Base32 编码 Crockford 变体,如 [a-hjkmnp-tv-z0-9] |
什么是“包字符串”?
包字符串是允许使用以下字符的字符串:
- 允许的输入字符(ASCII 子集)
- 大写字母(U+0041 到 U+005A)
- 小写字母(U+0061 到 U+007A)
- 数字(U+0030 到 U+0039)
- 点 (U+002E)
- 短划线 (U+002D)
禁止将以下值用作包字符串:
条件 | 禁止的值 |
---|---|
不能相等 | “.”、“..”、“con”、“prn”、“aux”、“nul”、“com1”、“com2”、“com3”、“com4”、“com5”、“com6”、“com7”、“com8”、“com9”、“lpt1”、“lpt2”、“lpt3”、“lpt4”、“lpt5”、“lpt6”、“lpt7”、“lpt8”、“lpt9” |
不能作为开头 | “con.”、“prn.”、“aux.”、“nul.”、“com1.”、“com2.”、“com3.”、“com4.”、“com5.”、“com6.”、“com7.”、“com8.”、“com9.”、“lpt1.”、“lpt2.”、“lpt3.”、“lpt4.”、“lpt5.”、“lpt6.”、“lpt7.”、“lpt8.”、“lpt9.”、“xn--” |
不能作为结尾 | [.] |
不能包含 | “.xn--” |
字符串必须使用不区分大小写的序号字符串比较 API(如 _wcsicmp)进行比较。
程序包标识符的 name
和 resourceid
字段是包字符串。
PackageId 对象
PackageId 对象包含由 5 部分构成的元组,这 5 个部分均是单独的字段(Name
、Version
、Architecture
、ResourceId
、Publisher
)。
包全名
包全名是不透明的字符串,派生自程序包标识符的所有 5 个部分(名称、版本、体系结构、资源 ID、发布者)
<Name>_<Version>_<Architecture>_<ResourceId>_<PublisherId>
例如,Windows 照片应用的某个包全名为“Microsoft.Windows.Photos_2020.20090.1002.0_x64__8wekyb3d8bbwe”,其中“Microsoft.Windows.Photos”是名称,“2020.20090.1002.0”是版本号,“x64”是目标处理器体系结构,资源 ID 为空(后面两个下划线之间没有内容),“8wekyb3d8bbwe”是 Microsoft 的发布者 ID。
包全名可唯一标识 MSIX 包或捆绑包。 如果两个包或捆绑包的内容不同,但包全名相同,则会出现错误。
注意
MSIX 是以前的术语 APPX 的新名称。 有关详细信息,请参阅什么是 MSIX?
包系列名称
包系列名称是一个不透明的字符串,只派生自程序包标识符的两个部分(即名称和发布者):
<Name>_<PublisherId>
例如,Windows 照片应用的包系列名称为“Microsoft.Windows.Photos_8wekyb3d8bbwe”,其中“Microsoft.Windows.Photos”是名称,“8wekyb3d8bbwe”是 Microsoft 的发布者 ID。
包系列名称通常称为“无版本包全名”。
注意
严格意义上,这并不准确,因为包系列名称也缺少体系结构和资源 ID。
注意
数据和安全性通常限定在包系列范围内。 例如,如果你将通过记事本版本 1.0.0.0 包安装的记事本应用配置为启用 Wordwrap,则体验会不佳。 然后,记事本后来更新到 1.0.0.1,你的配置数据不会转移到该包的新版本中。
发布者 ID
包系列名称是采用以下格式的字符串:
<name>_<publisherid>
其中的发布者 ID 具有一些特定属性:
- 派生自发布者
- 最小长度 = 最大长度 = 13 个字符 [固定大小]
- 允许的字符(正则表达式)= a-hj-km-np-tv-z0-9
- Base-32 Crockford 变体,即字母数字 (A-Z0-9),但没有 I (eye)、L (ell)、O (oh) 或 U (you)
- 用于比较的不区分大小写的序号 --- ABCDEFABCDEFG == abcdefabcdefg
因此,发布者 ID 中永远不会出现 % : \ / " ? 或其他字符。
有关更多详细信息,请参阅 PackageFamilyNameFromId 和 PackageNameAndPublisherIdFromFamilyName。
发布者 ID 通常称为 PublisherId。
为什么存在发布者 ID?
发布者 ID 存在的原因是发布者需要与证书的 X.509 名称/签名者匹配,因此:
- 它可能非常大(长度 <= 8192 个字符)
- 它可能包括难处理或受限的字符(反斜杠等)
这些问题可能会使某些 X.509 字符串难以处理或无法在文件系统、注册表、URL 和其他上下文中使用。
如何创建 PublisherId?
使用 PackageNameAndPublisherIdFromFamilyName 从 PackageFamilyName
中提取 PublisherId
。
使用 PackageIdFromFullName 从 PackageFullName
中提取 PublisherId
。
很少需要从 Publisher
创建 PublisherId
,但也可使用可用的 API 来完成此操作:
#include <appmodel.h>
HRESULT PublisherIdFromPublisher(
_In_ PCWSTR publisher,
_Out_writes_(PACKAGE_PUBLISHERID_MAX_LENGTH + 1) PWSTR publisherId)
{
PCWSTR name{ L"xyz" };
const size_t nameLength{ ARRAYSIZE(L"xyz") - 1 };
const size_t offsetToPublisherId{ name + 1 }; // xyz_...publisherid...
PACKAGE_ID id{};
id.name = name;
id.publisher = publisher;
WCHAR familyName[PACKAGE_PUBLISHERID_MAX_LENGTH + 1]{};
UINT32 n{ ARRAYSIZE(familyName) };
RETURN_IF_WIN32_ERROR(PackageFamilyNameFromId(&id, &n, familyName);
RETURN_IF_FAILED(StringCchCopyW(publisherId, PACKAGE_PUBLISHERID_MAX_LENGTH + 1, familyName + offsetToPublisherId));
return S_OK;
}
下面是同一操作的经典 Windows C 实现:
#include <appmodel.h>
HRESULT PublisherIdFromPublisher(
_In_ PCWSTR publisher,
_Out_writes_(PACKAGE_PUBLISHERID_MAX_LENGTH + 1) PWSTR publisherId)
{
const WCHAR c_name[]{ L"xyz" };
const UINT32 c_nameLength{ ARRAYSIZE(c_nameForPublisherToPublisherId) - 1 };
PACKAGE_ID id{};
id.name = c_name;
id.publisher = publisher;
WCHAR familyName[PACKAGE_PUBLISHERID_MAX_LENGTH + 1]{};
UINT32 n{ ARRAYSIZE(familyName) };
RETURN_IF_WIN32_ERROR(PackageFamilyNameFromId(&id, &n, familyName));
RETURN_IF_FAILED(StringCchCopyW(publisherId, PACKAGE_PUBLISHERID_MAX_LENGTH + 1, familyName + c_nameLength + 1);
return S_OK;
}
这通过将包 ID 转换为包系列名称来创建 PublisherId,最后生成的格式为 xyz_<publisherid>
。 此方法稳定可靠。
这只需要使用 SDK 中的 appmodel.h 进行编译,并使用 kernel32.lib 进行链接(如果使用的是 APIsets,则使用 kernelbase.lib、onecore.lib 或 api-ms-win-appmodel-runtime-l1.lib)。
了解程序包标识符中的处理器体系结构
常见的一个误解是 Architecture=x64
表示该包只能包含 x64 代码。 这不正确。 这表示该包适用于支持 x64 代码的系统,并且可由 x64 应用使用。 你可创建一个只包含 PDF 文件的包,但需使用 <Identity Architecture=x64...>
声明它,因为它仅用于安装在 x64 兼容的系统上(例如,x64 包只能安装在 x64 和 [从 Windows 11 起] Arm64 系统上,因为 x86、Arm 和 Windows 10 Arm64 系统不支持 x64)。
更让人误解的是,Architecture=neutral
并不表示该包不包含可执行代码。 而是表示该包适用于所有体系结构。 例如,你可创建一个包含用 JavaScript、Python、C# 等编写的 AES 加密 API 的包,但其性能在 Arm64 系统上是不可接受的。 因此,你包含经过优化的 Arm64 二进制文件并实现 API 来处理它:
void Encrypt(...)
{
HANDLE h{};
if (GetCpu() == arm64)
{
h = LoadLibrary(GetCurrentPackagePath() + "\bin\encrypt-arm64.dll")
p = GetProcAddress(h, "Encrypt")
return (*p)(...)
}
else
{
// ...call other implementation...
}
}
或可创建具有多个变体的中性包:
\
bin\
encrypt-x86.dll
encrypt-x64.dll
encrypt-arm.dll
encrypt-arm64.dll
然后,开发人员可以通过 LoadLibrary("bin\encrypt-" + cpu + ".dll")
在运行时为其进程获取相应的二进制文件。
通常,中性包不包含每个体系结构的内容,但它们可以包含。 可以执行的操作是有限制的(例如,可以创建一个包含为 x86 + x64 + arm + arm64 编译的 notepad.exe 的记事本包,但 appxmanifest.xml 只能声明指向其中之一的 <Application Executable=...>
)。 已知有些捆绑包支持只安装所需的位,这是一个十分不常见的做法。 但这并不是非法的,只是一种先进和新奇的做法。
此外,Architecture=x86
(或 x64|arm|arm64)并不表示该包只包含指定体系结构的可执行代码。 这只是极其常见的情况。
注意
在此上下文中讨论“代码”或“可执行代码”时,我们指的是可移植可执行 (PE) 文件。
程序包标识符是否区分大小写?
大多数情况下不区分,但 Publisher
区分大小写。
其余字段(Name
、ResourceId
、PublisherId
、PackageFullName
和 PackageFamilyName
)不区分。 这些字段将保留大小写,但在比较时不区分大小写。