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

使用 NAnt 为 .NET COM 组件创建 MSI

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (1投票)

2010年9月2日

CPOL

16分钟阅读

viewsIcon

24550

downloadIcon

158

使用 NAnt 创建 MSI。

引言

本文介绍了一个用 C# 创建的非常简单的 COM 组件,以及如何使用 NAntNAntContrib 创建的 MSI 安装文件进行部署。创建的 MSI 将构建并安装 COM 组件,以便 VBS 和 C++ 客户端可以使用它。创建的类型库也可以在进行一些手动操作后供 VBA 代码使用。

背景

NAnt、NAntContrib 以及 .NET Framework 提供了编译和部署 .NET 程序以及基于 .NET 的 COM 组件所需的所有工具。它们易于安装和使用,是广泛使用的 Microsoft Visual Studio 的便捷替代品。在自动化构建过程领域,NAnt 至少与 Microsoft 的 MSBuild 一样好。通过一些技巧和对 COM 的一些了解,它也可以用于将 COM 组件部署为 MSI 安装文件。

使用代码

要构建和运行代码,您需要以下组件

  1. .NET Framework 2.0 或更高版本,以及 C# 编译器。您通常已在 %SystemRoot%Microsoft.NET\Framework 下拥有它。
  2. NAnt 来自 nant.sourceforge.net,NAntContrib 来自 nantcontrib.sourceforge.net。下载安装文件。下载二进制版本,解压它们,并将搜索路径设置为包含可执行文件 nant.exe 的目录。有关详细的安装说明,请参阅 NAntContrib 包中的 readme.txt。阅读后,我决定将 NANtContrib 文件复制到 NAnt 的目录结构中。
  3. .NET SDK。附带的 NAnt 构建脚本使用了 tlbexp.exe 以及(在某些情况下)sn.exe,它们都包含在 .NET SDK 中。
  4. 可选:如果您想构建附带的 C++ 客户端,则需要 Microsoft C++ 编译器、nmake 构建实用程序以及 Platform SDK。
  5. 可选:您可能需要 uuidgen.exe 来为您的组件生成唯一的 GUID。附带的示例代码包含我的一些 GUID,这些 GUID 并非旨在唯一。uuidgen.exe 也包含在 Microsoft Platform SDK 中。

在一切安装正确后,您可以解压附带的代码,并在提取代码的目录中从 cmd.exe 发出简单的 nant 命令。这应该会生成一个 MSI 文件 comtest.msi,您可以直接调用它从 cmd.exe 安装,也可以双击它在 Explorer 中安装,或者使用 msiexec /i comtest.msi 安装。

调用 nant 命令会执行位于 nant.build 文件中的 NAnt 构建脚本。该构建脚本执行以下操作:

  1. 它将 C# 程序 ComTest.cs 编译为 DLL comtest.dll。该程序实现了一个名为 ComTest 的简单类,该类被设计为导出为 COM 类。C# 代码使用 NAnt 的 csc 任务进行编译。
  2. 然后,构建脚本使用 NAnt 的 tlbexp 任务从先前编译的 DLL 创建类型库。类型库对于通常在 VBA 或 C++ 中使用的所谓“早期绑定”是必需的。
  3. 源代码已文档化;它在源文件 ComTest.cs 中包含文档标签,并在 namespaces.xml 中包含简短的介绍。通过这些文档任务,构建脚本使用 NAnt 的 ndoc 任务在 doc 目录中生成类似 MSDN 的 HTML 文档。
  4. 最后,使用 NAntContrib 的 msi 任务生成 MSI 文件 comtest.msi。MSI 文件将安装生成的 DLL 库、文档、类型库,并应用必要的 Windows 注册表项来将 DLL 注册为 COM 组件。
  5. 使用 nant clean 命令,可以删除生成的文件。

C# 中的 COM 组件

