如何提供正确定位.NET Dll作为COM提供程序的私有并排清单?

我正在研究一个免费的私有注册WinSxS的配置,它提供了简单的程序集清单文件,在部署和运行时将Delphi可执行文件(COM客户端)和.NET(C#)COM可见DLL组合在一起。

我已经研究了MSDN “与非托管代码互操作”中提供的文档,特别是关于“ COM Callable Wrapper ”和“ 如何:为无注册激活配置基于.NET Framework的COM组件 ”的部分。

经过一个多星期的研究,并且(重新)指导文档不足的周期,我决定在这里提出我的第一个问题。

计划的部署结构如下所示:

./install-root ├───ProgramSuite1 │ ├───bin │ │ DelphiNativeCOMClient1.exe │ │ DelphiNativeCOMClient1.exe.config │ │ DelphiNativeCOMClient2.exe │ │ DelphiNativeCOMClient2.exe.config │ | ... │ │ │ └───data │ ... ├───ProgramSuite2 │ ├───bin │ │ DelphiNativeCOMClient3.exe │ │ DelphiNativeCOMClient3.exe.config │ │ DelphiNativeCOMClient4.exe │ │ DelphiNativeCOMClient4.exe.config │ | ... │ │ │ └───data │ ... └───SharedLibs ├───MyCompany.Libs.Set1 │ MyCompany.Libs.Set1.manifest │ SomeManagedCOMServerA.dll │ SomeNativeCOMServerB.dll │ SomeNativeCOMServerC.dll │ └───MyCompany.Libs.Set2 MyCompany.Libs.Set2.manifest SomeManagedCOMServerB.dll SomeNativeCOMServerX.dll SomeManagedCOMServerA.dll 

下面是关于Delphi本机可执行文件和C#.NET COM服务器DLL实现的实现的简短草图(我遗漏了原生COM服务器的示例,因为这些东西已经运行良好且不可能)。
我主要遵循“注册 – 免费激活COM组件:演练”中提供的内容 。 主要区别在于我使用Delphi而不是C,C ++或旧VB作为机客户端。

TestDllConsoleApp.exe

TestDllConsoleApp.dpr

 program TestDllConsoleApp; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, DllTests.Common, WinApi.ActiveX, WinApi.Windows, // These were generated using the tlbimplib tool CSharpCOMDll_TLB in 'CSharpCOMDll_TLB.pas', mscorlib_TLB in 'mscorlib_TLB.pas'; var comInterface1 : ICOMInterface1; comInterface2 : ICOMInterface2; intf1CoClass : _COMImplClass1; intf2CoClass : _COMImplClass2; res : HRESULT; coInitializeRes : integer; begin //Initialize COM coInitializeRes := CoInitializeEx(nil, COINIT_APARTMENTTHREADED); if (coInitializeRes  S_OK) and (coInitializeRes  S_FALSE) then begin System.ExitCode := 1; Exit(); // GUARD end; try try intf1CoClass := CoCOMImplClass1.Create(); res := intf1CoClass.QueryInterface(IID_ICOMInterface1,comInterface1); System.WriteLn(comInterface1.GetModuleName()); intf2CoClass := CoCOMImplClass2.Create(); res := intf2CoClass.QueryInterface(IID_ICOMInterface2,comInterface2); System.WriteLn(comInterface2.GetModuleName()); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; finally //Uninitialize COM CoUninitialize(); end; end. 

TestDllConsoleApp.manifest

(嵌入资源ID 1)

    A native COM client application.                            

TestDllConsoleApp.exe.config

(部署在与可执行文件相同的文件位置)

        

CSharpCOMDll.dll

(将部署在SharedLibs\MyCompany.Libs.Set1目录中)

Assemblyinfo.cs

 #region Using directives using System; using System.Reflection; using System.Runtime.InteropServices; #endregion [assembly: AssemblyTitle ("CSharpCOMDll")] [assembly: AssemblyProduct ("CSharpCOMDll")] [assembly: AssemblyCopyright ("Copyright 2018")] [assembly: ComVisible (true)] [assembly: AssemblyVersion ("1.0.0.0")] [assembly: Guid ("045d53ab-a9e4-4036-a21b-4fe0cf433065")] 

COMImplClass1.cs

 // Using namespaces ... namespace CSharpCOMDll { [Guid("6BDAF8DD-B0CF-4CBE-90F5-EA208D5A2BB0")] public interface ICOMInterface1 { string GetModuleName(); } [Guid("4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805")] public class COMImplClass1 : ICOMInterface1 { public string GetModuleName() { return typeof(COMImplClass1).Module.FullyQualifiedName; } } } 

COMImplClass2.cs

  // Using namespaces ... namespace CSharpCOMDll { [Guid("BE69E9C7-1B37-4CA8-A3C1-10BFA9230940")] public interface ICOMInterface2 { string GetModuleName(); } [Guid("067E5980-0C46-49C7-A8F0-E830877FB29C")] public class COMImplClass2 : ICOMInterface2 { public string GetModuleName() { return typeof(COMImplClass1).Module.FullyQualifiedName; } } } 

CSharpCOMDll.manifest

(嵌入到资源ID为2的DLL中)

         

最后,程序清单从TestDllConsoleApp.manifest dependency项中解析:

MyCompany.Libs.Set1.manifest

           

看来我已经到了一半,但仍无法诊断实际问题。

现在有两种失败的变体( 请注意 ,在可执行文件旁边部署托管COM服务器DLL而不是引用已解析的清单目录,只是按照预期正常工作):

  1. 我完全删除了全局清单中的proxyStubClsid32属性:

    • 启动可执行文件最终会出现exception
      EOleSysError: Error in dll, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}
    • 调试exception会导致HRESULT

        Error in the DLL (Exception from HRESULT: 0x800401F9 (CO_E_ERRORINDLL)) 
  2. 我在全局清单中提供了proxyStubClsid32属性:

    • 我不确定该属性实际上需要GUID。
      正如在文档中提到的那样,它自然似乎是comClass元素clsid属性中提到的相应的“co class ID”( CLSID )。
    • 我或者尝试从那里生成的,pas文件中提供LIBID GUID。

    两种变体sxstrace我留下了一个非常无用的错误,可以通过sxstrace工具1跟踪:

      ... INFORMATION: Manifestdatei ".\install-root\SharedLibs\MyCompany.Libs.Set1\MyCompany.Libs.Set1.MANIFEST" wird analysiert. INFORMATION: Die Manifestsdefinitionsidentität ist ",processorArchitecture="x86",type="win32",version="1.0.0.0"". FEHLER: Bei der Generierung des Aktivierungskontextes ist ein Fehler aufgetreten. Beendet die Generierung des Aktivierungskontextes. 

    请注意,没有任何简洁的错误/信息消息

      ... cannot resolve assembly XY ... 

    激活上下文生成之前搞砸了。 有很多参考资料表明这种特殊的错误。
    此外,无处不在的提到缺少Visual C ++可再发行框架并没有帮助。 我是从Delphi打电话的,那是不同的。

  3. 另一种尝试明确地引用CSharpCOMDll.dll (可执行文件清单中的另一个依赖项),并将它放入SharedLibs得到了一个成功创建的激活上下文 ,但失败了一个稍微不同的exception

     EOleSysError: Cannot find file, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805} 

