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

使用 C++ 从清单中检索程序集标识

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (19投票s)

2010年6月17日

CPOL

8分钟阅读

viewsIcon

50862

downloadIcon

1162

一组 C++ 接口,用于检索嵌入在映像文件中的清单的并行信息。

目标

本文概述了 Windows 并行程序集技术,然后介绍了一组 C++ 接口,您可以使用这些接口以编程方式从嵌入在任何映像文件中的清单中检索与并行相关的。信息。

Classes_diagram.png

引言

自 Windows XP 以来,操作系统中一项新功能得以提供,该功能使动态链接库的不同版本能够共存于同一系统上,而不会相互冲突。这项名为“并行程序集”的功能解决了被称为“DLL Hell”的问题,在该问题中,一个库可能会覆盖另一个版本(更新或更旧),而不管是否有任何应用程序仍然需要以前的版本。

是否注意到“\Windows\WinSxS”文件夹的大小?根据 Windows 的版本(家庭版、旗舰版、服务器版等)以及安装的应用程序,它的大小最多可达数 GB 甚至更多,包含 20,000 个文件夹和 80,000 个文件!仔细查看 WinSxS 托管的目录内容,您会发现其中许多目录存储了相同的动态链接库,每个库具有相同的名称但路径不同。

例如,我的系统的并行存储中存在七个不同版本(8.0.7600.xxxxx)的 IEFRAME.DLL 库。所有这些都有可能被使用不同版本的此库构建的任何应用程序使用。

comctl32_found_on_my_system.png

程序集缓存

WinSxS 目录有时被称为程序集缓存并行存储。从 Windows Vista 和 Windows Server 2008 开始,它被称为组件服务基础结构 (CSI) 组件存储。

此存储不仅托管并行程序集(动态链接库),还托管其他类型的文件,如所有标准的 Windows 内置应用程序、清单,甚至帮助文件。

“操作系统中的所有组件都可以在 WinSxS 文件夹中找到 - 事实上,我们将此位置称为组件存储。每个组件都有一个唯一的名称,其中包含为其构建的版本、语言和处理器体系结构。WinSxS 文件夹是系统上组件的唯一位置;您在系统上看到的文件的所有其他实例都是从组件存储“投影”出来的,通过硬链接……”

