基于 .NET 组件的免注册激活:演练

 

Steve White
面向开发人员的顶级支持,Microsoft 英国

莱斯利·穆勒
全球 IT 研究 & 开发,瑞士信贷第一波士顿

2005 年 7 月

总结: Microsoft 平台 SDK 在记录 独立应用程序和并行程序集的主题方面表现非常出色。 但是,并非每个人都将本主题等同于 COM 组件的免注册激活。 免注册 COM 是一项平台功能,对于在共享基础结构上隔离的锁定服务器和应用程序的企业非常感兴趣。 本文逐步讲解本机客户端通过 COM 互操作对基于 .NET Framework 的组件进行免注册激活的一个工作示例。 ) (11 个打印页

适用于:
   Microsoft Windows Server 2003
   Microsoft Windows XP Service Pack 2
   Microsoft .NET Framework 版本 1.1
   Microsoft Visual Studio .NET 2003
   Microsoft Visual Basic 6.0

下载本文随附的示例, MSDNRegFreeNet.msi

Contents

简介
Registration-Free COM 术语
运行示例
将 .NET 程序集生成为 COM 服务器
生成客户端
Registration-Free激活
故障排除
结论
深入阅读

简介

免注册 COM 是 Microsoft Windows XP (SP2 上提供的一种机制,适用于基于 .NET Framework) 和 Microsoft Windows Server 2003 平台的组件。 顾名思义,该机制支持轻松 (例如 XCOPY) COM 组件部署到计算机,而无需注册它们。

在目标平台上,初始化进程及其依赖模块的一个阶段是将任何关联的 清单文件 加载到称为 激活上下文的内存结构中。 在没有相应的注册表项的情况下,它是一个提供 COM 运行时所需的绑定和激活信息的激活上下文。 COM 服务器或客户端中不需要任何特殊代码,除非你选择通过使用激活上下文 API 自行生成激活 上下文来避免使用文件。

在本演练中,我将生成一个简单的 .NET 程序集,并从以 Visual C++ 和 Visual Basic 6.0 编写的本机 COM 客户端使用已注册和未注册的程序集。 可以下载源代码和示例并立即查看其运行情况,也可以按照演练进行操作并自行逐步生成。

Registration-Free COM 术语

任何熟悉 .NET 技术的人都将习惯术语 程序集 ,即一组部署的一个或多个模块,以单元的形式命名和版本控制,其中一个模块包含定义集的 清单 。 在免注册 COM 中,术语 程序集清单 是为概念相似但与 .NET 对应项不同的想法所借用的。

免注册 COM 使用 程序集 来表示一组 (的一个或多个 PE 模块,即本机 托管) 作为一个单元部署、命名和版本控制。 免注册 COM 使用 清单 来引用扩展名为包含 XML 的 .manifest 的文本文件,它定义 程序集 (程序集清单的标识) 及其类的绑定和激活详细信息,或者定义 应用程序 (应用程序清单) 的标识以及一个或多个程序集标识引用。 程序集清单文件为程序集命名,应用程序清单文件为应用程序命名。

术语 “并行 (SxS) 程序集 ”是指通过清单文件配置同一 COM 组件的不同版本,以便它们可由不同的线程同时加载,而无需注册。 SxS 启用且松散地与 免注册 COM 同义。

运行示例

下载并提取示例代码后,你将找到一个名为 \deployed 的文件夹。 下面是客户端应用程序的 Visual C++ 版本 (client.exe) ,其清单 (client.exe.manifest) ,以及 COM 服务器 (SideBySide.dll) 的 C# 版本。 继续运行 client.exe。 预期结果是,client.exe将激活在 SideBySide.dll) 中实现的 SideBySideClass (实例,并显示调用其 Version 方法的结果,该方法应类似于“1.0.0-C#”。

将 .NET 程序集生成为 COM 服务器

步骤 1

Visual Studio .NET 2003 中,创建新的 C#Visual Basic .NET 类库项目 并将其命名为 SideBySide。 删除 AssemblyInfo.[来自项目的 cs/vb] 文件并实现类,如下所示:

C# 代码

using System;
using System.Reflection;
using System.Runtime.InteropServices;

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: Guid("[LIBID_SideBySide]")]

