托管的无Reg COM服务器将无法激活

我开始使用一个非常复杂的客户端和服务器系统,带有COM引用和其他东西,我一直在减少直到我意识到我甚至无法获得Microsoft示例代码来管理托管COM服务器的免注册COM激活用C#编写。

服务器代码:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices.ComTypes; using System.Runtime.InteropServices; using System.ComponentModel; namespace ClassLibrary1 { [Guid("A7AC6D8C-FF17-4D2C-A3B1-2C8690A8EA04") ,ComVisible(true)] public interface IClass1 { [DispId(1)] string DummyFunction(string inputValue); } [Guid("81723475-B5E3-4FA0-A3FE-6DE66CEE211C"), ClassInterface(ClassInterfaceType.None), ComDefaultInterface(typeof(IClass1)), ComVisible(true)] public class Class1 : IClass1 { public string DummyFunction(string inputValue) { return inputValue.Substring(0, 1) + " Inserted " + inputValue.Substring(1); } } } 

客户端VB6代码:

 Dim c As ClassLibrary1.Class1 Set c = New Class1 MsgBox c.DummyFunction("Ben") 

客户端C ++代码:

 #include "stdafx.h" #import  raw_interfaces_only using namespace ClassLibrary1; int _tmain(int argc, _TCHAR* argv[]) { IClass1Ptr p; HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); hr = CoCreateInstance(__uuidof(Class1), NULL, CLSCTX_INPROC_SERVER, __uuidof(IClass1), (void **)&p); if (FAILED(hr)) { _tprintf_s(_T("Error %x\n"), hr); CoUninitialize(); return 1; } _bstr_t b = _T("Bobby"); BSTR b2; p->DummyFunction(b, &b2); wprintf_s(L"%s\n", b2); p->Release(); CoUninitialize(); return 0; } 

当我删除所有Reg-Free COM代码并使用regasm / codebase注册ClassLibrary1.dll时,两个客户端都正常工作。

然后我取消注册ClassLibrary1,并尝试使用文件Project1.exe.manifest为VB6客户端引入Reg-Free COM:

          

和ClassLibrary1.manifest:

       

现在我有时会得到错误429(ActiveX组件无法创建对象),并且(莫名其妙地)有时会出现自动化错误:

运行时错误’-2146234304(80131040)’:自动化错误

然后我尝试将COM Isolation引入C ++客户端: 隔离的COM类型库= F:\ Lib \ ClassLibrary1.tlb  - 组件文件名= ClassLibrary1.dll

现在,当我运行C ++客户端时,输出仅仅是

错误800401f9

经过Microsoft支持的各种样本的多次试验后,我发现在尝试使用非托管C ++ COM客户端实现托管COM服务器时出现的许多缺陷。 以下是我记得的关键信息,可以应用于问题中的示例代码以确保其有效。

  1. 客户端不应使用C ++项目中的Isolated COM设置。 我的内存正在消失,但微软支持告诉我这是其他的东西 – 我认为开发一个隔离的COM接口到一个未受管理的COM服务器而不是一个托管的COM服务器。 虽然从https://msdn.microsoft.com/en-us/library/zzbcs3x5(v=vs.120).aspx的描述中并未完全清楚。
  2. 客户端可以为“嵌入清单”设置选择“是”或“否”,但是如果选择“是”,则必须提供包括从属程序集信息的清单作为输入清单文件。 如果嵌入式清单不包含依赖程序集信息,则将忽略任何外部清单文件。 此外,请确保正在编辑的配置(例如,调试)与正在测试的配置相匹配! 在此处输入图像描述
  3. 如果COM服务器使用密钥签名(强名称),则客户端清单和服务器清单中的assemblyIdentity元素必须包含publicKeyToken,否则在CoCreateInstance期间将发生HRESULT错误0x80131040。
  4. 使用Visual Studio 2013将RT_MANIFEST资源作为Win32资源嵌入到托管代码中并不容易,因为C#和VB.NET项目倾向于将资源作为托管.NET资源而不是Win32资源嵌入(您可以通过打开DLL输出来validation这一点)使用资源查看器的文件,并注意到.NET可执行文件通常获得版本资源而不是其他内容,即使项目包含清单文件也是如此。 解决此问题的一种方法是创建一个这样的RC文件:

 #define RT_MANIFEST 24 #define MANIFEST_RESOURCE_ID 1 MANIFEST_RESOURCE_ID RT_MANIFEST ClassLibrary1.manifest 