(http://blogs.technet.com/b/askcore/archive/2008/09/17/what-is-the-winsxs-directory-in-windows-2008-and-windows-vista-and-why-is-it-so-large.aspx)

WinSxS 感知应用程序

为了使用位于并行存储中的组件,应用程序必须使用一组特殊标签进行编译。当应用程序启动时,Windows 加载程序会分析映像文件中导入地址表 (IAT) 中声明的应用程序依赖项,然后并行管理器会尝试定位引用的动态链接库。

使应用程序支持 WinSxS 的方法之一是将清单嵌入应用程序的映像文件中。下图显示了在 Visual Studio 2008 中打开的 NOTEPAD.EXERT_MANIFEST

RT_MANIFEST_of_Notepad.png

导出后,表示 NOTEPAD.EXE 清单的 XML 文件如下所示:

XML_Snapshot.png

如上图所示,NOTEPAD.EXE 支持 WinSxS,因为它在其清单中集成了包含 <dependentAssembly> 部分的 <dependency> 块。

<assemblyIdentity> 块包含几个参数(有些是必需的,有些不是),用于向系统声明(显式)应用程序对并行存储中特定程序集的期望。

要引用并行存储中的程序集,应用程序必须提供一些参数,如库的名称、版本、处理器体系结构、语言和公钥标记。这种装饰允许在任何其他可能先前(和将来)安装的版本中,对要使用的库进行非常精细的选择。

并行功能不仅适用于 .NET,也适用于任何标准的 Windows 应用程序和库。应用程序可以依赖其他库(并行),但库也可以依赖其他库(并行)。这是 Windows 动态链接库的常见问题。

导入地址表 (IAT)、应用程序清单、SxS 管理器和并行存储协同工作,如下图所示:

SxsManager.png

当应用程序启动时,系统会执行以下步骤:

  1. 加载程序会检查应用程序的导入地址表 (IAT),并发现所有直接和间接导入的库。IAT包含库名称和扩展名(例如,advapi32.dllgdi32.dllwinspool.drv、...ntdll.dll),但不包含路径信息。系统本身必须找出引用的库在物理上位于何处。这正是 DLL Hell 曾经开始的地方……如果应用程序包含清单,那么并行机制就会发挥作用。
  2. SxS 管理器会定位并读取应用程序清单,并构造应用程序引用的库的完全限定路径的名称。此路径是根据清单中包含的信息计算的。
  3. 当应用程序引用并行库时,加载程序不会在典型位置(应用程序文件夹、Windows 系统目录等)中搜索它,而是在并行存储中搜索。

下图显示了 NOTEPAD.EXE 的(隐式)导入库,使用 Dependency Walker(www.dependencywalker.com),在其导入地址表 (IAT) 中有一个条目,它告诉系统它导入了 COMCTL32.DLL 库。

notepad_iat.png

对于熟悉 Dependency Walker 的人来说,您知道它有一个选项可以显示依赖库的路径。

如前所述,映像文件中不包含任何关于导入库的路径。正如您在上图所示,Dependency Walker 显示了两种不同的路径:

  1. 几乎所有路径都引用了系统找到引用的库的典型 System32 文件夹。如果没有清单,或者如果找不到特定库的条目,系统将从典型位置(系统目录、应用程序目录等)加载库。
  2. 一个路径条目引用了 WinSxS 存储,其中找到了 COMCTL32.DLL 库的特定版本(基于其版本、名称、语言-区域设置、令牌...)。在 NOTEPAD.EXE 的情况下,只有 COMCTL32.DLL 被声明为从并行程序集中检索。

Dependency Walker 如何获得此 COMCTL32.DLL 库的路径?通过读取应用程序清单,识别依赖项,并根据 XML 参数拼接 WinSxS 目录中程序集的路径。

如上所示,NOTEPAD.EXE 的映像文件包含一个清单,该清单告诉系统它要使用 Microsoft.Windows.Common-Controls,它应该位于并行组件存储中。从SxS 管理器的角度来看,这将映射到 COMCTL32.DLL

下图显示了我系统 WinSxS 文件夹中 COMCTL32.DLL 库的两个不同版本:

comctl32_found_on_my_system.png

当启动 NOTEPAD.EXE 时,WinSxS 管理器会读取 NOTEPAD.EXE 的清单,并识别与某个并行程序集的依赖关系。它会读取其不同的参数,并使用这些字段构建一个路径:

职位

参数

描述

1

type

Win32 (x86)

2

名称

Microsoft.Windows.Common-Controls

3

publicKeyToken

6595b64144ccf1df

4

version

6.0.0.0

5

language

* (无)

有关这些字段的更多详细信息,请参阅 Microsoft 在 http://msdn.microsoft.com/en-us/library/aa374219(v=VS.85).aspx 提供的描述。

Windows 加载程序应查找并行库的路径基于从清单中读取的五个参数,每个参数之间用下划线分隔。

根据这些参数,构建了 COMCTL32.DLL 库的完全限定名称,正如在对 NOTEPAD.EXE 运行 Dependency Walker 时所见:

depends_with_numbers.png

如果应用程序通过其清单引用的并行库丢失,加载程序会抱怨应用程序环境未正确建立,程序将无法加载。这会发生在任何(隐式)缺失的库上。

MyApp_-_Failed_to_start_messagebox.png

当您尝试(使用 Dependency Walker)分析一个其依赖项未在 WinSxS 存储中找到的应用程序时,也会出现这种情况。

Depends_also_tests_WinSxs.png

如出现的错误消息框中所述,查看事件日志可以揭示错误的原因:

MyApp_-_Failed_to_start_in_Event_Log.png

将缺失的库复制到本地应用程序目录或 Windows 目录这一众所周知的快速修复方法将无法解决错误。这是因为 Windows 加载程序在 WinSxS 存储中查找应用程序使用的并行程序集。因此,任何并行程序集都必须部署在 WinSxS 存储中!

仔细查看事件日志的错误消息,会发现一个关于名为“sxstrace”的实用程序的有趣提示。请尝试一下,您将看到 Windows 的探测机制在尝试查找应用程序引用的资源时是如何尽力而为的……

sxstrace_-_trace.png

sxstrace_-_parse.png

下图是 myapp.txt 文件的一个摘录,其中包含系统在查找要加载的库时进行的探测活动:

=================
Begin Activation Context Generation.
Input Parameter:
Flags = 0
ProcessorArchitecture = x86
CultureFallBacks = en-US;en
ManifestPath = C:\temp\MyApp.exe
AssemblyDirectory = C:\temp\
Application Config File =
-----------------
INFO: Parsing Manifest File C:\temp\MyApp.exe.
INFO: Manifest Definition Identity is myapp.processorArchitecture="x86",
      type="win32",version="5.1.0.0".
INFO: Reference: Microsoft.Windows.Common-Controls,language="*",
      processorArchitecture="*",publicKeyToken="6595b64144ccf2df",
      type="win32",version="6.0.0.0"
INFO: Resolving reference Microsoft.Windows.Common-Controls,language="*",
      processorArchitecture="*",publicKeyToken="6595b64144ccf2df",
      type="win32",version="6.0.0.0".
........
ERROR: Cannot resolve reference Microsoft.Windows.Common-Controls,language="*",
       processorArchitecture="*",publicKeyToken="6595b64144ccf2df",
       type="win32",version="6.0.0.0".
ERROR: Activation Context generation failed.
End Activation Context Generation.

示例

本文提供的示例应用程序提供了一组 C++ 类,这些类使用 Visual Studio 2008 开发,您可以使用它们以编程方式检索应用程序清单文件中引用的任何并行程序集的不同项。

在处理可移植可执行文件及其依赖项的项目时,我遇到了这里介绍的问题,以及必须在运行时检索 WinSxS 库路径的事实。

Application_UI.png

由于我没有找到任何有价值的 C++ 示例说明如何检索清单中引用的并行依赖项的字段,因此我决定自己开发它,并在此提供代码。尽情享用。

优势

您可以使用这里提供的类来在程序安装期间验证应用程序,分析可执行文件的依赖项,开发收集应用程序统计信息的工具,或开发任何类型的取证工具。

使用代码

使用这里提供的类非常简单。只需实例化一个 CPeManifest 对象,并将要分析的包含清单的可移植可执行文件名传递进去。

CString s = "C:\temp\myapp.exe";
CPeManifest manifest(s);
const IPeManifestAssemblyIdentity* pIdentity = m_pPeManifest->GetFirstDependency(); 
while( pIdentity )
{
    m_listDependencies.InsertString( iPos, pIdentity->GetName() );
    m_listDependencies.SetItemDataPtr( iPos++, (void*)pIdentity );
    pIdentity = m_pPeManifest->GetNextDependency();
}

之后,您可以在代码中选择一个依赖项,并检索关联的参数,如下所示:

void CQueryManifestDlg::OnLbnSelchangeListDependentAssemblies()
{
    int iPos = m_listDependencies.GetCurSel();
    IPeManifestAssemblyIdentity* pIdentity = 
       (IPeManifestAssemblyIdentity*)m_listDependencies.GetItemDataPtr(iPos);
    if(pIdentity)
    {
        m_architecture = pIdentity->GetProcessorArchitecture();
        m_token = pIdentity->GetPublicKeyToken();
        m_type = pIdentity->GetType();
        m_language = pIdentity->GetPublicLanguage();
        m_version = pIdentity->GetVersion();
    }
    UpdateData(FALSE);
}

链接

历史

  • 2010 年 6 月 17 日:首次发布。
© . All rights reserved.