关于如何在 C# 中实现 COM 组件的信息有很多来源,包括 MSDN 和其他地方。例如,您可以在 这里找到一个非常简短而清晰的描述。通常,首先需要定义一个接口,为其分配一个 GUID,以及为接口中定义的每个方法分配一个所谓的 Disp-ID。为此需要的 GuidAttributeDispId 属性定义在 System.Runtime.InteropServices 命名空间中。方法可以包含参数的默认值,这是一个在 C# 中不允许的功能,但可以从 Visual Basic 等语言中使用;因此,可以使用 OptionalDefaultParameterValue 属性。

using System.Runtime.InteropServices;

[GuidAttribute("11111111-1111-1111-1111-111111111111")]
public interface IComTest {

  [DispId(1)]
  string MyMethod([Optional, DefaultParameterValue(1.0)] double param);
}

接下来,您需要创建一个实现的接口,其中包含方法的实现;在本例中,它将是 MyMethod 的实现。实现类还会获得自己的 GUID 以及有关类接口类型的信息。有关类接口类型的更多信息,请参见 MSDN

[GuidAttribute("11111112-1112-1112-1112-111111111112")]
[ClassInterface(ClassInterfaceType.None)]
public class ComTest : IComTest {

  private int m_counter;

  public ComTest() {
    this.m_counter = 0;
  }

  public string MyMethod([Optional, DefaultParameterValue(1.0)] double param) {
    this.m_counter++;
    return string.Format("MyMethod call {0} parameter {1}", this.m_counter,
                                                            param);
  }
}

方法 MyMethod 的示例实现会生成一个简单的字符串,其中包含迄今为止对其的调用次数以及传入的参数。我选择使用 ClassInterfaceType.None,以避免暴露 IDispatch 接口并使示例尽可能简单。

强名称

有关使用强名称原因的更多详细信息,请参见 https://codeproject.org.cn/KB/security/StrongNameExplained.aspx。对于 COM 组件,知道必须使用强名称就足够了。要获得强命名的 .NET 库,您需要

  1. 使用命令行实用程序 sn.exe 生成强名称密钥
  2. 使用编译器的命令行参数 -keyfile 将生成的密钥文件包含到编译中 csc.exe

命令行程序 sn.exe 包含在 .NET SDK 中,位于 .NET SDK 安装文件夹的 bin 子目录中。从命令行,可以通过调用以下命令生成强名称密钥:

sn.exe -q -k comtest.key

在附带的代码中,强名称密钥文件是通过 NAnt 使用以下方式生成的:

<property name="keyfile" value="comtest.key" />
<if test="${not file::exists(keyfile)}">
  <exec program="sn.exe">
    <arg value="-q"/>
    <arg value="-k"/>
    <arg value="${keyfile}"/>
  </exec>
</if>

仅当文件尚不存在时才会生成该文件。附带的代码已包含一个强名称密钥文件,因此不会调用 sn.exe

编译 C# 代码

可以使用以下命令编译代码:

csc.exe -target:library -keyfile:comtest.key -doc:comtest.xml ComTest.cs

在 NAnt 中,这是通过 csc 任务实现的;另请参见 http://nant.sourceforge.net/release/latest/help/tasks/csc.html

<csc target="library"
     output="ComTest.dll"
     keyfile="comtest.key"
     doc="comtest.xml"
     debug="false">
  <sources basedir="${project::get-base-directory()}">
     <include name="ComTest.cs" />
  </sources>
</csc>

设置 debug="false" 会导致不生成调试文件 comtest.pdb,因此无法进行调试且无法获得准确的堆栈跟踪。如果您也希望生成调试信息,请将此参数设置为 true,并将生成的 PDB 文件包含在 MSI 安装中。属性 keyfile="comtest.key" 确保将命令行参数 -keyfile 传递给 csc.exe 编译器,从而使生成的库具有强名称。

类型库

类型库文件在使用 VBA 或 C++ 使用 COM 组件时可能非常有用,并且可以使用命令行工具 tlbexp.exe 导出,该工具也包含在 .NET SDK 中。

tlbexp.exe /out:comtest.tlb comtest.dll

使用 NAnt,可以使用 tlbexp 任务导出类型库

