受管注册的COM服务器不会激活 [英] Managed Reg-Free COM Server Won't Activate

查看:364
本文介绍了受管注册的COM服务器不会激活的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我开始使用一个非常复杂的客户端和服务器系统的COM引用和其他事情,我已经削减了下来,直到我意识到,我甚至不能得到Microsoft示例代码工作注册免费COM激活



服务器代码:

 使用系统; 
using System.Collections.Generic;
using System.Linq;
using System.Text;
使用System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using System.ComponentModel;

命名空间ClassLibrary1
{
[Guid(A7AC6D8C-FF17-4D2C-A3B1-2C8690A8EA04)
,ComVisible(true)]
公共接口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代码: p>

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

客户端C ++代码:



  #includestdafx.h

#import< ClassLibrary1。 tlb> raw_interfaces_only

使用命名空间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\\\
),hr);
CoUninitialize();
return 1;
}
_bstr_t b = _T(Bobby);
BSTR b2;
p-> DummyFunction(b,& b2);
wprintf_s(L%s\\\
,b2);
p-> Release();
CoUninitialize();
return 0;
}

当我删除所有Reg-Free COM代码时,注册ClassLibrary1.dll与regasm / codebase。



然后我注销ClassLibrary1,并尝试引入Reg-Free COM的VB6客户端与文件Project1.exe.manifest :

 <?xml version =1.0encoding =UTF-8standalone =yes?> 
< assembly xmlns =urn:schemas-microsoft-com:asm.v1manifestVersion =1.0>
< assemblyIdentity type =win32name =Project1version =1.0.0.0/>
< dependency>
< dependentAssembly>
< assemblyIdentity name =ClassLibrary1version =1.0.0.0/>
< / dependentAssembly>
< / dependency>
< / assembly>

和ClassLibrary1.manifest:

 <?xml version =1.0encoding =utf-8?> 
< assembly manifestVersion =1.0xmlns =urn:schemas-microsoft-com:asm.v1>
< assemblyIdentity version =1.0.0.0name =ClassLibrary1/>
< clrClass clsid ={81723475-B5E3-4FA0-A3FE-6DE66CEE211C}name =ClassLibrary1.Class1tlbid ={F8A2D334-5BBB-4007-8308-A1417052E6D6}>< / clrClass> ;
< file name =ClassLibrary1.dll>< / file>
< / assembly>

现在我得到错误429(ActiveX组件不能创建对象)有时,自动化错误其他时间:


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


然后我尝试将COM Isolation引入C ++客户端:



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


错误800401f9



解决方案

许多试验通过各种示例与Microsoft支持工作,我已经确定了在尝试实现托管COM服务器与非托管C + + COM客户端时出现的许多陷阱。以下是我记得的关键信息,可以应用于问题中的示例代码,以确保其工作。


  1. 客户端不应该使用C ++项目中的Isolated COM设置。我的内存正在消失,但Microsoft支持告诉我这是别的东西 - 我认为开发一个孤立的COM接口到非托管COM服务器而不是托管COM服务器。虽然这在

  2. 如果COM服务器使用密钥签名(强命名),则客户端中的assemblyIdentity元素

  3. 在托管代码中将RT_MANIFEST资源嵌入为Win32资源不是必需的,因为它必须包含一个publicKeyToken,否则就会出现一个publicKeyToken,否则会出现0x80131040的错误。容易与Visual Studio 2013,因为C#和VB.NET项目往往希望嵌入资源作为托管.NET资源,而不是Win32资源(你可以通过打开DLL输出文件与资源查看器,并注意到.NET可执行文件通常得到一个版本资源,没有多少,即使项目有一个清单文件包括)。解决此问题的一种方法是创建如下的RC文件:

-


$ b b

  #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


简单地保持服务器COM DLL清单文件单独,不嵌入它作为资源。我已经阅读,这可能不可靠,但它可以在Windows 7企业64位SP1。


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

-

 < configuration> 
< startup useLegacyV2RuntimeActivationPolicy =true>
< supportedRuntime version =v4.0/>
< / startup>
< / configuration>

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

 < configuration> 