然后添加这样的预构建步骤:

 "C:\Program Files (x86)\Windows Kits\8.1\bin\x86\rc.exe" "$(ProjectDir)ClassLibrary1.rc" 

然后在项目设置“应用程序”选项卡中,更改“资源”以使用ClassLibrary1.res而不是“Icon and manifest”。 但是这带来了一些问题:首先,如果不对其进行硬编码,RC.EXE的路径就不容易定义; 其次,AssemblyInfoCommon中的版本信息将被忽略,因为RC文件中的资源完全取代了.NET编译器生成的所有Win32资源。

另一种可能性是简单地将服务器COM DLL清单文件保持独立,而不是将其作为资源嵌入。 我已经读过这可能不太可靠,但它可以在Windows 7 Enterprise 64位SP1上运行。

  1. 为确保非托管客户端加载正确的.NET运行时,它需要一个定义如何加载.NET的配置文件( ConsoleApplication1.exe.config )。 对于.NET 4.5,我看到了这项工作:

      

对于.NET 3.5,似乎需要切换useLegacyV2RuntimeActivationPolicy:

      
  1. 检查所有框架和CLR版本是否同步非常重要。 理解CLR版本与框架版本不同是很重要的。 例如,如果要在.NET 3.5上构建托管COM服务器,则CLR版本(runtimeVersion)应为“2.0.50727”,但.NET版本(supportedRuntime)应为“v3.5”。

  2. 确保COM服务器的.NET Framework目标版本与客户端的supportedRuntime匹配。 如果它是从命令行构建的,则可能无法从Visual Studio项目文件中获取框架版本(例如,如果您直接运行C#的VB.NET编译器而不是调用MSBuild),请确保构建针对的是正确的框架版本。

我还没有validation上述所有内容,但打算尽快完成整个过程,以validation我抓住了所有内容。 这是我最后提到的,我还没有提到:

ConsoleApplication1.exe.manifest (在源目录中,在构建时被复制或嵌入到输出目录中)

          

ClassLibrary1.manifest

      

编辑:

现在通过完整的错误消息信息等来validation每个细节。

我首先创建一个包含两个项目的解决方案,其中包含所有默认值和问题中显示的代码。 我首先没有明确的文件,也没有问题中提到的任何项目设置,并且在我在下面的过程中进行这些更改时会明确地调用。 这些是使该项目工作的步骤和错误。

  1. 错误:“Class1:未声明的标识符”。 需要运行Developer Command Prompt并执行以下命令行以获取C ++代码可以导入的TLB文件: tlbexp ClassLibrary1.dll
  2. 将TLB文件移动到ConsoleApplication1项目目录中并重新构建。 同样的错误。
  3. 用引号替换#import raw_interfaces_only上的尖括号,因此它读取#import "ClassLibrary1.tlb" raw_interfaces_only 。 重建:成功。
  4. 此时,如果我们运行,我们得到Error 80040154 (类未注册)因为我们没有注册组件也没有设置免注册COM。
  5. 知道尝试在客户端中设置隔离COM会出现Error 800401f9我们将跳过它并尝试创建客户端清单。 创建包含以下内容的新文本文件,并将其另存为ConsoleApplication1.exe.manifest在ConsoleApplication1项目目录中:

          
  1. 此时,此解决方案中前面提到的步骤似乎有点过于复杂。 您可以通过显示隐藏文件并使用清单文件中的“包含在项目中”命令,在项目中简单地包含清单文件。
  2. 此时运行将显示错误消息“应用程序无法启动,因为它的并行配置不正确。请参阅应用程序事件日志或使用命令行sxstrace.exe工具获取更多详细信息。” 这部分是因为我们没有将ClassLibrary1.dll放在ConsoleApplication1.exe可以找到它的任何地方。 此时解析的sxstrace输出如下所示:

 INFO: Parsing Manifest File C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ConsoleApplication1.exe. INFO: Manifest Definition Identity is ConsoleApplication1,type="win32",version="1.0.0.0". INFO: Reference: ClassLibrary1,version="1.0.0.0" INFO: Resolving reference ClassLibrary1,version="1.0.0.0". INFO: Resolving reference for ProcessorArchitecture ClassLibrary1,version="1.0.0.0". INFO: Resolving reference for culture Neutral. INFO: Applying Binding Policy. INFO: No binding policy redirect found. INFO: Begin assembly probing. INFO: Did not find the assembly in WinSxS. INFO: Attempt to probe manifest at C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ClassLibrary1.DLL. INFO: Attempt to probe manifest at C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ClassLibrary1.MANIFEST. INFO: Attempt to probe manifest at C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ClassLibrary1\ClassLibrary1.DLL. INFO: Attempt to probe manifest at C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ClassLibrary1\ClassLibrary1.MANIFEST. INFO: Did not find manifest for culture Neutral. INFO: End assembly probing. ERROR: Cannot resolve reference ClassLibrary1,version="1.0.0.0". ERROR: Activation Context generation failed. End Activation Context Generation. 
  1. 将ClassLibrary1.dll文件复制到与ConsoleApplication1.exe相同的目录不会更改任何内容,因为我们没有为可以识别COM依赖项的文件提供任何清单。 所以下一步是为ClassLibrary1创建一个清单。 ClassLibrary1.manifest的一个版本已经出现在问题中。 让我们通过创建一个包含该内容的文本文件并将其作为ClassLibrary1.manifest保存在ClassLibrary1项目目录中来尝试。 要将它包含在项目中,让我们尝试使用相同的简单“包含在项目中”命令(再次,打开隐藏文件的可见性以使其成为可能)。 现在,当使用ConsoleApplication1.exe将新的ClassLibrary1.dll复制到目录并运行时会发生什么?
  2. 发生相同的错误和sxstrace结果是因为托管DLL中的清单文件未作为Win32资源嵌入,因为您可以通过使用Visual Studio打开DLL文件来validation,该文件显示文件的Win32资源。 它显示版本资源,没有别的。 因此,让我们从ClassLibrary1中排除清单,只需将清单文件复制到ConsoleApplication1.exe的位置作为外部文件。
  3. 成功! 程序正常运行并完成。 但是,如果我们想要使用使用不同版本的.NET框架构建的组件,该怎么办? 或者也许您的测试在这一点上不起作用,因为您的Visual Studio默认为不同的版本? 现在我看到我的ClassLibrary1项目默认为.NET 3.5。 如果我将其更改为4.0,重建,复制并再次运行会发生什么?
  4. 发生错误8013101b。 这对应于(根据谷歌搜索)COR_E_NEWER_RUNTIME,这也意味着“未找到清单中指定的模块”。 例如,当加载.NET 2.0的EXE试图引用使用.NET 4.0构建的DLL时,会发生这种情况。 所以现在我们必须告诉非托管客户端EXE在解析其COM引用时加载哪个版本的.NET框架。 这是通过名为ConsoleApplication1.exe.config的配置文件完成的。 只需创建一个新的文本文件,并使用该名称将其保存在ConsoleApplication1.exe目录中。 它具有以下内容:

      

如果在这种情况下排除useLegacyV2RuntimeActivationPolicy,则仍会出现相同的错误。 不幸的是,我不完全理解为什么,但我怀疑它与较新的v4.0运行时激活策略有关,如果加载的可执行文件没有明确引用.NET 4.0,那么默认加载CLR v2.0托管代码不会,因为它没有显式引用.NET期间)。

  1. 再次成功! 但等等,还有更多。 如果您的COM dll是使用密钥签名(具有强名称),该怎么办? 让我们为ClassLibrary1添加一个键,将其配置为在项目的“Signing”选项卡上对DLL进行签名,并查看将更新的DLL复制到ConsoleApplication1.exe目录时会发生什么。
  2. 现在我们得到错误80131040(“找到的程序集的清单定义与程序集引用不匹配”)。 并且sxstrace和fuslogvw在这里产生任何有关正在发生的事情的信息都令人沮丧。 幸运的是,我现在知道,在这个特定的reg-free-com场景中,它是由于缺少描述ClassLibrary1的assemblyIdentity元素的publicKeyToken属性(在两个清单文件中)。 但是如何获得publicKeyToken值呢? 从开发人员命令提示符运行sn -T ClassLibrary1.dll 。 更新ClassLibrary1.manifest和ConsoleApplication1.exe.manifest后,请记住在嵌入清单时重建ConsoleApplication1.exe,并将ClassLibrary1.manifest复制到ConsoleApplication1.exe目录。 再跑一次?
  3. 我在sxstrace的帮助下经历了一些错误解决的旋转,但这是由于愚蠢的错误。 为了造成愚蠢错误的其他人的利益,如果你遇到sxstrace错误,还有一些事情要注意:a)确保你使用属性publicKeyToken而不是像privateKeyToken这样的其他荒谬名字。 b)确保您在服务器端清单上的assemblyIdentity中指定的所有属性都与客户端清单上的属性相匹配,并且您没有在一个上指定type="win32"而在另一个上没有指定。
  4. 成功! 输出为B Inserted obby

