史蒂夫·怀特
Microsoft英国开发人员的顶级支持
莱斯利·穆勒
全球 IT 研究 & 发展, 信贷苏西第一波士顿
2005 年 7 月
摘要: Microsoft 平台 SDK 可以很好地记录 隔离应用程序和并行程序集的主题。 但是,并不是每个人都将本主题等同于无注册激活 COM 组件。 无注册 COM 是一项平台功能,对锁定的服务器和在共享基础结构上隔离的应用程序的企业非常感兴趣。 本文逐步讲解了本机客户端对本机 COM 组件的无注册激活以及托管客户端通过 COM 互操作激活的工作示例。 (打印页 18 页)
适用于:
Microsoft Windows Server 2003
Microsoft Windows XP
Microsoft .NET Framework 版本 1.1
Microsoft Visual Studio .NET 2003
Microsoft Visual Studio 6.0
下载本文随附的示例,MSDNRegFreeCOM.msi。
内容
介绍
Registration-Free COM 术语
运行示例
生成 Visual C++ COM 服务器
生成 C++/.NET 客户端
Registration-Free 激活
Visual Basic 6.0 COM 服务器和客户端
不兼容的公寓
使用激活上下文 API
故障 排除
结论
进一步阅读
介绍
无注册 COM 是Microsoft Windows XP (SP2 for) 上可用的机制。基于 NET 的组件)和Microsoft Windows Server 2003 平台。 顾名思义,该机制可以轻松(例如,使用 XCOPY)将 COM 组件部署到计算机,而无需注册它们。
在目标平台上,初始化进程及其依赖模块的一个阶段是将任何关联的 清单文件 加载到名为 激活上下文的内存结构中。 如果没有相应的注册表项,则它是一个激活上下文,它提供 COM 运行时所需的绑定和激活信息。 COM 服务器或客户端中不需要特殊代码,除非你选择通过使用 激活上下文 API 自行生成激活上下文来不使用文件。
在本演练中,我将生成一个简单的本机 COM 组件,并从本机客户端和托管客户端中注册和注销它。 组件和本机客户端将显示在 Visual C++ 和 Visual Basic 6.0 中;托管客户端将显示在 C# 和 Visual Basic .NET 中。 可以下载源代码和示例,并立即在操作中查看它们,也可以按照演练操作并逐步生成它们。
Registration-Free COM 术语
任何熟悉 .NET Framework 技术的人都将习惯于术语 程序集,该术语表示一组部署、命名和版本控制为单元的模块,其中一个模块包含定义集的 清单。 在无注册 COM 中,程序集 和 清单 术语被借用,这些概念与概念相似,但与 .NET 对应项不同。
无注册 COM 使用 程序集 来表示一组或多台 PE 模块(即本机 或 托管)部署、命名和作为单元进行版本控制。 无注册 COM 使用 清单 引用包含 XML 的 .manifest 扩展名的文本文件,该文件定义 程序集(程序集清单)的标识及其类的绑定和激活详细信息,或定义 应用程序(应用程序清单)的标识以及一个或多个程序集标识引用。 程序集清单文件为程序集命名,应用程序清单文件为应用程序命名。
术语 并行 (SxS) 程序集 是指通过清单文件配置同一 COM 组件的不同版本,以便它们可由不同线程同时加载,而无需注册。
运行示例
下载并提取示例代码后,你将找到名为 \deployed的文件夹。 下面是客户端应用程序的 Visual C++ 版本(client.exe)、其清单(client.exe.manifest)、COM 服务器的 Visual C++ 版本(SideBySide.dll)及其清单(SideBySide.X.manifest)。 继续运行 client.exe。 预期结果是,client.exe 将激活 SideBySideClass(在 SideBySide.dll中实现)的实例,并显示调用其 Version 方法的结果,该方法应类似于“1.0.0-CPP”。
生成 Visual C++ COM 服务器
步骤 1
第一步是生成 COM 服务器。 在 Visual Studio 中创建新的 Visual C++ ATL 项目,并将其调用 SideBySide。 在 ATL 项目向导的 应用程序设置 选项卡上,取消选中“特性化”复选框,然后选择“允许合并代理/存根代码 复选框。
在解决方案资源管理器中,右键单击项目节点并选择 添加 |添加类...。选择 ATL 简单对象,然后选择 打开。 为类指定 SideBySideClass 的短名称,然后单击 完成。
在类视图中,右键单击 ISideBySideClass 节点,然后选择 添加 |添加方法...。在“添加方法向导”中,输入 版本 作为方法名称,选择 BSTR* 的参数类型,输入 pVer 作为参数名称,选中 重试 复选框,然后单击 添加,然后单击 完成。
在类视图中,展开 CSideBySideClass 节点,然后双击 Version 方法。 将 // TODO 行替换为:
*pVer = SysAllocString(L"1.0.0-CPP");
生成发布版本并将 \release\SideBySide.dll 复制到 \deployed。
生成 C++/.NET 客户端
下一步是生成客户端。 在本演练的这一部分中,可以选择为 Visual C++ 或 Visual C++ COM 服务器生成 .NET 客户端。 无需说,可以混合和匹配以 Visual C++、Visual Basic 6.0 和 .NET 编写的客户端和服务器。 如果想要这样做,你会发现这些样本很难修正,以便它们协同工作。 客户端和服务器集在演练中组织,以演示 as-is工作的代码。
步骤 2(选项 A:视觉对象C++)
在与 SideBySide 项目文件夹相关的同级文件夹中创建名为 客户端 的新 Visual C++ Win32 控制台项目。 在 Win32 应用程序向导的“应用程序设置”选项卡上,选中“添加对 ATL 的支持”复选框。
编辑 stdafx.h,并在文件顶部紧接在 #pragma once后面添加以下行:
#define _WIN32_DCOM
此外,stdafx.h 在文件底部添加以下行:
#import "..\deployed\SideBySide.dll" no_namespace
将 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。
步骤 2 (选项 B:.NET Framework)
在 Visual Studio .NET 2003 中,创建一个名为 客户端 的新 C# 或 Visual Basic .NET 控制台应用程序,该应用程序相对于 SideBySide 项目的文件夹中。 从 添加引用 对话框的 COM 选项卡添加对 SideBySide 1.0 类型库 的引用。
将以下代码粘贴到 main main 方法中:
C# 代码
SideBySideLib.ISideBySideClass obj =
new SideBySideLib.SideBySideClassClass();
Console.WriteLine(obj.Version());
Console.ReadLine();
Visual Basic .NET 代码
Dim obj As SideBySideLib.ISideBySideClass =
New SideBySideLib.SideBySideClassClass
Console.WriteLine(obj.Version())
Console.ReadLine()
生成发布版本并将 client.exe 复制到 \deployed。
步骤 3
目前,除某些中间文件外,\deployed 文件夹应包含,只有 client.exe 和 SideBySide.dll,后者将由其生成过程注册。 若要检查服务器和客户端在这些正常情况下是否协同工作,请运行 \deployed\client.exe 并记下预期的输出“1.0.0-CPP”。
步骤 4
本演练介绍如何 无注册 COM,因此我们现在需要注销 SideBySide 程序集。 在命令提示符下,导航到 \deployed 文件夹并执行命令:regsvr32 /u SideBySide.dll。
步骤 5
若要查看上一步的效果,请再次运行 \deployed\client.exe,并看到消息“类未注册”。 在此阶段,我们挫败了 COM 运行时在注册表中查找所需的信息,但我们尚未以替代方式提供信息。 我们将在以下步骤中纠正这一点。
Registration-Free 激活
步骤 6
在 \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.X"
version="1.0.0.0" />
</dependentAssembly>
</dependency>
</assembly>
步骤 7
在 \deployed 文件夹中,为 SideBySide.dll 组件创建专用程序集清单文件(文本文件),并将其 SideBySide.X.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.X"
version="1.0.0.0" />
<file name = "SideBySide.dll">
<comClass
clsid="{[CLSID_SideBySideClass]}"
threadingModel = "Apartment" />
<typelib tlbid="{[LIBID_SideBySide]}"
version="1.0" helpdir=""/>
</file>
<comInterfaceExternalProxyStub
name="ISideBySideClass"
iid="{[IID_ISideBySideClass]}"
proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
baseInterface="{00000000-0000-0000-C000-000000000046}"
tlbid = "{[LIBID_SideBySide]}" />
</assembly>
我以占位符的形式编写了 GUID 值,这些值将特别适用于你的项目。 可以在 SideBySide 项目的 SideBySide.idl 文件中,或通过在 OLE/COM ObjectViewer(Oleview.exe) 工具中打开 SideBySide.dll 来找到这些占位符的相应值。
[
object,
uuid([IID_ISideBySideClass]),
dual,
nonextensible,
helpstring("ISideBySideClass Interface"),
pointer_default(unique)
]
interface ISideBySideClass : IDispatch{
[id(1), helpstring("method Version")] HRESULT
Version([out,retval] BSTR* pVer);
};
[
uuid([LIBID_SideBySide]),
version(1.0),
helpstring("SideBySide 1.0 Type Library")
]
library SideBySideLib
{
importlib("stdole2.tlb");
[
uuid([CLSID_SideBySideClass]),
helpstring("SideBySideClass Class")
]
coclass SideBySideClass
{
[default] interface ISideBySideClass;
};
};
步骤 8
此时,我应将程序集清单文件嵌入为 Win32 资源的主题。 如果开发人员既能够又愿意重新生成其 COM 组件,建议将程序集清单(如在上一步中创建的组件)作为类型为 RT_MANIFEST 的 Win32 资源(在 windows.h中定义)。 如果无法执行此操作,请小心为程序集(因此程序集清单)提供不同于 COM DLL 文件名的名称。 因此,在上述情况下,COM DLL 称为 SideBySide,但程序集称为 SideBySide.X。 如果对此约束的原因感兴趣,请参阅 故障排除 部分。 在本演练中,程序集清单未嵌入,以反映许多实际情况下,这样做将不可行。
步骤 9
若要验证清单文件是否正常工作,客户端再次能够激活 SideBySideClass 类,运行 \deployed\client.exe 并记下预期的输出“1.0.0-CPP”。
Visual Basic 6.0 COM 服务器和客户端
步骤 1
第一步是生成 COM 服务器。 创建新的 Visual Basic 6.0 ActiveX DLL 项目。 在“项目资源管理器”中选择 Project1 节点,然后在“属性”窗口中,将其名称更改为 SideBySide。 在项目资源管理器中,选择 Class1 节点,并在“属性”窗口中将其名称更改为 SideBySideClass。
将以下子例程粘贴到代码窗口中:
Public Function Version()
Version = "1.0.0-VB6"
End Function
选择 文件 |SideBySide.dll... 并导航到 \deployed 文件夹,然后单击 “确定”。 若要防止每次生成 dll 时生成新的 GUID,请选择 Project |SideBySide 属性...,然后单击“组件”选项卡,然后在“版本兼容性”组中选择 二进制兼容性 单选按钮。
步骤 2
创建新的 Visual Basic 6.0 Standard EXE 项目。 在“项目资源管理器”中选择 Project1 节点,并在“属性”窗口中将其名称更改为 客户端。 选择 文件 |将 Project 另存为,并将窗体文件和项目文件保存在相对于 SideBySide 项目的文件夹中。 选择 项目 |引用,选中 SideBySide旁边的复选框,然后单击“确定”。
双击窗体设计器中的主窗体,并将以下代码粘贴到 Sub Form_Load()中:
Dim obj As New SideBySideClass
MsgBox obj.Version()
选择 文件 |client.exe... 并导航到 \deployed 文件夹,然后选择 确定。
步骤 3
目前,除某些中间文件外,\deployed 文件夹应包含 client.exe 和 SideBySide.dll;后者将由其生成过程注册。 若要检查服务器和客户端在这些正常情况下是否协同工作,请运行 \deployed\client.exe 并记下预期的输出“1.0.0-VB6”。
步骤 4
本演练介绍如何 无注册 COM,因此我们现在需要注销 SideBySide 程序集。 在命令提示符下,导航到 \deployed 文件夹并执行命令:regsvr32 /u SideBySide.dll。
步骤 5
若要查看上一步的效果,请再次运行 \deployed\client.exe,你将看到消息“运行时错误'429':ActiveX 组件无法创建对象”。 在此阶段,我们挫败了 COM 运行时在注册表中查找所需的信息,但我们尚未通过替代方式提供信息。 我们将在以下步骤中纠正这一点。
步骤 6
在 \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.X"
version="1.0.0.0" />
</dependentAssembly>
</dependency>
</assembly>
步骤 7
在 \deployed 文件夹中,为 SideBySide.dll 组件创建专用程序集清单文件(文本文件),并将其 SideBySide.X.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.X"
version="1.0.0.0" />
<file name = "SideBySide.dll">
<comClass
clsid="{[CLSID_SideBySideClass]}"
threadingModel = "Apartment" />
<typelib tlbid="{[LIBID_SideBySide]}"
version="1.0" helpdir=""/>
</file>
<comInterfaceExternalProxyStub
name="_SideBySideClass"
iid="{[IID__SideBySideClass]}"
proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
baseInterface="{00000000-0000-0000-C000-000000000046}"
tlbid = "{[LIBID_SideBySide]}" />
</assembly>
我以占位符的形式编写了 GUID 值,这些值将特别适用于你的项目。 可以通过在 OLE/COM ObjectViewer (Oleview.exe) 工具中打开 SideBySide.dll 找到这些占位符的相应值。
[
uuid([LIBID_SideBySide]),
version(1.0),
custom(50867B00-BB69-11D0-A8FF-00A0C9110059, 8169)
]
library SideBySide
{
// TLib : // TLib : OLE Automation : {00020430-0000-0000-
C000-000000000046}
importlib("stdole2.tlb");
// Forward declare all types defined in this typelib
interface _SideBySideClass;
[
odl,
uuid([IID__SideBySideClass]),
version(1.0),
hidden,
dual,
nonextensible,
oleautomation
]
interface _SideBySideClass : IDispatch {
[id(0x60030000)]
HRESULT Version([out, retval] VARIANT* );
};
[
uuid([CLSID_SideBySideClass]),
version(1.0)
]
coclass SideBySideClass {
[default] interface _SideBySideClass;
};
};
步骤 8
若要验证清单文件是否正常工作,客户端再次能够激活 SideBySideClass 类,运行 \deployed\client.exe 并记下预期的输出“1.0.0-VB6”。
不兼容的公寓
本演练中的所有 COM 服务器都构建为在 Single-Threaded 单元中运行(即它们是 STA 或单元线程组件)。 除C++客户端外,所有客户端都是 STA,即 MTA(其线程在多线程单元中运行)。 因此,有一种情况是客户端和服务器位于不兼容的公寓中,在这种情况下,需要在代理和存根之间对 COM 调用进行跨单元封送处理。 在这种情况下,请使用程序集清单文件的以下部分:
...
<typelib tlbid="{[LIBID_SideBySide]}"
version="1.0" helpdir=""/>
</file>
<comInterfaceExternalProxyStub
name="ISideBySideClass"
iid="{[IID_ISideBySideClass]}"
proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
baseInterface="{00000000-0000-0000-C000-000000000046}"
tlbid = "{[LIBID_SideBySide]}" />
...
这些元素提供注册表中将存在的信息。 comInterfaceExternalProxyStub 元素提供了足够的信息,使 类型库封送 发生,并且适用于派生自 IDispatch 的 COM 接口(其中包括所有自动化接口)。 在这些情况下,ole32.dll 提供使用的外部代理存根(即 程序集中的文件的外部)。 如果 COM 组件仅实现 调度 或 双 接口,则这是应使用的元素。
自定义接口很少见,对于这些接口,必须做更多的工作。 请考虑一个名为 ISxSCustom 的接口,该接口直接从 IUnknown 派生,并且与自动化不兼容。 要使 SideBySideClass 实现 ISxSCustom,以及客户端在无注册方案中调用其方法,我需要将 comInterfaceProxyStub 元素添加到程序集清单。 正如元素名称所建议的,这次代理存根
<file name = "SideBySide.dll">
...
<comInterfaceProxyStub
name="ISxSCustom"
iid="{[IID_ISxSCustom]}" />
</file>
否则,我需要添加 进一步 文件元素来声明代理存根 dll:
<file name = "SideBySide.dll"> ... </file>
<file name = "SideBySidePS.dll">
<comInterfaceProxyStub
name="ISxSCustom"
iid="{[IID_ISxSCustom]}" />
</file>
如果我不需要说明上述元素,我会生成 Visual C++ COM 服务器,使其 双线程,以便其 coclass 已在客户端的 MTA 中激活。 如果客户端和服务器存在于同一单元中,则只忽略刚刚讨论的元素。 但是,我敦促你不要依赖这种情况,因为在某些情况下,组件在不同于其客户端的不同单元中激活,即使其线程模型是一致的。 考虑到所需的工作量相对较小,我认为建议 始终 在清单中包含代理存根配置。
使用激活上下文 API
在 简介 部分中,我提到在本文适用的平台上初始化应用程序过程的一个阶段是查找应用程序清单文件。 应用程序清单文件包含对应用程序具有依赖项
如果应用程序知道 之前 一组在生存期内直接或间接激活的 coclass,则应用程序清单很方便。 但是,根据设计,应用程序(例如网格服务器)相对罕见,在运行时之前,它们不会知道它们将加载的模块。 在这种情况下,调用进程初始化后引用程序集清单文件的方法。
激活上下文 API满足这一点,这会使应用程序清单文件过时。 最简单的方法是使用程序集清单文件的位置初始化 ACTCTX 结构,然后从中 创建和激活
编辑 stdafx.h,并在 _WIN32_DCOM定义后立即添加以下行:
#define _WIN32_FUSION 0x0100 // this causes activation context
structs and APIs to be included.
如果查看 client.cpp中的 _tmain 函数,请在 COM 初始化和取消初始化调用之间看到一个复合语句,用于激活和调用 SideBySideClass。 我们需要将此复合语句(大括号之间的所有内容)移动到 初始化激活上下文的新代码部分,如下所示:
ACTCTX actCtx;
memset((void*)&actCtx, 0, sizeof(ACTCTX));
actCtx.cbSize = sizeof(ACTCTX);
actCtx.lpSource = "SideBySide.X.manifest";
HANDLE hCtx = ::CreateActCtx(&actCtx);
if (hCtx == INVALID_HANDLE_VALUE)
cout << "CreateActCtx returned: INVALID_HANDLE_VALUE"
<< endl;
else
{
ULONG_PTR cookie;
if (::ActivateActCtx(hCtx, &cookie))
{
// previous compound statement goes here...
::DeactivateActCtx(0, cookie);
}
}
上述代码在激活任何 coclass 之前运行。 该代码只是读取程序集清单文件(其名称在此示例中硬编码,但应对应于要动态加载的程序集)到激活上下文(即使当前程序集)。 从这一点开始,coclass 的激活将像以前一样发生。 现在可以放弃 client.exe.manifest。
直接激活上下文 API(但仅在 Windows Server 2003 上可用)的替代方法是 Microsoft.Windows.ActCtx 对象。
无需说,激活上下文 API 比此处所示的要多得多,你可以在 进一步阅读 部分中找到完整的 API 文档的链接。
故障 排除
正如我们所看到的,COM 组件的无注册激活不需要服务器或客户端中的特殊代码。 只需匹配一对清单文件。
我建议你按照本演练的方式处理自己的无注册开发。 具体而言:首先通过查看客户端使用已注册的服务器来访问已知状态;然后注销服务器并验证错误消息是否为预期;最后,通过创建和部署清单文件来纠正这种情况。 这样,有关无注册激活的故障排除工作将仅限于清单文件的结构(如果选择这样做,则程序集清单的正确嵌入)。
排查无注册 COM 问题时,Windows Server 2003 上的事件查看器是你的朋友。 当 Windows XP 或 Windows Server 2003 检测到配置错误时,通常会显示一个标题为已启动的应用程序的错误消息框,并包含消息“此应用程序未能启动,因为应用程序配置不正确。 重新安装应用程序可能会解决此问题。建议每当看到此消息时,在 Windows Server 2003 上重现问题时,请咨询系统事件日志,并从 SideBySide 源查找事件。 我不建议在这些情况下查看 Windows XP 事件日志的原因是它总是包含一条消息,例如“为 [path]\[application filename] 生成激活上下文失败”。清单。 引用错误消息:操作成功完成,“这无助于识别问题。
各种清单文件的架构记录在平台 SDK 的标题 清单文件参考下,架构验证工具 Manifestchk.vbs 可用,因此此处我只会调用与演练相关的几个要点。 首先,让我们检查程序集清单文件。 有关示例,请回顾步骤 7。
你会回想一下,在 无注册 COM 意义上,程序集 是一种抽象的概念,通过 程序集清单 文件的内容来关联一个或多个物理文件。 程序集的名称出现在三个位置,每个位置必须相同:在程序集清单文件的 assemblyIdentity 元素的 名称 属性中;在应用程序清单文件的 dependentAssembly/assemblyIdentity 元素的 名称 属性中;和程序集清单文件的名称,不包括 .manifest 扩展名。 如果清单文件名与 应用程序 清单中的 名称 不匹配,则在 Windows Server 2003 系统事件日志中看到以下消息:“找不到应用程序清单中 名称 属性的依赖程序集值,并且系统未安装上次错误。如果 程序集 清单中的 名称 元素不正确,则在 Windows Server 2003 系统事件日志中看到以下消息:“在清单中找到的组件标识与所请求的组件的标识不匹配。
如果 SideBySide.dll 是基于 .NET Framework 的组件,则我们必须将程序集清单文件作为 Win32 资源嵌入到 SideBySide 程序集中(例如,SideBySide.manifest) 之后命名清单文件)。 但是,由于程序集加载程序的搜索顺序,对于本机 COM 组件,可选 将清单嵌入模块。 在程序集加载程序查找 [AssemblyName].manifest之前,它会查找 [AssemblyName].dll 并在其中搜索类型为 RT_MANIFEST 的 Win32 资源。 资源内的配置必须具有与 [AssemblyName] 匹配的 AssemblyIdentity 元素,以及应用程序清单的 assemblyIdentity 引用中的其他属性。
但是,如果找到
assemblyIdentity 元素定义 程序集的标识。 对于本机 COM 组件,其 名称 或其 版本 属性都不需要与任何物理文件的名称匹配,尽管最好应用某种一致性。
文件 元素是 comClass、typelib、 和 comInterfaceProxyStub 元素的父级。 其用途是查找构成 程序集的物理文件。 如果 文件 元素的 名称 属性未正确引用文件系统中的文件,则 CoCreateInstance 将返回与“未注册的类”或“找不到指定模块”对应的 HRESULT。 因此,可以在不同的文件夹中安装不同版本的 COM 组件,名称 属性可以包含路径。 在 Windows XP SP2 上,路径可以是相对路径,也可以是绝对路径,并且可以在文件系统中的任何位置引用文件夹。 Windows Server 2003 需要路径来引用应用程序根文件夹或子文件夹,如果尝试违反此规则,则 Windows Server 2003 系统事件日志中会显示以下消息:“清单或策略文件中的语法错误 [程序集清单文件名] }值 = 无效。
comClass 元素只有一个必需属性:clsid。 如果应用程序尝试激活未注册的 coclass,其 CLSID 未在程序集清单中的 comClass 元素中列出,则 CoCreateInstance 将返回值为 REGDB_E_CLASSNOTREG(0x80040154)的 HRESULT,其消息文本为“未注册类”。
正如我所说,typelib 和 comInterface[External]ProxyStub 元素在客户端和服务器存在于不同的单元中时是必需的,因此仅在处理这些元素时才能看到以下错误。 这些元素中的配置错误导致 CoCreateInstance 返回与消息“库未注册”、“加载类型库/DLL 错误”或“不支持此类接口”的消息对应的 HRESULT。 如果看到其中任何消息,请仔细检查 GUID 并确保存在所有必需的属性。 可以在平台 SDK 中找到清单文件架构。
现在,让我们关注应用程序清单文件。 有关示例,请回顾步骤 6。 应用程序清单 必须以 [application filename].manifest] 格式命名。 因此,在演练中,它被命名为 client.exe.manifest,以明确在将 client.exe 加载到进程中时应读取它。 如果未正确执行此操作,则 CoCreateInstance 将返回值为 REGDB_E_CLASSNOTREG (0x80040154) 的 HRESULT,其消息文本为“未注册类”。
应用程序清单中最重要的元素是 dependentAssembly/assemblyIdentity 元素。 此元素是对程序集清单中等效项的引用,两个元素必须与 完全匹配。 确保它们这样做的一个好方法是从程序集清单复制元素并将其粘贴到此处。 如果有任何差异,你将在 Windows Server 2003 系统事件日志中看到以下消息:“在清单中找到的组件标识与所请求的组件的标识不匹配。
结论
无注册 COM 是一种技术,可将 COM 组件从依赖于 Windows 注册表中解放出来,从而解放使用这些组件的应用程序,而无需使用专用服务器。 它使依赖于相同 COM 组件的不同版本的应用程序可以共享基础结构,并在 .NET Framework 版本控制与部署机制的回显中并行加载这些各种 COM 组件版本。
本文将引导你演示 Visual C++ 和 Visual Basic 6.0 和托管客户端编写的本机客户端应用程序对本机 COM 组件的无注册激活。 它解释了机制的工作原理,并强调了一些可能的配置错误以及如何对其进行故障排除。
进一步阅读
- Registration-Free 激活 。NET-Based 组件:演练
- 独立应用程序和并行程序集
- 清单文件架构
- 使用激活上下文 API
关于作者
史蒂夫·怀特 是一名应用程序开发顾问,在Microsoft英国的开发人员团队的顶级支持团队工作。 他支持使用 Visual C#、Windows 窗体和 ASP.NET 进行开发的客户。 他的 博客 详细了解他对音乐、可视化效果和编程的兴趣。
莱斯利·穆勒 是瑞士信贷第一波士顿分校研究 & 发展团队的技术专家。 Leslie 拥有 12 年的开发人员和技术架构师经验,在金融服务、技术初创公司、工业自动化和国防等环境中工作。 当他没有编程或做研究时,他喜欢滑雪、冰球,以及尽可能在冰岛或落基山脉等极端环境中对机动车辆做轻微疯狂的事情。