<tlbexp assembly="comtest.dll" output="comtest.tlb" />

在没有 MSI 的情况下安装 COM 组件

本文档的目的是描述如何使用 MSI 安装 COM 组件。对于测试等场景,频繁生成和安装 MSI 文件可能会耗费大量时间并造成混淆。生成的 DLL 文件可以使用以下命令作为 COM 组件安装:

regasm.exe -codebase comtest.dll

regasm.exe 位于 .NET Framework 目录中;对于 .NET Framework 2.0:%SystemRoot%\Microsoft.NET\Framework\v2.0.50727codebase 参数允许 RegAsm 生成指向 comtest.dll 文件**当前**位置的注册表项。如果没有此命令行参数,我们将必须使用 gacutil.execomtest.dll 安装到全局程序集缓存 (GAC) 中(这通常不是一个好主意,尤其是对于损坏的程序集…)。

文档

可以使用 NDoc 从编译后的 DLL 中提取文档:http://ndoc.sourceforge.net。幸运的是,您不必单独安装 NDoc,因为 NAnt 已包含一个功能齐全的任务定义和所有依赖项。文档使用 NAnt 构建文件中的以下调用生成为一组具有类似 MSDN 外观的 HTML 文件:

<ndoc>
  <assemblies>
     <include name="comtest.dll" />
  </assemblies>
  <summaries>
    <include name="namespaces.xml" />
  </summaries>
  <documenters>
    <documenter name="MSDN">
      <property name="OutputDirectory" value="doc" />
      <property name="OutputTarget" value="Web" />
      <property name="IncludeFavorites" value="False" />
      <property name="Title" value="Using COM with C# and NAnt" />
      <property name="SplitTOCs" value="False" />
      <property name="DefaulTOC" value="" />
      <property name="ShowVisualBasic" value="True" />
      <property name="ShowMissingSummaries" value="True" />
      <property name="ShowMissingRemarks" value="False" />
      <property name="ShowMissingParams" value="True" />
      <property name="ShowMissingReturns" value="True" />
      <property name="ShowMissingValues" value="True" />
      <property name="DocumentInternals" value="True" />
      <property name="DocumentProtected" value="True" />
      <property name="DocumentPrivates" value="True" />
      <property name="DocumentEmptyNamespaces" value="False" />
      <property name="IncludeAssemblyVersion" value="True" />
      <property name="CopyrightText" value="No copyright"/>
      <property name="CopyrightHref" value=""/>
    </documenter>
  </documenters>
</ndoc>
  • assemblies 文件集定义了我们想要从中生成文档的 .NET 二进制文件。这些二进制文件必须使用编译器的 -doc 选项进行编译,csc.exe,并且 - 当然 - 它们必须包含合理的文档标签,如 summary、param 等。有关 NDoc 识别的文档标签的更多详细信息,请参见 http://ndoc.sourceforge.net/content/tags.htm
  • summaries 文件集包含一个 XML 文件列表,其中包含代码中使用的命名空间的整体文档。附带的代码使用了一个名为 Com 的命名空间,该命名空间被文档化如下:
  • <namespaces>
      <namespace name="Com">
        Example of a COM component in C# and how to build it with NAnt
      </namespace>
    </namespaces>
  • 我们将 MSDN 风格的文档生成器与 OutputTarget 属性的值“Web”一起使用。这确保了生成具有类似 MSDN 外观的上述 HTML 代码。有关 NDoc 可用的文档生成器和格式的更多详细信息,请参见 http://ndoc.sourceforge.net/content/documenters.htm
  • OutputDirectory 属性定义了生成结果 HTML 文件的目录。
  • 有关 MSDN 风格文档生成器 MSDN 使用的其他属性的详细信息,请参见 http://ndoc.sourceforge.net/content/msdn.htm

创建 MSI

使用 NAntContrib 的 MSI 任务创建安装文件 comtest.msihttp://nantcontrib.sourceforge.net/release/latest/help/tasks/msi.html

