代码之家  ›  专栏  ›  技术社区  ›  user9212993

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

  •  20
  • user9212993  · 技术社区  · 6 年前

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

    我已经研究了MSDN上提供的文档 "Interoperating with Unmanaged Code" ,关于 " COM Callable Wrapper " " How to: Configure .NET Framework-Based COM Components for Registration-Free Activation " 特别地。

    经过一个多星期的研究,在文档不足的循环中(重新)指导,我决定把我的第一个问题放在这里。

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

    ./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服务器的示例,因为这些东西已经很好地工作了,而且是毫无疑问的)。
    我主要遵循 "Registration-Free Activation of COM Components: A Walkthrough" . 主要区别在于,我使用的是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)

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"> 
        <assemblyIdentity name="MyCompany.Software.Application" processorArchitecture="x86" version="1.0.0.0" type="win32" />
        <description>A native COM client application.</description>
        <asmv3:trustInfo>
            <asmv3:security>
                <asmv3:requestedPrivileges>
                    <asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
                </asmv3:requestedPrivileges>
            </asmv3:security>
        </asmv3:trustInfo>
        <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> 
            <application>
                <!-- Windows 10 and Windows Server 2016 --> 
                <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
                <!-- Windows 8.1 and Windows Server 2012 R2 -->
                <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
                <!--  Windows 8 and Windows Server 2012 -->
                <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
                <!-- Windows 7 and Windows Server 2008 R2 -->
                <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
                <!-- Windows Vista and Windows Server 2008 -->
                <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
            </application>
        </compatibility>
        <dependency>
            <dependentAssembly>
                <assemblyIdentity type="win32" name="MyCompany.Libs.Set1" version="1.0.0.0" processorArchitecture="x86" />
            </dependentAssembly>
        </dependency>
    </assembly>
    

    TestDllConsoleApp.exe.config

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

    <configuration>  
       <runtime>  
          <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">  
             <probing privatePath="..\..\SharedLibs"/>  
          </assemblyBinding>  
       </runtime>  
    </configuration>  
    

    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。反恐精英

    // 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。反恐精英

     // 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中)

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
      manifestVersion="1.0">
        <assemblyIdentity
                    type="win32"
                    processorArchitecture="x86"
                    name="CSharpCOMDll"
                    version="1.0.0.0" />
        <clrClass
                    clsid="{4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}"
                    progid="CSharpCOMDll.COMImplClass1"
                    threadingModel="Both"
                    name="CSharpCOMDll.COMImplClass1" 
                    runtimeVersion="v4.0.30319">
        </clrClass>
        <clrClass
                    clsid="{067E5980-0C46-49C7-A8F0-E830877FB29C}"
                    progid="CSharpCOMDll.COMImplClass2"
                    threadingModel="Both"
                    name="CSharpCOMDll.COMImplClass2" 
                    runtimeVersion="v4.0.30319">
        </clrClass>
    </assembly>
    

    最后是从 TestDllConsoleApp。显示 dependency 条目:

    MyCompany.Libs.Set1.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="MyCompany.Libs.Set1" version="1.0.0.0" processorArchitecture="x86" />
        <file name="CSharpCOMDll.dll"> 
            <comClass
                clsid="{4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}"
                threadingModel="Both"
                />
            <comClass
                clsid="{067E5980-0C46-49C7-A8F0-E830877FB29C}"
                threadingModel="Both"
                />
            <comInterfaceProxyStub
                name="ICOMInterface1"
                iid="{6BDAF8DD-B0CF-4CBE-90F5-EA208D5A2BB0}"
                proxyStubClsid32="????"
            />
            <comInterfaceProxyStub
                name="ICOMInterface2"
                iid="{BE69E9C7-1B37-4CA8-A3C1-10BFA9230940}"
                proxyStubClsid32="????"
            />
        </file>
    </assembly>
    

    看起来我已经成功了一半,但仍然无法诊断出实际问题。

    目前有两种类型的故障( 请注意 ,即在可执行文件旁边部署托管COM服务器DLL,而不是引用已解析的清单目录,可以正常工作,并且符合预期):

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

      • 启动可执行文件会导致异常
        EOleSysError: Error in dll, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}

      • 调试异常会导致 HRESULT 价值

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

      • 我不确定该属性实际需要哪个GUID。
        正如文档中所提到的,它自然似乎是一个对应的“co类ID”( CLSID )如中所述 comClass 元素 clsid 属性
      • 或者,我尝试从生成的 ,pas 在那里归档。

      这两种变体都给我留下了一个非常无用的错误,可以通过 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++可再发行框架 在这里没有帮助。我是从德尔福打来的,那是不一样的。

    3. 再次尝试引用 CSharpCOMDll。动态链接库 显式地(可执行清单中的另一个依赖项),并将其放入 SharedLibs 已成功创建 激活上下文 ,但失败时出现与以前略有不同的异常

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

    这里有没有人知道如何直接做我想做的事情,或者可以另外做什么(除了 sxstrace公司 )更深入地诊断问题。

    我几乎可以肯定,必须能够提供 部署 这样地。


    TL;博士;

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

    更新:

    今天进一步研究,我意识到(尽管术语非常相似),解决 激活上下文 使用专用SxS并解析的位置。用于COM可调用包装器实例化的NET DLL是两种完全不同且独立的机制。我主要是从这两个和更多的 Jufeng Zhang's 精彩而深入的解释博客文章:

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

    使用任何方法,如指定 <codebase> <probing> 配置中的元素 <runtime> 指向目录外部的节 .config 文件已部署,根本无法工作。

    我使用Sysinternals Process Monitor和Fusion log viewer工具验证了 2. .

    我不会把它作为最后的答案,因为我会在下一步尝试如何欺骗它。NET机制来定位托管COM服务器DLL,使用程序集清单或本机DLL指定依赖项和 <探测(>); / <代码库(&G); 元素重定向定位机制。

    作为最后手段(原文如此!)似乎甚至可以提供您自己的定制 appDomainManagerAssembly appDomainManagerType 在应用程序配置中的 <运行时(>); 要素


    更新II:

    恐怕我们得去管理 AppDomain 我们从本机CLR主机使用CLR API。

    需要进一步调查。我在这里找到了一个很有希望的资源:

    "Customizing the Microsoft .NET Framework Common Language Runtime"


    1) 请原谅德国错误信息。我手头没有英文版的编译器。但谷歌提供的翻译应该能很好地工作。

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

    1 回复  |  直到 4 年前
        1
  •  6
  •   user9212993 user9212993    6 年前
    • 是否有可能提供如上所述的部署结构,并维护特定的。NET COM服务器DLL是否位于引用的可执行文件位置之外?

    它是 绝对不可能(!) 在外部解析为内部CLR宿主机制提供的所有程序集 AppDomain 的可执行目录。

    您可以使用

    <probing privatePath="<some directory below your executable's location>" />`
    

    但是 <probing> 标记对SxS解析的作用不同(显示在清单下 <windows> 标记)和CLR的实例化机制 COM可调用包装器 出现在 <runtime> 标签


    它甚至没有文档记录,但指定

    <windows>
        <probing privatePath="../<xxx>" />
    </windows>
    

    对于解决SxS依赖关系,支持的相对路径 <xxx> 最多3个 ../ 可执行文件位置的父目录级别适用于任何 本机COM服务器 虽然

    <runtime>
        <probing privatePath="../<xxx>" />
        <!--                  ^^^ -->
    </runtime>
    

    <runtime>
        <codebase href="../<xxx>/xyz.dll" version="1.0.0.0"/>
        <!--            ^^^ -->
    </runtime>
    

    不允许您指定指向外部向上位置的装配位置 应用程序域 使用标准windows的托管目录。NET机制来解析要实例化为 COM可调用包装器 (由 mscoreee.dll ).
    从可执行文件的部署目录中进行更深入的下行操作,效果良好,符合预期。


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

     <configuration>
         <runtime>
              <appDomainManagerAssembly value="MyAppDomainMgr" />
              <appDomainManagerType value="MyAppDomainMgr.MyCustomAppDomainMgr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
         </runtime>
     <configuration>
    

    实施 MyAppDomainMgr.MyCustomAppDomainMgr 类应位于a中。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可调用包装器)(即调用 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 ),必须放置在非托管可执行应用程序的下方。