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

排查免注册 COM

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (11投票s)

2018 年 5 月 1 日

CPOL

9分钟阅读

viewsIcon

19845

downloadIcon

715

如何使用工具排查免注册 COM

引言

有很多关于如何实现无注册 COM 的文章。如果一切如广告宣传的那样工作,那么其中大部分文章都写得很好并且非常有用。但是,如果出现问题了呢?一个人该如何深入了解并尝试理解出了什么问题?

在本文中,我想介绍几种可用于排除实现和测试使用无注册 COM 的模块时可能遇到的任何问题的工具。文章附带了一个示例无注册 COM 应用程序,并通过该应用程序解释了这些工具。

这里介绍的工具实际上是 Windows 操作系统或 Windows SDK 的一部分。我还想简要提及激活上下文的重要性,对于任何从事无注册 COM 和清单文件工作的人来说,这一点都是必须了解的。

本文假设读者对 COM 和无注册 COM 有所了解,但并非全知全晓。

背景

在一切就绪后,我得到了一个运行良好的无注册 COM 服务器和客户端。COM 服务器是一个 C# DLL,它利用 .NET 4.6,而客户端是一个 C++ 应用程序。我开始着手处理其他部分的工作,后来发现在某些情况下,C++ 客户端应用程序会收到“类未注册”的消息。显然,当 C++ 客户端应用程序尝试创建 C# DLL 中包含的 COM 组件的实例时,就会出现此消息。

正是在这一点上,我开始接触到一些非常有用的工具。它们可用于查找在测试无注册 COM 模块及其与客户端应用程序的交互过程中可能出现的各种问题。

使用应用程序

我想从一个无注册 COM 应用程序开始。它包含一个公开为进程内 COM 服务器的 C# 库,以及一个通过无注册 COM 调用 C# COM 库的 C++ 控制台应用程序。在阅读本文时,您可以下载并参考示例应用程序源代码或演示二进制文件。

请注意,对于 C# COM 库,Visual Studio 选项“Register for COM interop”(注册 COM 互操作)未选中。这确保了有关 COM 接口的任何信息都不会保存在 Windows 注册表中。

如果您对无注册 COM 不太熟悉,那么示例应用程序将以非常简单的方式演示它。

选择 C# COM 库和 C++ 客户端的组合是为了强调 COM 的优点,它能够促进用不同编程语言编写的模块之间的顺畅交互。

在解释如何排除无注册 COM 的故障之前,我想提及一些在实现 COM 库时了解这些信息会很有用的重要事项。

Guid 属性的使用

在实现接口时,可以使用“guidgen.exe”之类的工具生成 GUID,并将生成的 GUID 与上面显示的 Guid 属性一起指定。这将防止每次编译项目时自动生成 GUID,并确保在后续编译过程中接口没有变化。下面是示例应用程序中的一段摘录。

using System.Runtime.InteropServices;

[Guid("7E7DB6F5-7A52-47F9-8C12-093BDA5B3811")]
public class RegFree : IRegFree
{
}

类型库和清单文件生成

这是关于如何使用生成后脚本生成类型库(.tlb)文件和清单(.manifest)文件。

"$(TargetFrameworkSDKToolsDirectory)tlbexp.exe" $(TargetPath) /out:"$(TargetDir)$(TargetName).tlb" 
"$(MTToolPath)" -managedassemblyname:$(TargetPath) -out:$(TargetName).manifest -nodependency

请注意,MTToolPath 是通过首先卸载项目(右键单击解决方案资源管理器中的 RegFreeCOMDll 并选择“卸载项目”)然后再次右键单击解决方案资源管理器中的 RegFreeCOMDll 并选择“编辑 RegFreeCOMDll.csproj”来添加到 RegFreeCOMDll.csproj 文件中的自定义值。

一切正常

当一切就绪时,一切都会很好。但是,当出现一点小问题时,事情就会开始出错。这时就凸显了理解故障排除的重要性。

以下每个故障排除部分都包含可以在您自己的机器上尝试的示例步骤,并使用本文提供的演示二进制文件。每个部分还包含有用的参考资料。

故障排除 #1

“类未注册”