< startup useLegacyV2RuntimeActivationPolicy =false>
< supportedRuntime version =v3.5/>
< / startup>
< / configuration>




  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),请确保


我尚未验证所有上述内容,但打算走过这个整个过程很快,以验证我捕获的一切。这是我最后还没有提到的:



ConsoleApplication1.exe.manifest (在源目录中,

 <?xml version =1.0encoding =UTF-8standalone = yes?> 
< assembly xmlns =urn:schemas-microsoft-com:asm.v1manifestVersion =1.0>

< assemblyIdentity
type =win32
name =ConsoleApplication1
version =1.0.0.0/&
< dependency>
< dependentAssembly>
< assemblyIdentity
type =win32
name =ClassLibrary1
version =1.0.0.0
publicKeyToken =541b4aff0f04b60a/>
< / dependentAssembly>
< / dependency>
< / assembly>

ClassLibrary1.manifest

 <?xml version =1.0encoding =utf-8?> 
< assembly manifestVersion =1.0xmlns =urn:schemas-microsoft-com:asm.v1>
< assemblyIdentity type =win32name =ClassLibrary1version =1.0.0.0publicKeyToken =541b4aff0f04b60a/>
< clrClass clsid ={81723475-B5E3-4FA0-A3FE-6DE66CEE211C}progid =ClassLibrary1.Class1threadingModel =bothname =ClassLibrary1.Class1runtimeVersion =v2.0.50727> < / clrClass>
< / assembly>

编辑



现在通过并验证每个细节,并提供完整的错误消息信息等。



我开始创建一个单一的解决方案包含两个项目与所有默认值和代码显示在问题中。我开始没有清单文件或问题中提到的任何项目设置,并将明确调用,当我在下面的过程中进行这些更改。


  1. 错误:Class1:未分配的标识符。需要运行开发人员命令提示符并执行以下命令行为了获得一个TLB文件,C ++代码可以导入: tlbexp ClassLibrary1.dll

  2. 将TLB文件移动到ConsoleApplication1项目目录并重新构建。相同错误。

  3. 替换 #import< ClassLibrary1.tlb> raw_interfaces_only 使用引号,所以它读取 #importClassLibrary1.tlbraw_interfaces_only 。重建:成功。

  4. 此时,如果我们运行,我们得到错误80040154

  5. 知道在客户端尝试设置隔离的COM会出现错误800401f9 我们'将跳过它,只是尝试创建一个客户端清单。创建一个具有以下内容的新文本文件,并将其作为ConsoleApplication1.exe.manifest保存在ConsoleApplication1项目目录中:

-

 <?xml version =1.0encoding =UTF-8standalone =yes?& 
< assembly xmlns =urn:schemas-microsoft-com:asm.v1manifestVersion =1.0>
< assemblyIdentity type =win32name =ConsoleApplication1version =1.0.0.0/>
< dependency>
< dependentAssembly>
< assemblyIdentity name =ClassLibrary1version =1.0.0.0/>
< / dependentAssembly>
< / dependency>
< / assembly>




  1. 在这个解决方案中有点过于复杂。您可以通过显示隐藏文件,并在清单文件中使用包含在项目中命令,简单地将清单文件包含在项目中。

  2. 此时运行将显示错误消息应用程序启动失败,因为它的side-by-side配置不正确。请参阅应用程序事件日志或使用命令行sxstrace.exe工具更多的细节。这部分是因为我们没有把ClassLibrary1.dll放在ConsoleApplication1.exe可以找到的地方。此时解析的sxstrace输出如下所示:

-

  INFO:解析清单文件C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ConsoleApplication1.exe。 
INFO:Manifest Definition Identity是ConsoleApplication1,type =win32,version =1.0.0.0。
INFO:Reference:ClassLibrary1,version =1.0.0.0
INFO:解析引用ClassLibrary1,version =1.0.0.0。
INFO:解析ProcessorArchitecture ClassLibrary1,version =1.0.0.0的引用。
INFO:解决文化中的参考。
信息:应用绑定策略。
信息:找不到绑定策略重定向。
INFO:开始汇编探测。
信息:在WinSxS中找不到程序集。
信息:尝试在C:\Users\bmarty\Documents\Visual Studio 2013 \Projects\RegFreeCOM\Debug\ClassLibrary1.DLL中探索清单。
信息:尝试在C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ ClassLibrary1.MANIFEST中探索清单。
信息:尝试在C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ClassLibrary1\ClassLibrary1.DLL中探索清单。
信息:尝试在C:\Users\bmarty\Documents\Visual Studio 2013\Projects\RegFreeCOM\Debug\ClassLibrary1\ClassLibrary1.MANIFEST中探索清单。
信息:没有找到文化中立的清单。
INFO:结束程序集探测。
错误:无法解析引用ClassLibrary1,version =1.0.0.0。
错误:激活上下文生成失败。
结束激活上下文生成。




  1. 将ClassLibrary1.dll文件复制到目录作为ConsoleApplication1.exe不会更改任何内容,因为我们没有提供任何清单的文件,可以识别COM依赖关系。所以下一步是为ClassLibrary1创建一个清单。问题中已经存在一个版本的ClassLibrary1.manifest。让我们通过创建一个具有该内容的文本文件并将它保存在ClassLibrary1项目目录中作为ClassLibrary1.manifest来尝试。要将其包含在项目中,让我们尝试使用相同的简单包括在项目中命令(再次,打开隐藏文件的可见性,使其成为可能)。现在,当使用ConsoleApplication1.exe将新的ClassLibrary1.dll复制到目录并运行时会发生什么?

  2. 出现同样的错误和sxstrace结果,因为托管DLL中的清单文件未嵌入作为Win32资源,你可以通过打开DLL文件与Visual Studio,它显示文件的Win32资源进行验证。它显示版本资源,没有其他。因此,让我们从ClassLibrary1中排除清单,并将清单文件复制到ConsoleApplication1.exe的位置作为外部文件。

  3. 成功!程序正常运行并完成。但是如果我们想使用一个使用不同版本的.NET框架构建的组件。或者你的测试在这一点上不工作,因为你的Visual Studio默认使用不同的版本?现在我看到我的ClassLibrary1项目默认为.NET 3.5。如果我将其更改为4.0,重建,复制并再次运行会发生什么情况?

  4. 出现8013101b错误。这对应于(根据google搜索)到COR_E_NEWER_RUNTIME,这也意味着未在清单中指定的模块。例如,当加载.NET 2.0的EXE试图引用使用.NET 4.0构建的DLL时,会发生这种情况。所以现在我们必须告诉非管理客户端EXE要在加载它的.NET框架的哪个版本解析其COM引用。这是通过名为ConsoleApplication1.exe.config的配置文件完成的。只需创建一个新的文本文件,并将其与该名称保存在ConsoleApplication1.exe目录中。它具有以下内容:

-

 < configuration> 
< startup useLegacyV2RuntimeActivationPolicy =true>
< supportedRuntime version =v4.0/>
< / startup>
< / configuration>

如果在这种情况下排除useLegacyV2RuntimeActivationPolicy,仍会发生同样的错误。不幸的是,我不完全明白为什么,但我怀疑它与新的v4.0运行时激活策略有关的默认情况下加载CLR v2.0如果可执行文件加载没有明确引用.NET 4.0(其中un-托管代码不是因为它没有显式引用.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插入obby

也可以与VB6客户端一起使用以下文件:



Project1.exe.config

 < configuration> 
< startup useLegacyV2RuntimeActivationPolicy =true>
< supportedRuntime version =v4.0/>
< / startup>
< / configuration>

Project1.exe.manifest

 <?xml version =1.0encoding =UTF-8standalone =yes?> 
< assembly xmlns =urn:schemas-microsoft-com:asm.v1manifestVersion =1.0>
< assemblyIdentity type =win32name =Project1version =1.0.0.0/>
< dependency>
< dependentAssembly>
< assemblyIdentity name =ClassLibrary1version =1.0.0.0publicKeyToken =541b4aff0f04b60a/>
< / dependentAssembly>
< / dependency>
< / assembly>但是,似乎有一个倾向,报告ActiveX组件无法创建对象($)。



我以为我是一个相当不错的问题解决方案,但关于许多移动部件和许多无用的错误代码和无记名的com配置问题报告的错误消息,这使得几乎不可能找出没有一些坚实的经验,内部知识或Microsoft源代码。希望这个答案将帮助其他人获得类似的经验。如果您了解更多信息,请扩展此答案。



附录1



托管COM服务器的清单 如果您在项目中使用添加 - >新建项目...命令添加应用程序清单文件,则可以正确轻松地嵌入。这会向项目添加一个名为app.manifest的文件。但真正棘手的部分是,它这样做的方式不能通过Visual Studio UI复制任何其他方式,除非通过一个螺旋式的解决方法。由于项目设置窗口的应用程序选项卡上的清单字段对类库类型项目禁用,因此无法为类库设置通常在此处设置的清单。但是您可以临时将项目更改为Windows应用程序,在此更改清单选择,然后将其恢复到类库。设置将粘贴,所选的清单将正确嵌入。您可以通过查看项目文件在文本编辑器中验证设置。查找:

 < PropertyGroup> 
< ApplicationManifest> app.manifest< / ApplicationManifest>
< / PropertyGroup>



附录2



错误0x80131040发生与上述所有预防措施。为了帮助缩小这种情况的原因,它有助于使用融合日志查看器来查看有关加载和解析程序集时发生的更多信息。 GoogleFuslogvw有关如何查看此日志的详细信息(fuslogvw.exe是安装Visual Studio时提供的实用程序)。还应该意识到,默认情况下,这个应用程序显然不会显示任何信息,直到您配置它将日志信息记录到文件,重现问题,然后重新启动应用程序以读取日志文件生成后。并且,根据MSDN文档,还要记住以管理员身份运行此实用程序也很重要。



一旦您通过所有障碍运行fuslogvw.exe,您可以在日志中看到类似下面的内容:

  WRN:比较程序集名称导致不匹配:主版本
ERR :程序集引用与找到的程序集定义不匹配。
ERR:无法完成装配设置(hr = 0x80131040)。探测终止。

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



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


应用程序启动失败,因为其side-by-side
配置不正确。请参阅应用程序事件日志或
使用命令行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 - 您的外部显示被忽略)。要解决此问题,您可以使用以下命令触摸其清单被忽略的文件的日期戳:

  / b myfile.exe + ,, 



附录5



我看到另一个难以解释的类未注册错误( 0x80040154 - REGDB_E_CLASSNOTREG 在以下条件下调用CoCreateInstance:


  1. CPP文件包含在全局范围内实例化的类的构造函数,因此动态初始化将调用如果 / clr 开关应用于CPP文件,则 DllMain / clr 切换 时, .cctor >
  2. DLL具有嵌入式清单,以便能够引用通过Reg-Free COM创建的COM类。

  3. COM DLL在管理

  4. 加载DLL的EXE 有一个Reg-Free Manifest引用了创建COM类。

  5. COM DLL 注册 regasm

  6. 调用 CoCreateInstance 的CPP文件已将 / clr 开关应用于C ++编译器设置。 li>

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


I started with a very sophisticated system of clients and servers with COM references and other things, and I've cut down and down until I realized I can't even get Microsoft sample code to work for registration free COM activation of a managed COM server written in C#.

Server code:

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);
      }
   }
}