namespace SideBySide
{
   [Guid("[IID_ISideBySideClass]")]
   public interface ISideBySideClass
   {
      string Version();
   }

   [Guid("[CLSID_SideBySideClass]")]
   public class SideBySideClass : ISideBySideClass
   {
      public string Version()
      {
         return "1.0.0-C#";
      }
   }
}

Visual Basic .NET 代码

Imports System
Imports System.Reflection
Imports System.Runtime.InteropServices

<Assembly: AssemblyVersion("1.0.0.0")> 
<Assembly: Guid("[LIBID_SideBySide]")>

<Guid("[IID_ISideBySideClass]")> _
Public Interface ISideBySideClass
    Function Version() As String
End Interface

<Guid("[CLSID_SideBySideClass]")> _
Public Class SideBySideClass
    Implements ISideBySideClass
    Function Version() As String Implements ISideBySideClass.Version
        Version = "1.0.0-VB.NET"
    End Function
End Class

我以占位符的形式编写了 GUID 值,这些值将特别适用于你的项目。 你需要使用 guidgen 工具生成唯一的 GUID,这些 GUID 将是我随后使用占位符时所希望的相应值。

步骤 2

为了在生成时生成和注册类型库,请将项目的 Register for COM 互操作 设置设置为 true。

步骤 3

生成发布版本并将 SideBySide.dll 复制到 \deployed 中。

生成客户端

下一步是生成客户端,对于本演练,可以选择生成 Visual C++Visual Basic 6.0 客户端。

步骤 4 (选项 A:Visual C++)

在相对于 SideBySide 项目的 文件夹的同级文件夹中创建名为 client 的新 Visual C++ Win32 控制台项目。 在 Win32 应用程序向导“应用程序设置”选项卡上,检查“添加对 ATL 的支持”复选框。

编辑 stdafx.h 并在文件顶部紧接 #pragma once在 后面添加以下行:

#define _WIN32_DCOM

此外,在 stdafx.h 中,在文件底部添加以下行:

import "[path]\SideBySide.tlb" no_namespace

此处, [path] 应该是生成 SideBySide 程序集时生成的类型库的相对路径。 此路径通常因在步骤 1 中选择 C# 还是 Visual Basic .NET 项目而异。

client.cpp 的内容替换为以下代码:

#include "stdafx.h"
#include <iostream>
using namespace std;

void ErrorDescription(HRESULT hr)
{
    TCHAR* szErrMsg;
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
      FORMAT_MESSAGE_FROM_SYSTEM, 
      NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
      (LPTSTR)&szErrMsg, 0, NULL) != 0)
   {
        cout << szErrMsg << endl;
        LocalFree(szErrMsg);
    }
   else
        cout << "Could not find a description for error 0x" 
           << hex << hr << dec << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
   CoInitializeEx(0, COINIT_MULTITHREADED);

   {
      ISideBySideClassPtr ptr;
      HRESULT hr = 
              ptr.CreateInstance(__uuidof(SideBySideClass));
      if (SUCCEEDED(hr))
      {
         cout << ptr->Version() << endl;
      }
      ErrorDescription(hr);

      char c;
      cin >> c;
   }

   CoUninitialize();

   return 0;
}

生成发布版本并将 \release\client.exe 复制到 \deployed 中。

步骤 4 (选项 B:Visual Basic 6.0)

创建新的 Visual Basic 6.0 标准 EXE 项目。 在“项目资源管理器”中选择“Project1”节点,并在“属性”窗口中将其名称更改为“客户端”。 选择 文件 |将项目另存为 ,并将表单文件和项目文件保存在相对于 SideBySide 项目的文件夹的同级文件夹中。 选择项目 |引用,检查 SideBySide 旁边的复选框,然后选择“确定”。