<msi sourcedir="." license="license.rtf" output="comtest.msi">
...
</msi>

生成的 MSI 文件将创建在 sourcedir\output 目录下,即 .\comtest.msi。属性 license 中的许可证文件不是必需的,但如果未定义它,则生成的 MSI 将包含有关缺少许可证的警告。

MSI 任务包含以下子元素,其中大部分(如果不是全部)必须按指定顺序定义:

  1. summaryinformation
  2. 属性
  3. directories
  4. environment
  5. components
  6. registry
  7. shortcuts
  8. 功能

summaryinformation

包含描述包的信息条目。

<summaryinformation>
  <title>COM with C# and NAnt</title>
  <subject>comtest</subject>
  <author>Daniel Stoinski</author>
  <keywords>COM, C#, NAnt</keywords>
  <comments>Installation package for comtest</comments>
  <template>;1033</template>
  <revisionnumber>{11111121-1121-1121-1121-111111111121}</revisionnumber>
  <creatingapplication>NAnt</creatingapplication>
</summaryinformation>

请确保修订号始终包含相同的 GUID,否则 NAnt 将自动生成它,您将无法使用新创建的 MSI 文件升级、重新安装或修复现有安装。

属性

有关 MSI 属性的描述,请参见 MSDN 上的 属性参考。在下面的属性中,最有趣的是:

  • ProductCodeUpgradeCode - 对于一个版本,请使用相同的唯一 GUID;否则,MSI 包的升级、重新安装或修复将失败。
  • ALLUSERS - 如果要为所有用户安装包,请将其设置为非 0 值;如果必须为当前用户安装包,请将其设置为 0。例如,可以在 快捷方式 部分看到这种区别,该部分安装在 C:\Documents and Settings\All Users\Start MenuC:\Documents and Settings\%USERNAME%\Start Menu 下。如果 ALLUSERS 非零,则需要管理员权限才能安装 MSI。
<properties>
  <property name="ProductName" value="comtest" />
  <property name="ProductVersion" value="0.0.0" />
  <property name="Manufacturer" value="Daniel Stoinski" />
  <property name="ProductCode" value="{11111122-1122-1122-1122-111111111122}" />
  <property name="UpgradeCode" value="{11111123-1123-1123-1123-111111111123}" />
  <property name="ALLUSERS" value="2" />
</properties>

属性也可以从 msiexec.exe 的命令行参数设置。此部分中定义的属性将覆盖从命令行设置的属性。请使用真正唯一的 GUID 值,最好是使用 uuidgen.exe 生成的。uuidgen.exe 使用小写十六进制数字,而 MSI 定义需要大写。您需要先将这些小写字母转换为大写字母,然后才能将 uuidgen.exe 生成的 GUID 插入 MSI 定义。

directories

我们决定将包安装到以下目录结构中:

  • %ProgramFiles%
    • comtest - 我们的主安装目录
      • bin - 二进制文件;在本例中是 DLL comtest.dll
      • com - COM 文件;在本例中是类型库 comtest.tlb
      • doc - 文档,使用 ndoc 生成
<directories>
  <directory name="D_COMTEST" foldername="comtest" root="ProgramFilesFolder">
    <directory name="D_COMTESTB" foldername="bin" />
    <directory name="D_COMTESTC" foldername="com" />
    <directory name="D_COMTESTD" foldername="doc" />
  </directory>
  <directory name="D_COMTESTS" foldername="ComTest" root="ProgramMenuFolder" />
</directories>

这些目录连接到 components 部分中的组件,组件也连接到 features。如果用户决定不安装某个功能,则相应的组件也不会安装,然后连接到该组件的目录也不会创建。

environment

MSI 能够设置环境变量。这对于设置例如 PATH 中的搜索路径,或者 C 包含文件 (INCLUDE) 或库和类型库 (LIB) 的路径很有用。在这里,我们将环境变量 LIB 设置为 %ProgramFiles%\comtest\com 目录,类型库 comtest.tlb 就位于其中。

<environment>
  <variable name="LIB" append="[D_COMTESTC]" component="C_COMTESTC" />