我还应该注意VB6客户端也可以使用以下文件和VB6客户端:

Project1.exe.config:

      

Project1.exe.manifest:

          

但是,在创建配置和清单文件之前构建并运行可执行文件的极少数情况下,似乎存在报告“ActiveX组件无法创建对象”(运行时错误429)的趋势。 重建EXE文件似乎可以修复它,但是后来我无法回答问题,所以很难确定具体的原因。

我认为我是一个相当不错的问题解决者,但是在无reg的com配置问题中报告的众多移动部件和众多无用的错误代码和错误消息使得这几乎不可能在没有一些扎实经验,内部知识或Microsoft源代码的情况下弄清楚码。 希望这个答案能帮助其他人获得类似的体验。 如果您了解更多,请扩展此答案!

附录1

如果在项目中使用“添加” – >“新项…”命令添加“应用程序清单文件”, 可以正确且轻松地嵌入托管COM服务器的清单。 这会将名为app.manifest的文件添加到项目中。 但真正棘手的部分是,它以一种无法通过Visual Studio UI以任何其他方式复制的方式实现,除非通过一个棘手的解决方法。 由于项目设置窗口的“应用程序”选项卡上的“清单”字段对“类库”类型项目禁用,因此无法为类库设置清单(通常在此处设置)。 但您可以暂时将项目更改为Windows应用程序,在此处更改清单选择,然后将其还原到类库。 设置将坚持,以便所选清单正确嵌入。 您可以通过查看项目文件来validation文本编辑器中的设置。 寻找:

  app.manifest  