这里有没有人知道如何做我想要的直截了当,或者可以做些什么(除了sxstrace )以更深入地诊断问题。

我几乎可以肯定必须提供这样的部署


TL; DR;

  • 是否有可能提供如上所述的部署结构,并在引用的可执行文件位置之外维护某些.NET COM服务器DLL?

更新:

今天进一步研究,我意识到(尽管术语非常相似),使用私有SxS解析ActivationContext并解析用于COM可调用包装器实例化的.NET DLL的位置是两个完全不同且分离的机制。 我大部分都是从这两篇文章中得到的,还有一些张巨峰的精彩和深入解释的博客文章:

  • “COM激活,免费注册COM激活,COM / .Net Interop,免费注册COM / .Net Interop”
  • “免费注册COM / .Net互操作”

定位未注册的.NET程序集(托管COM服务器DLL)的问题是,这只会发生在应用程序部署目录及其下方。

使用任何方法(如在配置.config文件所在目录之外的配置部分中指定元素),根本不起作用。

我validation了使用Sysinternals Process Monitor和Fusion日志查看器工具2

我不会将其作为最终答案发布,因为我将尝试下一步以某种方式欺骗.NET机制来定位托管COM服务器DLL,使用程序集清单或指定依赖项和 / 本机DLL 元素重定向定位机制。