</environment>

因此,我们可以轻松地编译 C++ 客户端,而无需向 C 编译器输入任何额外的 -I 命令行参数。

环境变量的值仅在打算安装 C_COMTESTC 组件的功能时定义。环境变量的值可以是一个所谓的格式化字符串(另请参见 http://msdn.microsoft.com/en-us/library/Aa368609);它可能包含对 MSI 中定义的属性或目录的引用,然后由安装程序在安装过程中进行扩展。

在 MSI 中设置环境变量仍有一个缺点——环境变量仅为当前用户设置,而与 properties 部分中设置的 MSI 属性 ALLUSERS 的值无关。

components

组件连接功能和底层元素,如目录、环境变量、快捷方式和注册表项。组件条目还包含最重要的信息:MSI 需要安装哪些文件。

我们示例安装的结构相当简单:它包含三个 功能,每个功能包含一个组件,每个组件负责一个目录,其中一些组件还有其他问题,如环境变量、快捷方式或注册表项。

元素 功能 组件 (Component) Directory(目录) 要安装的文件 其他问题
二进制文件 F_COMTESTB C_COMTESTB D_COMTESTB comtest.dll  
COM 条目 F_COMTESTC C_COMTESTC D_COMTESTC comtest.tlb 注册表项、环境变量 LIB
文档 F_COMTESTD C_COMTESTD D_COMTESTD 用于 ndoc 文档的 HTML 文件 开始/程序下的 快捷方式

我们示例的组件定义如下:

<components>
  <component name="C_COMTESTB"
             id="{11111124-1124-1124-1124-111111111124}"
             attr="2"
             directory="D_COMTESTB"
             feature="F_COMTESTB">
    <key file="${bdllfile}" />
    <fileset basedir="${bindir}">
      <include name="${bdllfile}" />
    </fileset>
  </component>
  <component name="C_COMTESTC"
             id="{11111125-1125-1125-1125-111111111125}"
             attr="2"
             directory="D_COMTESTC"
             feature="F_COMTESTC">
    <key file="${btlbfile}" />
    <fileset basedir="${bindir}">
      <include name="${btlbfile}" />
    </fileset>
  </component>
  <component name="C_COMTESTD"
             id="{11111126-1126-1126-1126-111111111126}"
             attr="2"
             directory="D_COMTESTD"
             feature="F_COMTESTD">
    <key file="index.html" />
    <fileset basedir="${docdir}">
      <include name="*.*" />
    </fileset>
  </component>
</components>

属性和子元素具有以下含义:

  • component name:组件的符号名称,在其他元素(如 environment、features 等)中引用。
  • component id:组件的唯一 GUID,最好使用 uuidgen 生成;请记住,它不能包含小写十六进制字母。
  • component attr:估算组件的安装方式。有关更多详细信息,请参见 NAntContrib 任务 MSI 的说明。
  • component directory:包含 directories 部分中定义的组件名称。
  • component feature:组件所属的 feature 的名称。
  • key file 元素命名的是特定组件目录中的文件,安装程序可以使用这些文件来检测组件是否已安装。
  • fileset 定义了此组件要安装的所有文件集。

registry

注册 COM 组件需要设置几个 Windows 注册表项和值。有关注册 COM 组件和所需注册表项的更多详细信息,请参见 MSDN 上的文档 MSDN。与 在没有 MSI 的情况下安装 COM 组件一章中描述的注册类似,您可以使用 regasm.exe 程序生成一个 regedit 格式的文本文件:

regasm /regfile:comtest.reg /codebase comtest.dll

对于我们示例中的 comtest.dll 组件,该调用生成了以下文件 comtest.reg

REGEDIT4

[HKEY_CLASSES_ROOT\Com.ComTest]
@="Com.ComTest"

[HKEY_CLASSES_ROOT\Com.ComTest\CLSID]
@="{11111112-1112-1112-1112-111111111112}"

[HKEY_CLASSES_ROOT\CLSID\{11111112-1112-1112-1112-111111111112}]
@="Com.ComTest"

[HKEY_CLASSES_ROOT\CLSID\{11111112-1112-1112-1112-111111111112}\InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="Com.ComTest"
"Assembly"="comtest, Version=0.0.0.0, 
        Culture=neutral, PublicKeyToken=7616258ad77c5bde"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="file:///D:/temp/comtest.dll"

[HKEY_CLASSES_ROOT\CLSID\{11111112-1112-1112-1112-111111111112}\InprocServer32\0.0.0.0]
"Class"="Com.ComTest"
"Assembly"="comtest, Version=0.0.0.0, 
           Culture=neutral, PublicKeyToken=7616258ad77c5bde"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="file:///D:/temp/comtest.dll"

[HKEY_CLASSES_ROOT\CLSID\{11111112-1112-1112-1112-111111111112}\ProgId]
@="Com.ComTest"

[HKEY_CLASSES_ROOT\CLSID\{11111112-1112-1112-1112-111111111112}\
         Implemented Categories\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}]

MSI 任务允许定义安装期间将放入注册表中的注册表项和值。它们在 registry 部分中使用 XML elements key 定义,具有属性:

  • attribute component - 是安装程序将定义注册表项的 MSI 组件的名称,另请参见 components
  • attribute root - 是根键的名称;使用字符串表示 HKEY_CLASSES_ROOT。
  • attribute path - 要定义的键的名称。
  • 子元素 value - 是键的值。它有两个属性:
    • name - 是值的名称;如果未给出,则使用默认名称 @。
    • value - 是键值本身。

通常,我将从 regasm 生成的注册表文件中的所有条目复制到 NAnt 文件中,并且只更改了以下条目:

  1. CodeBase 的值设置为 [D_COMTESTB]\comtest.dll;因此,DLL 文件的位置将最终由 Windows 安装程序确定。我们无法预测文件最终将安装在哪里;在本例中,%ProgramFiles% 可能位于 C:D: 或其他位置,或者您可以生成一个用户可以输入自己的位置的 MSI。
  2. 即使使用较新的 .NET Framework 编译我们的代码,.NET 运行时版本也必须始终为 2.0.50727。
  3. 程序集名称
  4. "Assembly"="comtest, Version=0.0.0.0, Culture=neutral, PublicKeyToken=7616258ad77c5bde"

    我们的 comtest.dll 可以通过 NAnt 函数 assembly::get-name()assembly::load-from-file() 找到,这样您就不必记住版本和强名称密钥了 :-)

  5. 注册表项已连接到 C_COMTESTC 组件,并且仅在打算安装此组件时应用。