Client VB6 Code:

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

Client C++ Code:

#include "stdafx.h"

#import <ClassLibrary1.tlb> 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;
}

Both of the clients work fine when I remove all Reg-Free COM code and register the ClassLibrary1.dll with regasm /codebase.

Then I unregister ClassLibrary1, and try to introduce Reg-Free COM for the VB6 client with the file Project1.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="Project1" version="1.0.0.0" />
   <dependency>
   <dependentAssembly>
   <assemblyIdentity name="ClassLibrary1" version="1.0.0.0" />
   </dependentAssembly>
   </dependency>
</assembly>

And ClassLibrary1.manifest:

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
   <assemblyIdentity version="1.0.0.0" name="ClassLibrary1" />
   <clrClass clsid="{81723475-B5E3-4FA0-A3FE-6DE66CEE211C}" name="ClassLibrary1.Class1" tlbid="{F8A2D334-5BBB-4007-8308-A1417052E6D6}"></clrClass>
   <file name="ClassLibrary1.dll" ></file>
</assembly>

Now I get Error 429 (ActiveX Component can't create object) sometimes, and (inexplicably) an automation error other times:

Run-time error '-2146234304 (80131040)': Automation Error

then I try to introduce COM Isolation into the C++ client:

Now when I run the C++ client, the output is merely

Error 800401f9

解决方案

After many trials working through various samples with Microsoft support, I have identified many pitfalls that arise in attempting to implement a managed COM server with an unmanaged C++ COM client. Here are the key pieces of information that I recall, which can be applied to the sample code in the question to make sure it works.

  1. The client should not use the Isolated COM settings in the C++ project. My memory is fading, but Microsoft support tells me this is for something else -- I think for developing an isolated COM interface to an un-managed COM server instead of a managed COM server. Although this is not at all clear from its description at https://msdn.microsoft.com/en-us/library/zzbcs3x5(v=vs.120).aspx.
  2. The client may select "Yes" or "No" for the "Embed Manifest" setting, but if "Yes" is selected, then the manifest that includes the dependent assembly information must be provided as an input manifest file. If the embedded manifest does not contain dependent assembly information, then any external manifest file would be ignored. Also, make sure that the configuration (Debug, for example) being edited matches the configuration being tested!
  3. If the COM server is signed with a key (is strongly named), then the assemblyIdentity element in both the client manifest and the server manifest must contain a publicKeyToken, otherwise HRESULT error 0x80131040 will occur during CoCreateInstance.
  4. Embedding the RT_MANIFEST resource as a Win32 resource in managed code is not easy with Visual Studio 2013 because C# and VB.NET projects tend to want to embed resources as managed .NET resources, not Win32 resources (you can verify this by opening the DLL output file with the resource viewer and notice that .NET executables generally get a version resource and not much else, even if the project has a manifest file included). One way to get around this is to create a RC file like this:

-

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

Then add a pre-build step like this:

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

Then in the project settings "Application" tab, change the "Resources" to use ClassLibrary1.res instead of "Icon and manifest". But this comes with problems: firstly, the path to RC.EXE is not easy to define without hard-coding it; secondly, the version information from AssemblyInfoCommon will be ignored because the resources in the RC file totally replace all Win32 resources that would be generated by the .NET compiler.

Another possibility is to simply keep the server COM DLL manifest file separate and not embed it as a resource. I have read that this may not be reliable, but it works on Windows 7 Enterprise 64-bit SP1.

  1. To ensure that the unmanaged client loads the proper .NET runtime, it needs a config file (ConsoleApplication1.exe.config) that defines how to load .NET. For .NET 4.5, I have seen this work:

-

<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0"/>
  </startup>
</configuration>

While for .NET 3.5, it seems useLegacyV2RuntimeActivationPolicy needs to be switched:

<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="false">
    <supportedRuntime version="v3.5"/>
  </startup>
</configuration>

  1. It's important to check that all the framework and CLR versions are in sync. And it's important to understand that the CLR versions are not the same as the framework versions. For example, if you want to build a managed COM server on .NET 3.5, the CLR version (runtimeVersion) should be "2.0.50727", but the .NET version (supportedRuntime) should be "v3.5".

  2. Make sure that the COM server's .NET Framework target version matches the client's supportedRuntime. If it is being built from the command line, it may not be picking up the framework version from the Visual Studio project file (for example, if you are running the C# of VB.NET compiler directly instead of calling MSBuild), make sure that the build is targeting the right version of the framework.

I have not validated all the above yet, but intend to walk through this whole process soon to verify that I caught everything. Here is what I ended up with that I haven't mentioned yet:

ConsoleApplication1.exe.manifest (in source directory, gets copied or embeded into output directory at build time)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

<assemblyIdentity
            type = "win32"
            name = "ConsoleApplication1"
            version = "1.0.0.0" />
<dependency>
            <dependentAssembly>
                        <assemblyIdentity
                                    type="win32"
                                    name="ClassLibrary1"
                                    version="1.0.0.0"
                                    publicKeyToken="541b4aff0f04b60a"/>
            </dependentAssembly>
</dependency>
</assembly>

ClassLibrary1.manifest

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
   <assemblyIdentity type="win32" name="ClassLibrary1" version="1.0.0.0" publicKeyToken="541b4aff0f04b60a" />
   <clrClass clsid="{81723475-B5E3-4FA0-A3FE-6DE66CEE211C}" progid="ClassLibrary1.Class1" threadingModel="both" name="ClassLibrary1.Class1" runtimeVersion="v2.0.50727"></clrClass>
</assembly>

EDIT:

Now to go through and validate every detail with full error message info etc.

I start by creating a single solution containing two projects with all default values and the code shown in the question. I begin with no manifest files nor any of the project settings mentioned in the question, and will explicitly call out when I am making these changes in the process below. These are the steps and errors that are on the path to making this project work.

  1. Error: "Class1: Undeclared identifier". Need to run a Developer Command Prompt and execute the following command line in order to get a TLB file that the C++ code can import: tlbexp ClassLibrary1.dll
  2. Move the TLB file into the ConsoleApplication1 project directory and re-build. Same Error.
  3. Replace the angle brackets on #import <ClassLibrary1.tlb> raw_interfaces_only with quotes, so it reads #import "ClassLibrary1.tlb" raw_interfaces_only. Rebuild: Success.
  4. At this point, if we run we get Error 80040154 (Class not registered) because we have not registered the component nor set up registration-free COM.
  5. Knowing that trying to set up Isolated COM in the client would present Error 800401f9 we'll skip that and just try to create a client manifest. Create a new text file with the following content, and save it as ConsoleApplication1.exe.manifest in the ConsoleApplication1 project directory:

-

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

  1. At this point it appears the steps mentioned earlier in this solution are a little over-complicated. You can simply include the manifest file in the project by showing hidden files, and using the "Include In Project" command on the manifest file.
  2. Running at this point would present the error message "The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command line sxstrace.exe tool for more detail." This is partly because we haven't put ClassLibrary1.dll anywhere where ConsoleApplication1.exe can find it. The parsed sxstrace output at this point looks like this:

-

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. Copying the ClassLibrary1.dll file into the same directory as ConsoleApplication1.exe doesn't change anything because we haven't provided any manifest for the file by which the COM dependency can be identified. So the next step is to create a manifest for ClassLibrary1. One version of ClassLibrary1.manifest is already present in the question. Let's try that one by creating a text file with that content and saving it in the ClassLibrary1 project directory as ClassLibrary1.manifest. To include it in the project, let's try the same simple "Include in Project" command (again, turning on the visibility of hidden files to make that possible). Now what happens when copying the new ClassLibrary1.dll to the directory with ConsoleApplication1.exe and running?
  2. The same error and sxstrace results occur because a manifest file in a managed DLL does not get embedded as a Win32 resource, as you can verify by opening the DLL file with Visual Studio, which shows the file's Win32 resources. It shows the version resource and nothing else. So let's exclude the manifest from the ClassLibrary1 and just copy the manifest file over to ConsoleApplication1.exe's location as an external file instead.
  3. Success! The program runs and completes normally. But what if we want to use a component built with a different version of .NET framework. Or maybe your test isn't working at this point because your Visual Studio defaulted to a different version? Right now I see that my ClassLibrary1 project defaulted to .NET 3.5. What happens if I change it to 4.0, rebuild, copy and run again?
  4. Error 8013101b occurs. That corresponds (according to a google search) to COR_E_NEWER_RUNTIME which also means "A module specified in the manifest was not found." This happens when, for example, an EXE that loaded .NET 2.0 is trying to reference a DLL built with .NET 4.0. So now we have to tell the un-managed client EXE which version of the .NET framework to load as it resolves its COM reference. This is done with a config file named ConsoleApplication1.exe.config. Just create a new text file and save it with that name in the ConsoleApplication1.exe directory. It has the following content:

-

<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0"/>
  </startup>
</configuration>

The same error would still occur if useLegacyV2RuntimeActivationPolicy were excluded in this case. Unfortunately, I don't fully understand why, but I suspect it has something to do with the newer v4.0 runtime activation policy defaulting to loading CLR v2.0 if the executable being loaded does not explicitly reference .NET 4.0 (which un-managed code does not because it doesn't explicitly reference .NET period).

  1. Success again! But wait, there's more. What if your COM dll is signed with a key (has a strong name)? Let's add a key to ClassLibrary1, configure it to be used in signing the DLL on the project's "Signing" tab, and see what happens when we copy the updated DLL to ConsoleApplication1.exe's directory.
  2. Now we get Error 80131040 ("The located assembly's manifest definition does not match the assembly reference"). And sxstrace, and fuslogvw are frustratingly unhelpful here in yielding any information about what is going on. Fortunately, I now know, in this particular reg-free-com scenario, it's caused by the lack of the publicKeyToken attribute on the assemblyIdentity elements describing ClassLibrary1 (in both manifest files). But how do you get the publicKeyToken value? Run sn -T ClassLibrary1.dll from a developer command prompt. After updating ClassLibrary1.manifest and ConsoleApplication1.exe.manifest, remember to rebuild ConsoleApplication1.exe if the manifest is embedded, and to copy ClassLibrary1.manifest to the ConsoleApplication1.exe directory. Run again and?
  3. I went through a few more gyrations of error solving with the help of sxstrace, but that was due to stupid errors. For the benefit of others who make stupid errors, here are some more things to be aware of if you're getting sxstrace errors: a) make sure you're using the attribute publicKeyToken and not some other ridiculous name like privateKeyToken; b) Make sure that all the attributes you specified in the assemblyIdentity on the server side manifest match those on the client side manifest, and that you don't have type="win32" specified on one but not the other.
  4. Success! The output is B Inserted obby