在窗体设计器中双击main窗体,并将以下代码粘贴到 Sub Form_Load () 中:

    Dim obj As New SideBySideClass
    Dim isxs As SideBySide.ISideBySideClass
    Set isxs = obj
    MsgBox isxs.Version()

选择 文件 |生成client.exe... 导航到 \deployed 文件夹,然后选择 “确定”。

步骤 5

目前,除了一些中间文件外, \deployed 文件夹应仅包含 client.exeSideBySide.dll;后者将通过其生成过程进行注册。 若要检查服务器和客户端在这些正常情况下协同工作,请运行\deployed\client.exe并记下预期的输出“1.0.0-C#”或“1.0.0-VB.NET”。

步骤 6

本演练介绍 免注册 COM,因此我们现在需要注销 SideBySide 程序集。 在 Visual Studio 2003 命令提示符下,导航到 \deployed 文件夹并执行命令: regasm /u SideBySide.dll

步骤 7

若要查看上一步的效果,请再次运行 \deployed\client.exe ,你将看到消息“类未注册”或“运行时错误'429':ActiveX 组件无法创建对象”。 在此阶段,我们已使 COM 运行时无法找到它在注册表中所需的信息,但我们尚未通过其他方式提供这些信息。 我们将在以下步骤中对此进行补救。

Registration-Free激活

步骤 8

\deployed 文件夹中,创建应用程序清单文件 (client.exe 应用程序的文本文件) ,并将其 client.exe.manifest 调用。 将以下内容粘贴到 文件中:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
   manifestVersion="1.0">
<assemblyIdentity
            type = "win32"
            name = "client"
            version = "1.0.0.0" />
<dependency>
            <dependentAssembly>
                        <assemblyIdentity
                                    type="win32"
                                    name="SideBySide"
                                    version="1.0.0.0" />
            </dependentAssembly>
</dependency>
</assembly>

步骤 9

SideBySide 项目的 文件夹中, (文本文件) 创建专用程序集清单文件,并将其命名为 SideBySide.manifest。 将以下内容粘贴到 文件中:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
  manifestVersion="1.0">
<assemblyIdentity
            type="win32"
            name=" SideBySide"
            version="1.0.0.0" />
<clrClass
            clsid="{[CLSID_SideBySideClass]}"
            progid="SideBySide.SideBySide"
            threadingModel="Both"
            name="SideBySide.SideBySideClass" >
</clrClass>
</assembly>

下一个任务是将上述程序集清单文件作为 Win32 资源嵌入 SideBySide 程序集。 在撰写本文时,这对于 Windows XP 是必需的,但对于 Windows Server 2003 则不是必需的。 在 Windows Server 2003 上, 只需 将程序集清单文件与程序集一起部署即可。 但是,我敦促你不要依赖此行为,因为它很可能在即将推出的 Windows Server 2003 Service Pack 中发生更改。 若要确保将来继续支持这两个平台,请遵循以下几个步骤,将程序集清单文件作为 Win32 资源嵌入 .NET 程序集。 这仅在免注册激活 时是必需的 。基于 NET 的 组件,不需要注册激活 本机 COM 组件。

步骤 10

SideBySide 项目的 文件夹中,创建一个资源定义脚本文件 (文本文件) ,并将其命名为 SideBySide.rc。 将以下内容粘贴到 文件中:

#include <windows.h>
#define MANIFEST_RESOURCE_ID 1
MANIFEST_RESOURCE_ID RT_MANIFEST SideBySide.manifest

安装平台 SDK (Core SDK 部分) 或 Visual C++ 时, windows.h 文件及其依赖项可用。 此处所需的 windows.h 部分是定义:

#define RT_MANIFEST 24

因此, SideBySide.rc 的内容解析为:

1 24 SideBySide.manifest

但是,按照指示使用宏定义更清晰、更通用。

步骤 11

SideBySide 项目的文件夹中,创建一个生成命令文件 (文本文件) ,并将其称为 build.cmd。 将以下内容粘贴到 文件中:

生成 C#:

rc SideBySide.rc
csc /t:library /out:..\deployed\SideBySide.dll 
/win32res:SideBySide.res Class1.cs

生成 Visual Basic .NET:

rc SideBySide.rc
vbc /t:library /out:..\deployed\SideBySide.dll 
/win32resource:SideBySide.res /rootnamespace:SideBySide Class1.vb

这些命令首先从平台 SDK (rc.exe) 调用 Microsoft Windows 资源编译器工具,将步骤 10 中的资源定义脚本编译为名为 SideBySide.res 的已编译资源文件。接下来,它会调用 C# 或 Visual Basic .NET 编译器,将源代码文件生成到程序集中,并将编译的资源文件作为 Win32 资源嵌入其中。 编译的程序集将写入 \deployed 文件夹,但 注册 COM 互操作。

步骤 12

在 Visual Studio 2003 命令提示符下,导航到 SideBySide 项目的 文件夹并执行命令: build

步骤 13

为了验证客户端是否能够再次通过 COM 互操作激活 SideBySideClass 类,运行 \deployed\client.exe 并记下预期的输出“1.0.0-C#”或“1.0.0-VB.NET”。

疑难解答

如前所述,对基于.NET Framework的组件进行免注册激活不需要服务器或客户端中的特殊代码。 只需要一对匹配的清单文件,其中一个作为类型为 RT_MANIFEST 的 Win32 资源嵌入到 .NET 程序集中。

建议按照本演练的方式进行自己的免注册开发。 具体而言:首先通过查看客户端使用已注册的服务器来获取已知状态;然后注销服务器并确认错误消息是预期的;最后,通过创建和部署清单文件来纠正这种情况。 这样,围绕免注册激活的故障排除工作将仅限于清单文件的结构和程序集清单的正确嵌入。

排查免注册 COM 问题时,Windows Server 2003 上的事件查看器是你的朋友。 当 Windows XP 或 Windows Server 2003 检测到配置错误时,它通常会显示一个错误消息框,标题为已启动的应用程序,并包含消息“此应用程序无法启动,因为应用程序配置不正确。 重新安装应用程序可能会解决此问题。”我建议每当看到此消息时,在 Windows Server 2003 上重现问题,请查阅系统事件日志并从 SideBySide 源查找事件。 我不建议在这些情况下查看 XP 的事件日志的原因是,它将总是包含一条消息,如“生成激活上下文失败的 [路径]\[应用程序文件名]。清单。 引用错误消息:操作已成功完成,“这无助于识别问题。