因此,我们 COM 组件的注册表定义如下:

<property name="assname"
          value="${assembly::get-name(assembly::load-from-file('comtest.dll'))}" />
<property name="fkver" value="2.0.50727" />
<property name="codebase" value="[D_COMTESTB]\comtest.dll" />
<registry>
  <key component="C_COMTESTC" root="classes" path="Com.ComTest">
    <value value="Com.ComTest" />
  </key>
  <key component="C_COMTESTC" root="classes" path="Com.ComTest\CLSID">
    <value value="{11111112-1112-1112-1112-111111111112}" />
  </key>
  <key component="C_COMTESTC"
       root="classes"
       path="CLSID\{11111112-1112-1112-1112-111111111112}">
    <value value="Com.ComTest" />
  </key>
  <key component="C_COMTESTC"
       root="classes"
       path="CLSID\{11111112-1112-1112-1112-111111111112}\InprocServer32">
    <value value="mscoree.dll" />
    <value name="ThreadingModel" value="Both" />
    <value name="Class" value="Com.ComTest" />
    <value name="Assembly" value="${assname}" />
    <value name="RuntimeVersion" value="${fkver}" />
    <value name="CodeBase" value="${codebase}" />
  </key>
  <key component="C_COMTESTC"
       root="classes"
       path="CLSID\{11111112-1112-1112-1112-111111111112}\InprocServer32\0.0.0.0">
    <value name="Class" value="Com.ComTest" />
    <value name="Assembly" value="${assname}" />
    <value name="RuntimeVersion" value="${fkver}" />
    <value name="CodeBase" value="${codebase}" />
  </key>
  <key component="C_COMTESTC"
       root="classes"
       path="CLSID\{11111112-1112-1112-1112-111111111112}\ProgId">
    <value value="Com.ComTest" />
  </key>
  <key component="C_COMTESTC"
       root="classes"
       path="CLSID\{11111112-1112-1112-1112-111111111112}\
                Implemented Categories\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}">
   <value value="NAnt required dummy value" />
  </key>