I should also note that the VB6 client also works by using the following files along with the VB6 client:

Project1.exe.config:

<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0"/>
  </startup>
</configuration>

Project1.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="Project1" version="1.0.0.0" />
   <dependency>
   <dependentAssembly>
   <assemblyIdentity name="ClassLibrary1" version="1.0.0.0" publicKeyToken="541b4aff0f04b60a" />
   </dependentAssembly>
   </dependency>
</assembly>

However, there does seem to be a tendency to report "ActiveX Component Can't Create Object" (Runtime error 429) in rare cases when the executable is built and run before creating the configuration and manifest files. Rebuilding the EXE file seems to fix it, but then I can't get the problem to come back, so it's hard to identify a specific cause.

I thought I was a reasonably good problem solver, but something about the numerous moving parts and numerous unhelpful error codes and error messages reported in reg-free com configuration problems makes this nearly impossible to figure out without some solid experience, inside knowledge or Microsoft source code. Hopefully this answer will help others acquire similar experience. Please extend this answer if you learn more!

Addendum 1

The managed COM server's manifest can be properly and easily embedded if you use the "Add"->"New Item..." command on the project to add an "Application Manifest File". This adds a file called app.manifest to the project. But the real tricky part is that it does so in a way that cannot be replicated any other way via the Visual Studio UI, except through one screwy work-around. Since the "Manifest" field on the "Application" tab of the project settings window is disabled for "Class Library" type projects, the manifest, which would normally be set here, cannot be set for a class library. But you can temporarily change the project to a Windows Application, change the Manifest selection here, then restore it to a Class Library. The setting will stick so the selected manifest gets properly embedded. You can verify the setting in a text editor by viewing the project file. Look for:

<PropertyGroup>
  <ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>

Addendum 2

Error 0x80131040 can still occur with all the above precautions taken. To help narrow down the cause of this, it helps to use the fusion log viewer to see more information about what is happening as assemblies are being loaded and resolved. Google "Fuslogvw" for more information about how to view this log (fuslogvw.exe is a utility provided when Visual Studio is installed). It's also important to realize that this application, by default, apparently does not show any information until you configure it to log information to files, reproduce the problem, then restart the application to read the log files after they are produced. And, according to MSDN documentation, it's also important to remember to run this utility as administrator.

Once you've passed all the hurdles to running fuslogvw.exe, you may see something like this in the log:

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.

Despite the fact that the COM server's manifest file listed the version as 1.0.0.0, that is not the (only) version used when binding from a COM client reference to the server. My client EXE file was trying to reference 1.0.0.0, which exactly matched the version in the COM server's manifest file, but it did not match the .NET version of the DLL. After correcting both the client and server manifest files to reflect the version actually in the .NET server DLL, then error 0x80131040 went away, and fuslogvw.exe was the key to identifying that as the source of the problem.

If the Client manifest is in sync with the actual .NET DLL version, but the server DLL's manifest file does not reflect this version, a different error will occur:

The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail.

Addendum 3

Error 0xc0150002 or the following message may be reported:

The application was unable to start correctly (0xc0150002), Click OK to close the application.

I have seen this occur in a case where the client manifest was embedded in an unmanaged DLL rather than an unmanaged EXE, and the manifest's assemblyIdentity element did not exactly match the server's assemblyIdentity. The client had an extra processorArchitecture="x86" in it that the server did not specify, causing a mismatch. Unfortunately I don't know how to learn this without luckily thinking to check the manifest files to see that they match (or reading this article). That error doesn't clearly point to a manifest file being the source of the problem, so you just have to be aware that there's a possible correlation between that error message and this cause.

Addendum 4

I have seen external manifest files get completely ignored yielding a completely empty sxstrace log, even when the executables involved have no embedded manifests. This can apparently happen as a result of the activation context cache (a problem documented at http://csi-windows.com/blog/all/27-csi-news-general/245-find-out-why-your-external-manifest-is-being-ignored). To work around this problem you can use the following command to touch the date stamp of the file whose manifest is being ignored:

copy /b myfile.exe+,,

Addendum 5

I have seen another hard-to-explain Class Not Registered error (0x80040154 - REGDB_E_CLASSNOTREG) that occurs when calling CoCreateInstance under the following conditions:

  1. An CPP file contains a constructor for a class that is instantiated in global scope so dynamic initialization will call the constructor during DllMain if the /clr switch is not applied to the CPP file, or during .cctor if the /clr switch is applied to the file.
  2. The DLL has an embedded manifest to make it be able to reference the COM class being created via Reg-Free COM.
  3. The COM DLL is implemented in managed code (with a COM-Callable Wrapper aka CCW) in .NET 2.0.
  4. The EXE that loaded the DLL does not have a Reg-Free Manifest referencing the created COM class.
  5. The COM DLL is not registered with regasm.
  6. The CPP file calling CoCreateInstance has the /clr switch applied to the C++ compiler settings.

If any of the last 3 conditions are altered, the problem goes away. (Additionally, if the last condition is altered, you may get a loader lock due to #1 -- read about loader lock and it's relation to CLR at Initialization of Mixed Assemblies). So if you are encountering a Class Not Registered error in similar circumstances, consider whether you can alter any of those last 3 conditions to resolve the error. Note: I'm having a hard time nailing down the behavior of #6. It seems the effect of switching this also depends the state of #1. It looks like calling the constructor (including its CoCreateInstance) after the DLL is fully loaded still causes Class Not Registered whereas calling the constructor during the DLL initialization will succeed if the /clr switch is not specified. My solution for the time being is to re-code the client CPP file in managed C++ since it was a relatively simple interface class between the COM component and the rest of the un-managed code. So now there's no more COM in this client, just a .NET reference.

这篇关于受管注册的COM服务器不会激活的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