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

通过 Vista 认证:如何确保应用程序获得认证。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (74投票s)

2007年4月11日

12分钟阅读

viewsIcon

174065

downloadIcon

780

您希望您的应用程序获得微软的批准,以便在 Windows Vista 上运行吗?本文将对此进行解释。

Screenshot - VistaCertification.gif

引言

随着最新版 Microsoft Windows Vista 操作系统的发布,出现了一个新的认证计划,允许我们在应用程序通过严格的测试流程后,为其授予“通过 Vista 认证”的徽标。应用程序需要通过 32 项测试才能获得认证。在准备我们的应用程序(Fascia – 一个 C# .NET (托管) 应用程序)进行认证时,我们遇到了一些问题和需要考虑的事项。要克服许多困难所需的信息很难找到,因此我写了这篇文章,以总结我学到的东西,希望能帮助您今后的认证尝试。

我包含了一些代码示例,这些示例引用了“DataInterface”命名空间中的一些功能。本文末尾将讨论此命名空间中相关的程序集。这些程序集的源代码和一个使用它们的大型程序可在下载文件中找到。

测试概述

幸运的是,完整的测试脚本和执行测试所需的工具都可以免费获取。因此,在提交应用程序之前,您可以运行所有测试,从而对您的应用程序是否能通过测试有一定程度的信心。互联网上有许多有用的资源可以帮助您准备。我在本文的最后总结了这些资源。测试分为三个部分:

  • 安全性与兼容性
  • 安装/卸载
  • 可靠性

安全性与兼容性

用户帐户控制 (UAC) 和权限提升

Vista 不允许应用程序执行管理任务,除非管理员确认其可以执行。即使您以管理员身份登录到 Vista,您仍然需要确认正在执行的管理任务(通过输入您的密码)。这个过程称为权限提升。这与 Vista 应用程序的开发人员相关,因为我们需要确定应用程序中的哪些任务需要管理员权限。我们应该尽量避免执行任何需要管理员权限的操作,而那些需要管理员权限的任务则需要被分离成独立的独立可执行文件。所有可执行文件都必须包含一个指示其运行所需执行级别的清单。我在测试用例 1 的文本中解释了如何为可执行文件添加清单。

主可执行文件必须有一个清单,其请求的执行级别为“asInvoker”(其他选项为“requireAdministrator”和“highestAvailable”)。这意味着它可以无需管理员权限运行。如果主可执行文件一开始就需要管理员权限并且在大部分任务中都需要(这种情况非常罕见),您必须向微软申请特殊豁免。如果主程序需要执行需要管理员权限的操作,它必须调用另一个具有“requireAdministrator”请求执行级别的清单的可执行文件。调用“requireAdministrator”程序的按钮或其他控件必须用 Vista 盾牌图标表示。我稍后将解释如何做到这一点。

测试用例 1:验证应用程序的所有可执行文件都包含一个定义其执行级别的嵌入式清单

要为可执行文件添加清单,请创建一个文本文件(清单),格式如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity version="2.0.2.0" processorArchitecture="X86" name="Fascia"

type="win32"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

将此文件保存为 <可执行文件名>.exe.manifest。使用 mt.exe 工具将清单添加到已编译的可执行文件中。最好让 Visual Studio 在编译后使用“生成后事件”运行 mt.exe。生成后命令行为:

$(DevEnvDir)..\..\SDK\v2.0\bin\mt.exe -manifest 
            $(ProjectDir)$(TargetName).exe.manifest
 -outputresource:$(TargetDir)$(TargetFileName);#1

使用通用的 $(ProjectDir)$(TargetName) 等意味着此命令行可以“按原样”复制到任何项目中。

保存文件

Fascia 在运行时会将许多文件保存到磁盘的不同位置。除了保存用户创建的文件外,它还保存日志文件、调试文件、配置文件和异常报告。Vista 认证测试和 Vista 本身强制执行一些规则。

  • 默认情况下,应用程序只能将文件安装到应用程序文件夹(Program Files 的子目录)或用户的 AppData 目录中。如果安装是“每台计算机”,则不存在正确的用户 AppData 文件夹。在这种情况下,用户数据必须在程序首次运行时写入,而不是在安装期间写入。
  • 在运行时,文件只能创建在 (.NET) 中
    • Application.LocalUserAppDataPath(用于用户特定文件)或
    • Application.CommonAppDataPath(用于适用于所有用户的文件),并按如下方式为 Vista 进行修改:

if(DataInterface.Controls.Vista.Global.RunningVistaOrLater()) publicDir =
Application.CommonAppDataPath.Replace("ProgramData", "Users\\Public");

对于 Fascia 来说,这意味着需要检查代码中创建或修改文件的所有部分,并确保只使用了这两个位置。Fascia 有一个例外。Fascia 具有自动更新功能,导致“Program Files”目录中的应用程序文件被更新。这需要管理员权限,因此这部分必须被分离成一个带有“requireAdministrator”清单的独立可执行文件。

测试用例 5:验证应用程序安装的可执行文件和文件是否已签名(要求:1.3)

为了签名 dll,我们必须从 Verisign 获取一个 Authenticode 证书。它包含一个证书文件和一个私钥文件。使用 SignTool.exe 程序对 dll 进行签名。我们将文件保存在安全服务器上,并通过生成后事件运行批处理文件来签名 dll。

我们有两个第三方 dll 未签名。您需要发送豁免申请表到 swlogo@microsoft.com 来申请这些文件的豁免。在“Windows Vista 创新”合作伙伴网站上有一个指向豁免表的链接(请参阅有用的资源)。

安装/卸载

我们的应用程序安装程序是使用 Visual Studio 2005 构建的。这确保了它满足第一个要求;即使用 Windows Installer。Visual Studio 将安装项目编译为“.msi”文件。“.msi”文件实际上是一组(数据库)表,其中包含数据。您可以使用“Orca.exe”工具查看和编辑这些原始表及其内容。

Orca 包含一些内置的验证测试,这些测试在测试用例 12 中执行。这些内部一致性评估器 (ICE) 确保了我们的安装是干净的,并且没有尝试安装同一个文件两次。

令人惊讶的是,使用 Visual Studio 创建的“.msi”文件将无法通过 Vista 认证的所有测试。需要使用 Orca 进行一些更改。幸运的是,Orca 可以用来创建一个转换文件,每次构建 msi 文件时都可以应用该文件,以进行所需的更改。我创建了两个转换文件:

  • AddMsiRMFileInUse.mst – 这会创建一个测试用例 25 所需的对话框,该对话框处理安装过程中正在使用的文件。有关此内容,请参阅 MSDN 论坛上的此帖子
  • VistaPatch2.mst - 这会执行以下操作:
    • AdvtExecuteSequence 表中,删除唯一一个具有 GUID 且条件设置为 UpgradeCode 的行。
    • CustomAction 表中,添加此行:MyTargetDir, 51, ARPINSTALLLOCATION, [TARGETDIR]。这确保安装位置被写入注册表,用于测试用例 19。
    • InstallExecuteSequence 表中,添加此行:MyTargetDir, , 798。这确保安装位置被写入注册表,用于测试用例 19。
    有关此内容,请参阅 http://www.creativedocs.net/blog/

可以使用 Microsoft Platform SDK for Windows Server 2003 R2 中的 msitran.exe 应用转换文件。使用“-a”选项,如下例所示:

Msitran.exe -a VistaPatch2.mst Fascia.msi

最后一步是使用 Authenticode 证书对 msi 文件进行签名。

可靠性

测试用例 30:验证应用程序是否支持重启管理器

如果我们的应用程序需要被 Vista 关闭,以响应另一个安装或更新,Vista 重启管理器就会发挥作用。应用程序必须在启动时使用以下代码注册一个重启消息:

DataInterface.RestartManager.Global.RegisterApplicationForRestart
                        ("SomeCommandLineArgs");

当 Fascia 重启时,它将接收在 RegisterApplicationForRestart 调用中指定的命令行参数。

Fascia 通过重写主窗口的 WndPrc 函数来响应重启消息,如下例所示:

protected override void WndProc(ref Message m)
{
   base.WndProc(ref m);

   if(DataInterface.RestartManager.Global.IsRestartMessage(m))
   {
       //This application is about to be shut down, so save state...
       if (this.InvokeRequired)
           this.BeginInvoke(new MethodInvoker(this.SaveState));
       else
           this.SaveState();
   }
}

当 Fascia 重启时,它通过检查注册重启时提供的命令行参数来检测是重启管理器发起的重启。状态将在用户登录后恢复。

测试用例 31:验证应用程序不会因指定的 AppVerifier 检查而中断调试器

最新版本的测试用例在测试用例 31 的末尾包含一条注释,指出此测试用例不适用于完全托管的应用程序。由于这条注释是在我完成 Fascia 的认证准备之后才添加的,所以我花时间确保 Fascia 能够通过此测试用例。即使它不是托管应用程序的要求,Fascia 足够稳定能够通过此测试,这一点也令人欣慰。

在修复了导致此测试失败的几个代码区域后,我发现我的最终故障是通过确保可执行文件以发布模式构建来解决的。对于以发布模式构建的托管应用程序,这些测试可能不会失败,尽管我还没有对此进行验证。我建议您将所有托管程序集都以发布模式构建,如果您也想了解您的应用程序是否能通过此测试。

如果您在窗体上有任何带有透明度的图像,存在一个已知问题。如果您有此类图像,此测试将失败。我必须用不带透明度的图像替换带有透明部分的图像。

测试规定您使用“AppVerifier”工具对可执行文件运行某些自动测试(来自基础检查的 Exceptions、Handles、Heaps、Locks、Memory 和 TLS,来自杂项检查的 DangerousAPIs 和 DirtyStacks)。调试任何错误的主要困难在于,当从 Visual Studio 运行时,行为会有很大不同。我发现我只能通过使用 MessageBox 和/或注释掉大块代码来缩小导致失败的具体代码行。最终通过执行更多空值检查来修复了这些故障,尤其是在被空异常处理包围的代码区域,例如:

Try
{
    DoSomethingThatMightGoWrong();
}
Catch{}

显然,这本身就是糟糕的编程实践,但在您想要获得认证的应用程序中,绝对应该避免。

测试用例 32:验证应用程序只处理已知和预期的异常

此测试的值得注意之处在于,如果您构建一个完全不包含 try-catch 块的应用程序,它将通过。异常不能在未重新抛出时被捕获,因为它们需要被抛出,以便 Windows 错误报告能够捕获它们。Fascia 捕获异常并将它们发送到一个静态函数,该函数可以将报告发送到一个中央 Web 服务,使我们能够有效地诊断客户问题。它的原理与 Windows 错误报告类似,但导致了测试失败,因为 Windows 错误报告必须由要获得 Vista 认证资格的应用程序使用。解决方案是在任何异常处理程序的 catch 块中调用 throw,以确保异常被重新抛出。顺便说一下,如果您调用 throw(ex),其中 ex 是已捕获的特定异常,测试仍然会失败。您必须调用不带参数的 throw

应用程序必须在 winqual 网站注册,才能获得 Windows 错误报告的资格。

源代码

.NET 程序集以协助认证过程

我编写了两个程序集,它们封装了准备应用程序进行 Vista 认证所需的一些功能。

DataInterface.Controls.Vista

DataInterface.Controls.Vista 包含特定于 Vista 的控件,但也能在 Windows XP 上运行。它包含两个控件:CommandLinkShieldButtonCommandLink 控件允许您创建 Vista 命令链接样式的按钮。当 Vista 要求您确认权限提升时,您会看到这些链接按钮。您可以查看任何 Vista 管理任务,例如更改系统日期和时间,来看到这些按钮。ShieldButton 控件是一个普通的 Windows 按钮,它有一个 ShowShield 布尔属性,用于指示是否在按钮上显示 Vista 盾牌图标。程序集中的有用静态代码包含在 Global 类中,如下所示:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace DataInterface.Controls.Vista
{
    /// <summary>
    /// Class that contains static functions for Vista control functionality
    /// </summary>
    public class Global
    {
        internal const int BS_COMMANDLINK = 0x0000000E;
        private const uint BCM_SETNOTE = 0x00001609;
        private const uint BCM_SETSHIELD = 0x0000160C;

        /// <summary>
        /// Override for SendMessage for setting the shield icon
        /// </summary>
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        internal static extern IntPtr SendMessage(HandleRef hWnd, UInt32 Msg, 
                        IntPtr wParam, IntPtr lParam);

        /// <summary>
        /// Override for SendMessage for setting note text
        /// </summary>
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        static extern IntPtr SendMessage(HandleRef hWnd, UInt32 Msg, 
                        IntPtr wParam, string lParam);

        /// <summary>
        /// Shows or hides the Vista shield icon on the specifed control
        /// </summary>
        /// The control on which to display the shield
        /// Indicates whether to show or hide the shield
        public static void SetShield(Control ctrl, bool showShield)
        {
            SendMessage(new HandleRef(ctrl, ctrl.Handle), BCM_SETSHIELD, 
        IntPtr.Zero, new IntPtr(showShield ? 1 : 0));
        }

        /// <summary>
        /// Shows command link style note text on the specified control
        /// </summary>
        /// 
        /// 
        public static void SetNote(Control ctrl, string NoteText)
        {
            SendMessage(new HandleRef(ctrl, ctrl.Handle), BCM_SETNOTE, 
                        IntPtr.Zero, NoteText);
        }

        /// <summary>
        /// Returns true if the operating system is Vista or later
        /// </summary>
        /// <returns></returns>
        public static bool RunningVistaOrLater()
        {
            return System.Environment.OSVersion.Version.Major > 5;
        }
    }
}

要显示盾牌图标在按钮控件上,必须将 FlatStyle 设置为 FlatStyle.System,并在 Global 类中调用 SetShield 方法。使用 RunningVistaOrLater 函数在应用任何 Vista 样式之前,检查应用程序是否正在 Windows Vista 下运行。SetNote 方法用于在 CommandLink 按钮上显示注释。

为了使盾牌在 ShieldButton 上显示,应用程序首次启动时必须运行以下两行代码:

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

DataInterface.RestartManger

DataInterface.RestartManager 包含用于使应用程序“支持重启管理器”的代码。它封装了“Kernel32.dll” API 所需的功能。代码如下:

using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using DataInterface.Controls.Vista;

namespace DataInterface.RestartManager
{
    public class Global
    {
        private const Int32 WM_QUERYENDSESSION = 0x0011;
        private const Int32 ENDSESSION_CLOSEAPP = 0x1;

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern uint RegisterApplicationRestart
                (string pszCommandline, int dwFlags);

        /// <summary>
        /// Returns true if the specified windows message is a restart message
        /// </summary>
        /// The windows message to be checked
        /// <returns>True if it is a restart message</returns>
        public static bool IsRestartMessage(System.Windows.Forms.Message msg)
        {
            bool ret = false;
            if (msg.Msg == Global.WM_QUERYENDSESSION)
            {
                if ((msg.LParam.ToInt32() & Global.ENDSESSION_CLOSEAPP) ==
                        Global.ENDSESSION_CLOSEAPP)
                    ret = true;
            }
            return ret;
        }

        /// <summary>
        /// Registers the currently running application for a restart message.
        /// </summary>
        /// <param name="restartCommandLine" />
    /// The application will be restarted with this command line string</param />
        public static void RegisterApplicationForRestart(string restartCommandLine)
        {
            //Can only do this in Vista
            if(DataInterface.Controls.Vista.Global.RunningVistaOrLater())
                RegisterApplicationRestart(restartCommandLine, 0);
        }
    }
}

VistaDemo

VistaDemo 是一个演示应用程序,演示了如何使用上面描述的两个程序集。它展示了四个方面:
  • RunningVistaOrLater() 函数在窗体构造函数中使用,以确定要在窗体顶部的标签上显示的文本。
  • ShieldButton。显示在窗体上,并将 ShowShield 属性设置为 true。在 Vista 中运行时,这将显示盾牌图标。
  • CommandLink 按钮。显示在窗体上,并设置了 Note 属性。在 Vista 中运行时,此注释将可见。
  • 重启管理器功能。应用程序在 Main() 函数中使用特定的命令行参数向重启管理器注册。Main() 函数检查命令行参数,以确定应用程序是否由重启管理器启动。窗体的 WndPrc 函数被重写,以检测重启管理器何时关闭程序。此时,我们应该(快速)保存状态。

关注点

为 Fascia 准备认证最困难的部分是发现所有必需的信息。希望本文能为您的应用程序准备提供一个良好的开端。

有用资源

© . All rights reserved.