作为最后的手段(原文!),似乎甚至可以在元素下的应用程序配置中提供您自己的自定义appDomainManagerAssemblyappDomainManagerType


更新II:

我担心我们必须自己使用来自本机CLR主机的CLR API来管理AppDomain

需要进一步调查。 一个有前途的资源我怎么做我在这里找到:

“自定义Microsoft .NET Framework公共语言运行时”



1) 请原谅德语错误消息。 我手头没有英文版编译器。 但谷歌提供的翻译应该运作良好。

2) 因此,关于更好的诊断问题的工具的问题可以被认为已经解决了。

  • 是否有可能提供如上所述的部署结构,并在引用的可执行文件位置之外维护某些.NET COM服务器DLL?

绝对不可能(!)解析为AppDomain可执行文件目录之外的内部CLR托管机制提供的任何程序集。

你可以使用

 ` 

但是标签对于SxS解析(出现在清单标签下)以及CLR用于实例化出现在标签下的COM Callable Wrappers的机制有所不同。


它甚至没有记录,但具体说明

    

解析SxS依赖关系支持最终3 ../父目录级别的相对路径从您的可执行文件位置适用于任何本机COM服务器 ,而

     

要么

     

将不允许您使用标准的Windows .NET机制来指定在AppDomain的托管目录之外指向位置的程序集位置,以解析候选者被实例化为COM Callable Wrappers (由mscoreee.dll托管)。
从可执行文件的部署目录中更深入地下载可以正常工作。


拦截CLR探测机制的一种方法(可能是最简单的)是提供自定义AppDomainManager实现并在应用程序配置文件的元素中指定它:

        

MyAppDomainMgr.MyCustomAppDomainMgr类的实现应该在.NET程序集中,例如用C#编写:

 namespace MyAppDomainMgr { [ComVisible(true)] public class MyCustomAppDomainMgr : AppDomainManager { public MyCustomAppDomainMgr() { } public override void InitializeNewDomain(AppDomainSetup appDomainInfo) { Console.Write("Initialize new domain called: "); Console.WriteLine(AppDomain.CurrentDomain.FriendlyName); InitializationFlags = AppDomainManagerInitializationOptions.RegisterWithHost; // Several ways to control settings of the AppDomainSetup class, // or add a delegate for the AppDomain.CurrentDomain.AssemblyResolve // event. } } } 

一旦您的非托管应用程序尝试通过CLR访问某些COM接口(COM Callable Wrapper)(即调用CoCreateInstance() ),将实例化MyCustomAppDomainMgr类并首先调用InitializeNewDomain()函数。

最不具侵入性的方式似乎是添加该委托函数:

 public override void InitializeNewDomain(AppDomainSetup appDomainInfo) { // ... AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(MyCustomAssemblyResolver); } static Assembly MyCustomAssemblyResolver(object sender, ResolveEventArgs args) { // Resolve how to find the requested Assembly using args.Name // Assembly.LoadFrom() would be a good way, as soon you found // some matching Assembly manifest or DLL whereever you like to look up for it } 

生成的程序集( MyAppDomainMgr.dll )必须放在非托管可执行应用程序下面。