当客户端应用程序的清单文件(在此例中为 RegFreeCOMDllClient.exe.manifest)不在应用程序路径中时,将出现带有上述错误消息的 COM 异常。此消息表示系统无法找到客户端应用程序正在尝试创建的 COM 组件。

如示例应用程序中所述,下面是创建 COM 对象的典型方法。

    try
    {
        IRegFreePtr pRegFree(__uuidof(RegFree));

        ...

    }
    catch (_com_error _com_err)
    {
        wprintf(L"\n %s", _com_err.ErrorMessage());
        getch();
    }

当操作系统无法找到正在创建的 COM 组件的引用时,它将抛出异常。

让我们以演示文件夹中的内容为例。

  1. 重命名“RegFreeCOMDllClient.exe.manifest”
  2. 运行 RegFreeCOMDllClient.exe。

控制台窗口中将显示“类未注册”消息。

这里的重点是,为了使无注册 COM(也称为并行程序集)正常工作,清单文件必须与客户端应用程序一起存在。这个示例似乎微不足道,但在真实环境中,如果存在许多无注册 COM 模块,则必须确保存在适当的清单文件。

参考

关于并行程序集

故障排除 #2

当程序集清单与应用程序清单之间出现差异时,将显示类似如下的消息框。

在这种情况下,引用的组件的标识与请求的组件不匹配。启动应用程序后,Windows 操作系统将显示上述消息,并且应用程序将退出。

一种显而易见的方法是查看 Windows 事件查看器(eventvwr.exe)以了解出了什么问题。在这种情况下,可以看到一个源为“SideBySide”的错误日志,如下所示。

Activation context generation failed for "D:\Demo\RegFreeCOMDllClient.exe.Manifest".
Error in manifest or policy file "D:\Demo\RegFreeCOMDll.DLL" on line 1. 
Component identity found in manifest does not match the identity of the component requested. 
Reference is RegFreeCOMDll,processorArchitecture="msil",version="2.0.0.0". 
Definition is RegFreeCOMDll,processorArchitecture="msil",version="1.0.0.0". 
Please use sxstrace.exe for detailed diagnosis.

在这种情况下,很明显,引用的组件版本与引用的组件的实际版本之间存在不匹配。

让我们以演示文件夹中的内容为例。

  1. 在文本编辑器中打开 RegFreeCOMDllClient.exe.manifest。
  2. 将 assemblyIdentity 节点下的“version=”1.0.0.0”的值更改为 version=”2.0.0.0”。
  3. 运行 RegFreeCOMDllClient.exe

将出现上述消息框。

关键是,当发生此类问题时,Windows 事件查看器中的应用程序事件是首先要查找的地方。

参考

应用程序清单

程序集清单

故障排除 #3

使用工具 **sxstrace.exe**

上面故障排除 #2 中显示的事件日志为使用另一个名为“sxstrace.exe”的工具提供了线索。

在某些情况下,Windows 事件日志对出了什么问题只会提供很小的线索。这时就可以使用一个名为“sxstrace.exe”的 Windows 工具。此工具提供 Windows SxS 管理器执行操作的详细顺序。

该工具通常位于“C:\Windows\System32”路径下。为了使用该工具,可以将以下几行放入一个批处理文件,例如“SxTrace.bat”。

sxstrace Trace -logfile:d:\demo\sxsTrace.bin 
sxstrace Parse -logfile:d:\demo\sxsTrace.bin -outfile:d:\demo\sxsTrace.txt

第一行将启动跟踪工具,并将并行程序集加载事件记录到指定的文件,例如 sxsTrace.bin。一旦批处理文件开始运行,您就可以运行有问题的无注册 COM 应用程序。然后,通过在批处理控制台窗口中按 Enter 键停止批处理文件。它将生成脚本第二行中指定的文件,即 sxsTrace.txt。

让我们以演示文件夹中的内容为例。

  1. 确保存在一个名为 D:\Demo 的文件夹。
  2. 将“RegFreeCOMDll.manifest”重命名为其他名称。
  3. 启动 SxTrace.bat。
  4. 运行 RegFreeCOMDllClient.exe。这将导致在故障排除 #2 中显示的消息框。
  5. 在 SxTract.bat 控制台中按 Enter 键。

您可以看到 D:\Demo\sxsTrace.txt 的内容,类似于以下内容。