</registry>

shortcuts

MSI 安装程序能够创建快捷方式。这通常用于在“开始”菜单或桌面上创建快捷方式。在本例中,我决定为我们示例包的文档在 Start/Programs/comtest 下创建一个快捷方式:

<shortcuts>
  <shortcut name="documentation"
            directory="D_COMTESTS"
            filename="documentation"
            component="C_COMTESTD"
            target="[$C_COMTESTD]\index.html">
    <description>documentation</description>
  </shortcut>
</shortcuts>

仅当用户决定安装文档时才会安装快捷方式。

功能

最后但同样重要的是,features 部分最终定义了需要安装的内容。我们在示例中定义了三个功能:

  1. 二进制文件
  2. 文档
  3. COM 组件

另请参见 components 下的表格。

custinst

<features>
  <feature name="F_COMTESTB" title="binary files" display="1" typical="true" />
  <feature name="F_COMTESTD" title="documentation" display="1" typical="true" />
  <feature name="F_COMTESTC" title="COM components" display="1" typical="true" />
</features>

如果用户决定不安装特定功能,例如,她/他不想安装文档,那么 F_COMTESTD 功能将不会安装;同样,Windows 安装程序将省略属于 F_COMTESTD 的所有组件,在本例中是 C_COMTESTD,因此最终 D_COMTESTD 目录将不会创建,NDoc 生成的文件也不会复制到其中。在这种情况下,Start/Programs 下的快捷方式也不会创建。

使用 COM 组件

Visual Basic 脚本 VBS

示例 VBS 代码

Dim obj
Dim s

Set obj = CreateObject("Com.ComTest")
s = obj.MyMethod
WScript.Echo s
s = obj.MyMethod(2.0)
WScript.Echo s

创建 ComTest 类的实例,并调用其中的 MyMethod 方法,首先使用默认参数 1.0,然后使用参数 2.0。生成的字符串存储在变量 s 中,然后使用 VBS 函数 WScript.Echo 打印。VBS 脚本可以使用 cscript.exewscript.exe 调用,它们都包含在每个 Windows 安装的 %SystemRoot%\System32 目录下。

D:\>cscript comclnt.vbs
Microsoft (R) Windows Script Host Version 5.7
Copyright (C) Microsoft Corporation. All rights reserved.

MyMethod call 1 parameter 1
MyMethod call 2 parameter 2

cscript.exewscript.exe 之间的唯一区别是,第一个解释器使用当前控制台 cmd.exestdout 来打印输出,而后者则使用消息框。

Visual Basic for Applications: VBA

必须将类型库添加到 VBA 项目的引用中。以下屏幕截图显示了如何在 Excel 中执行此操作:

  1. 启动 Excel 并使用 Alt-F11 打开 VBA 窗口。在 VBA 窗口中,选择菜单 Tools/References。
  2. vba1

  3. 按“Browse”按钮并选择文件 %ProgramFiles%\comtest\com\comtest.tlb
  4. vba2

  5. 激活选定的 COM 组件 comtest
  6. vba3

  7. 在 VBA 模块中测试代码。在键入代码时,VBA IDE 应该会帮助您并提供 COM 组件名称 (comtest.comtest) 和方法名称 (MyMethod) 及其参数。
  8. vba4

  9. 新组件也应该在对象浏览器 (F2) 中可见。

Microsoft C++

以下 C++ 程序 comclnt.cpp