附录2

如果采取上述所有预防措施,仍可能出现错误0x80131040。 为了帮助缩小原因,有助于使用融合日志查看器查看有关正在加载和解决程序集时发生的情况的更多信息。 有关如何查看此日志的详细信息,请参阅Google“Fuslogvw”(fuslogvw.exe是安装Visual Studio时提供的实用程序)。 同样重要的是要意识到,默认情况下,此应用程序显然不显示任何信息,直到您将其配置为将信息记录到文件,重现问题,然后重新启动应用程序以在生成后读取日志文件。 并且,根据MSDN文档,记住以管理员身份运行此实用程序也很重要。

一旦你通过了所有障碍来运行fuslogvw.exe,你可能会在日志中看到类似的东西:

 WRN: Comparing the assembly name resulted in the mismatch: Major Version ERR: The assembly reference did not match the assembly definition found. ERR: Failed to complete setup of assembly (hr = 0x80131040). Probing terminated. 

尽管COM服务器的清单文件将版本列为1.0.0.0,但这不是从COM客户端引用绑定到服务器时使用的(唯一)版本。 我的客户端EXE文件试图引用1.0.0.0,它与COM服务器的清单文件中的版本完全匹配,但它与DLL的.NET版本不匹配。 在更正客户端和服务器清单文件以反映.NET服务器DLL中的实际版本之后,错误0x80131040消失了,并且fuslogvw.exe是识别问题根源的关键。

如果客户端清单与实际的.NET DLL版本同步,但服务器DLL的清单文件未反映此版本,则会发生另一个错误:

应用程序无法启动,因为它的并排配置不正确。 有关更多详细信息,请参阅应用程序事件日志或使用命令行sxstrace.exe工具。

附录3

可能会报告错误0xc0150002或以下消息:

应用程序无法正确启动(0xc0150002),单击“确定”关闭应用程序。

我已经看到这种情况发生在客户端清单嵌入非托管DLL而不是非托管EXE的情况下, 并且清单的assemblyIdentity元素与服务器的assemblyIdentity不完全匹配。 客户端有一个额外的processorArchitecture="x86" ,服务器没有指定,导致不匹配。 不幸的是,我不知道如何学习这个,没有幸运地想检查清单文件,看看它们匹配(或阅读本文)。 该错误并未明确指出清单文件是问题的根源,因此您只需要知道该错误消息与此原因之间可能存在关联。