日志描绘了操作系统在尝试解析程序集引用时执行的操作顺序。上面跟踪日志中值得注意的一行是“ERROR: Cannot resolve reference RegFreeCOMDll,processorArchitecture="msil",version="1.0.0.0".”。这行指出了需要解决的问题。

这只是一个示例,用于展示 sxsTrace.exe 工具的使用可能性。如果此工具没有遇到任何错误,那么倒数第二行将显示“INFO: Activation Context generation succeeded.”。

参考

程序集搜索顺序

诊断并行故障

故障排除 #4

使用工具 **FUSLOGVW.exe**

在某些情况下,sxstrace.exe 可能无法提供帮助。这时就可以使用另一个工具,即程序集绑定日志查看器或 FUSLOGVW.exe。该工具是 Windows SDK 的一部分,可以找到其位置,例如“C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools”。该工具应以**管理员模式**运行。

让我们以演示文件夹中的内容为例。

  1. 以管理员模式运行 FULOGVW.exe。
  2. 将 RegFreeCOMDll.dll 重命名为其他名称。
  3. 运行 RegFreeCOMDllClient.exe。

运行 RegFreeCOMDllClient.exe 后,您将在控制台窗口中收到一条消息,例如“系统找不到指定的文件”。现在查看程序集绑定日志查看器。您将看到一个类似如下的窗口。
 

选择上述窗口中的第一个日志,然后按“View Log”(查看日志)按钮。日志文件通常会在浏览器中显示。日志显示的一些最重要的消息如下所示。

从上面的日志中可以清楚地看到,系统正在尝试查找重命名的 RegFreeCOMDll.dll。由于我们已将其重命名,因此系统无法找到它,日志最后显示“All probing URLs attempted and failed.”(所有探测 URL 都已尝试但失败)。

故障排除 #5

随着对这些工具的深入使用,后来出现了一种情况,即这些工具保持沉默。“类未注册”的消息仍然弹出,直到我了解到**激活上下文**是什么。

我写这篇文章的触发点实际上就是这个“类未注册”消息。尽管我确保了故障排除 #1 中提到的那一点,但消息仍然弹出。

为了找到解决此问题的方法,我参考了 stackoverflow 上的一个非常有用的提示:here。即使如此,也没有成功。

最终,我意识到可以使用激活上下文 API,并最终将我的解决方案添加到here的解决方案列表中。

激活上下文

激活上下文是一个 Windows 操作系统子系统,它负责加载 DLL,包括 COM 的 DLL。

在我的例子中,我不得不调用 C++ 客户端应用程序中激活上下文提供的一些 API。

以下函数可以在本文提供的示例客户端应用程序中找到。

void EnableActivationContext()
{
    ACTCTX ctContext;
    memset(&ctContext, 0, sizeof(ctContext));
    WCHAR   modulePath[MAX_PATH] = { 0 };

    //Build manifest file path.
    GetModuleFileName(0, modulePath, sizeof(modulePath));
    wstring wstrModulePath = modulePath;

    int pos = wstrModulePath.rfind('\\');
    wstrModulePath = wstrModulePath.substr(0, pos);

    ctContext.cbSize = sizeof(ctContext);
    ctContext.lpSource = L"RegFreeCOMDllClient.exe.manifest";
    ctContext.lpAssemblyDirectory = wstrModulePath.c_str();
    ctContext.dwFlags = ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID;

    g_ActivationContext = CreateActCtx(&ctContext);
    ActivateActCtx(g_ActivationContext, &g_Cookie);
}

void DisableActivationContext()
{
    DeactivateActCtx(0, g_Cookie);
    ReleaseActCtx(g_ActivationContext);
}

上面的代码实际上是要求操作系统为给定的清单文件创建一个激活上下文,以便能够成功创建 COM 组件,而不会出现“类未注册”等问题。

参考

激活上下文

兴趣点

有一种有趣的现象叫做激活上下文缓存。在这种情况下,系统会忽略外部清单文件,您将陷入修改清单文件无效的情况。这在测试期间会非常令人沮丧。一种摆脱这种缓存的方法是创建一个新的文件夹,然后将所有相关的二进制文件、可执行文件和清单文件复制到该目录。然后,从该位置运行所需组件。

© . All rights reserved.