#include <windows.h>
#include <atlbase.h>
#include <stdio.h>
#import <comtest.tlb>

using ComTest::IComTest;

#ifndef SPRTF
# ifdef UNICODE
#  ifdef __CEGCC__
extern int _snwprintf(wchar_t*, size_t, const wchar_t*, ...);
#  endif
#  define SPRTF _snwprintf
# else
#  define SPRTF _snprintf
# endif
#endif

#ifdef UNICODE
# define PRTERR(aHR) { TCHAR buf[128]; printf("%s:%d %ls\n", 
                       __FILE__, __LINE__, _errMsg(buf, 127, aHR)); }
#else
# define PRTERR(aHR) { TCHAR buf[128]; printf("%s:%d %s\n", 
                       __FILE__, __LINE__, _errMsg(buf, 127, aHR)); }
#endif

static LPCTSTR _errMsg(LPTSTR  theBuf,
                       size_t  aMaxLen,
                       HRESULT aSysErr) {
  if (theBuf != NULL) {
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
                  NULL,
                  aSysErr,
                  0,
                  theBuf,
                  aMaxLen,
                  NULL);
    theBuf[aMaxLen] = TEXT('\0');
  }
  return theBuf;
} /* errMsg() */

int main() {
  CComBSTR str;
  CLSID    clsid;
  HRESULT  hr;
  IComTest *ct;

  ct = NULL;
  hr = CoInitialize(NULL);
  if (!SUCCEEDED(hr)) {
    PRTERR(hr);
    goto end;
  }
  hr = CLSIDFromProgID(L"Com.ComTest", &clsid);
  if (!SUCCEEDED(hr)) {
    PRTERR(hr);
    goto end;
  }
  hr = CoCreateInstance(clsid,
                        NULL,
                        CLSCTX_INPROC_SERVER,
                        __uuidof(IComTest),
                        (void **) &ct);
  if (!SUCCEEDED(hr)) {
    PRTERR(hr);
    goto end;
  }
  USES_CONVERSION;
  hr = ct->raw_MyMethod(1.0, &str);
  if (!SUCCEEDED(hr)) {
    PRTERR(hr);
    goto end;
  }
#ifdef UNICODE
  printf("MyMethod returned: %ls\n", OLE2CT(str));
#else
  printf("MyMethod returned: %s\n", OLE2CT(str));
#endif
  hr = ct->raw_MyMethod(2.0, &str);
  if (!SUCCEEDED(hr)) {
    PRTERR(hr);
    goto end;
  }
#ifdef UNICODE
  printf("MyMethod returned: %ls\n", OLE2CT(str));
#else
  printf("MyMethod returned: %s\n", OLE2CT(str));
#endif
  end:
  if (ct != NULL) ct->Release();
  return 0;
}

使用 Microsoft 特有的命令 #import 来应用类型库文件 comtest.tlb。由于我们将 MSI 安装目录的 com 子文件夹添加到了环境变量 LIB 中(另请参见 environment),编译器 cl.exe 将能够找到类型库文件,而无需将此目录添加到 #import 命令中或在编译器命令行选项 -I 中定义它。然后,代码初始化 COM 操作,创建 ComTest 类的实例,并——与上面描述的 VBS 代码类似——调用其中的 MyMethod 方法两次。代码可以使用简单的 cl.exe comclnt.cpp 进行编译。

D:\>cl.exe -nologo comclnt.cpp
comclnt.cpp

D:>comclnt.exe
MyMethod returned: MyMethod call 1 parameter 1
MyMethod returned: MyMethod call 2 parameter 2

附带的代码还包含此 C++ 程序的 makefile,以便可以使用 nmake.exe -f makefile.vc 编译代码。当然,您也可以使用 NAnt 的 cl 任务(http://nant.sourceforge.net/release/latest/help/tasks/cl.html)来编译 C 程序。

历史

  • 2010-08-31:文档初始版本。
  • 2010-09-05:重新编辑了 HTML 代码。
© . All rights reserved.