附录4

我已经看到外部清单文件被完全忽略,产生一个完全空的sxstrace日志,即使涉及的可执行文件没有嵌入的清单。 这显然是由于激活上下文缓存而发生的( http://csi-windows.com/blog/all/27-csi-news-general/245-find-out-why-your-external中记录了一个问题- 显然被忽略了 )。 若要解决此问题,您可以使用以下命令来触摸正在忽略清单的文件的日期戳:

 copy /b myfile.exe+,, 

附录5

我看到在下列条件下调用CoCreateInstance时出现的另一个难以解释的Class Not Registered错误( 0x80040154REGDB_E_CLASSNOTREG ):

  1. CPP文件包含在全局范围内实例化的类的构造函数,因此如果/clr开关应用于CPP文件,则动态初始化将在DllMain期间DllMain构造函数;如果/clr开关应用于.cctor文件,则在.cctor期间DllMain构造函数文件。
  2. DLL具有嵌入式清单,使其能够引用通过Reg-Free COM创建的COM类。
  3. COM DLL在.NET 2.0中以托管代码(带有COM可调用包装器,即CCW)实现。
  4. 加载DLL的EXE 没有引用创建的COM类的Reg-Free Manifest。
  5. COM DLL regasm注册。
  6. 调用CoCreateInstance的CPP文件将/clr开关应用于C ++编译器设置。

如果最后3个条件中的任何一个被改变,问题就会消失。 (此外,如果最后一个条件被更改,您可能会因#1而获得加载程序锁定 – 读取加载程序锁定及其与混合程序集初始化时 CLR的关系)。 因此,如果您在类似情况下遇到Class Not Registered错误,请考虑是否可以更改最后3个条件中的任何一个来解决错误。 注意:我很难确定#6的行为。 转换它的效果似乎也取决于#1的状态。 看起来在DLL完全加载后调用构造函数(包括其CoCreateInstance )仍导致类未注册,而如果未指定/clr开关,则在DLL初始化期间调用构造函数将成功。 我目前的解决方案是在托管C ++中重新编写客户端CPP文件,因为它是COM组件和其他未托管代码之间相对简单的接口类。 所以现在这个客户端没有更多的COM,只是一个.NET引用。

令我沮丧的是,BlueMonkMN的解释非常有用,但我遇到了Class not registered消息。

我有一个.NET 4.6 COM互操作Dll和一个C ++客户端,它通过Windows SxS技术使用免注册COM。 程序集清单嵌入在COM Dll中,应用程序清单放在外面,作为单独的.manifest文件。

一切都按照宣传的方式工作,但是当发生以下任何一种情况时,它在CreateInstance期间开始获取Class not registered消息。

  • 将互操作COM Dll与C ++客户端可执行文件和清单文件一起复制到其他目录。
  • 对C ++客户端进行非COM相关更改,然后重新构建应用程序。

在这种情况下,为了添加侮辱伤害, sxstrace日志为空,应用程序事件日志( eventvwr )未报告任何SideBySide错误,并且程序集绑定日志查看器( FUSLOGVW.exe )未显示C ++客户端应用程序中的任何内容。

让我为BlueMonkMN的回答添加另一个附录。

附录6

我从目前为止的实验中理解的是,在这种情况下,C ++客户端应用程序的激活上下文没有被创建。

在这种情况下,需要自己创建激活上下文。

在调用CoInitialize(0);之后,将以下代码放入C ++客户端应用程序中CoInitialize(0); 在调用CreateInstance

 ACTCTX context; memset(&context, 0, sizeof(context)); context.cbSize = sizeof(context); context.lpSource = L"CPPClient.exe.manifest"; context.lpAssemblyDirectory = L"D:\\code\\vs2017\\CPPClient\\Debug"; context.dwFlags = ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID; HANDLE hActCtx = CreateActCtx(&context); ULONG_PTR cookie = 0; BOOL result = ActivateActCtx(hActCtx, &cookie); //Rest of the code goes here. 

作为一种清理措施,在调用CoUninitialize();之前放入以下代码段CoUninitialize();

 DeactivateActCtx(0, cookie); ReleaseActCtx(hActCtx); 

现在听起来不错。 Side by Side工作得非常好。 如果出现任何问题,则可以在sxstrace或应用程序事件日志或程序集绑定日志查看器中看到它。