在继续介绍清单文件的结构之前,让我们讨论一下 Win32 资源。 如上所述, windows.h 将RT_MANIFEST符号定义为值 24,这是操作系统将识别为嵌入清单文件的值。 如果忘记在资源定义脚本 (.rc 文件) 包含 windows.h ,则生成仍会成功,并且清单文件仍将嵌入为资源,但不是正确类型。 若要验证是否已正确嵌入其清单,请在 Visual Studio 中打开 SideBySide.dll (文件 |打开 |文件。。。) 。你将看到一个树状视图,其中显示了模块中的资源。 根节点下应是名为 RT_MANIFEST 下的另一个节点,该节点应显示演练) 中清单资源的资源编号 (1。 双击最后一个节点以查看二进制视图中的数据,并快速检查它类似于 XML 清单文件。 虽然它是二进制的,但 ASCII 范围内的字符将是显而易见的。 如果二进制数据缺失或看起来不正确,请验证资源定义脚本 (.rc 文件) 引用清单文件。 如果RT_MANIFEST节点的文本在引号中,则可能忘记在资源定义脚本 (.rc 文件) 中包含 windows.h

刚才提到的每个错误都将在 Windows Server 2003 系统事件日志中报告,并显示消息:“找不到依赖程序集 [name] ,并且上次错误是 系统上未安装引用的程序集。”

各种清单文件的架构记录在平台 SDK 的标题清单 文件参考下, 并且架构 验证工具Manifestchk.vbs可用,因此此处我仅指出与演练相关的一些要点。 首先,让我们检查程序集清单文件。 有关示例,请回顾步骤 9。

你会记得,从 免注册 COM 的意义上说, 程序集 是一种抽象的概念,你可以通过 程序集清单 文件的内容将一个或多个物理文件关联起来。

assemblyIdentity 元素定义程序集的标识。 对于 。基于 NET 的组件其 name 属性必须与 .NET 程序集的名称和文件名匹配,否则你将在 Windows Server 2003 系统事件日志中看到以下消息:“找不到依赖程序集 [ name 属性的值],并且上次错误是未在系统上安装引用的程序集。”但是, 版本 属性不需要与 .NET 程序集的 AssemblyVersionAssemblyFileVersion 匹配,尽管最好应用某种一致性。

clrClass 元素只有两个必需属性:nameclsidname 属性必须与正在激活的 CLR 类的组合命名空间和类名匹配。 否则,CoCreateInstance 将返回值COR_E_TYPELOAD (0x80131522) HRESULT。 这源于当类型加载程序在 .NET 程序集中找不到请求的 CLR 类型时引发的 System.TypeLoadException。 如果 .NET 程序集是用 Visual Basic .NET 编写的,则watch的一点是,在命令行上向 Visual Basic .NET 编译器 (vbc.exe) 提供 /rootnamespace 开关。 clsid 属性必须与分配给通过其 GuidAttribute 激活的 CLR 类的 CLSID 匹配。 否则,CoCreateInstance 将返回值为 REGDB_E_CLASSNOTREG (0x80040154) 的 HRESULT,消息文本为“未注册类”。

现在,让我们将注意力转向应用程序清单文件。 有关示例,请回顾步骤 8。 应用程序清单 必须 采用 [application filename].manifest 格式命名。 因此,在本演练中,它被命名为 client.exe.manifest ,以表明每当 client.exe 加载到进程中时,都应该读取它。 如果未正确完成此操作,CoCreateInstance 将返回值为 REGDB_E_CLASSNOTREG (0x80040154) 的 HRESULT,消息文本为“未注册类”。

应用程序清单中最重要的元素是 dependentAssembly/assemblyIdentity 元素。 此元素是对程序集清单中等效元素的引用,两者必须 完全匹配。 确保这样做的一个好方法是从程序集清单复制 元素并将其粘贴到此处。 如果有任何差异,你将在 Windows Server 2003 系统事件日志中看到以下消息:“清单中找到的组件标识与所请求的组件的标识不匹配。”

结论

免注册 COM 是一种技术,可将 COM 组件从 Windows 注册表的依赖项中解放出来,从而将使用这些组件的应用程序从需要专用服务器中解放出来。 它使依赖于同一 COM 组件不同版本的应用程序可以共享基础结构,并在 .NET 版本管理和部署机制的回响中并行加载这些不同的 COM 组件版本。

本文演示了使用 Visual C++ 和 Visual Basic 6.0 编写的本机客户端应用程序对基于 .NET Framework 的组件进行免注册激活。 它解释了该机制的一些工作原理,并下划下了一些可能的配置错误以及如何对其进行故障排除。

深入阅读

 

关于作者

Steve White 是一名应用程序开发顾问,在 Microsoft UK 的开发人员顶级支持团队工作。 他支持客户使用 Visual C#、Windows 窗体 和 ASP.NET 进行开发。 他的 博客 中详细介绍了他在音乐、可视化效果和编程方面的兴趣。

莱斯利·穆勒 是瑞士信贷第一波士顿研究 & 开发团队的技术专家。 Leslie 拥有 12 年的开发人员和技术架构师经验,在金融服务、技术初创公司、工业自动化和国防等环境中工作。 当他不编程或做研究时,他喜欢滑雪、冰球,并尽可能在冰岛或落基山脉等极端环境中用机动车辆做有点疯狂的事情。