c# - 如何提供可以正确定位.NET Dll作为COM提供程序的私有(private)并排 list ?

标签 c# .net delphi com winsxs

我正在研究无偿注册的WinSxS的配置,其中简单地提供了汇编 list 文件,以便在部署和运行时将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 native 可执行文件和C#.NET COM服务器DLL的实现的简短概述(我省略了 native COM服务器的示例,因为这些东西已经可以很好地工作了,毫无疑问)。
我主要遵循"Registration-Free Activation of COM Components: A Walkthrough"提供的内容。主要区别在于,我使用Delphi而不是C,C++或旧的VB作为 native 客户端。TestDllConsoleApp.exeTestDllConsoleApp.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.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中)
<?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>

最后,程序集 list 通过TestDllConsoleApp.manifest 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而不是引用已解析的manifest目录就可以并按预期工作):
  • 我完全删除了全局 list 中的proxyStubClsid32属性:
  • 启动可执行文件最终会出现异常EOleSysError: Error in dll, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}
  • 调试异常会导致HRESULT
       Error in the DLL (Exception from HRESULT: 0x800401F9 (CO_E_ERRORINDLL))
    

  • 我在全局 list 中提供了proxyStubClsid32属性:
  • 我不确定该属性实际上需要哪个GUID。
    正如文档中提到的那样,它自然似乎是一个对应的“co class ID”(CLSID),如comClass元素clsid属性中所述。
  • 我也尝试从那里生成的,pas文件中提供LIBID GUID。

  • 两种变体都给我带来了一个无用的错误,可以使用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打来的,那是不同的。
  • 另一种尝试显式引用CSharpCOMDll.dll(可执行 list 中的另一个依赖项)并将其放入SharedLibs的尝试成功创建了激活上下文,但失败,但异常情况与之前有所不同
    EOleSysError: Cannot find file, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}
    

  • 这里有没有人知道如何直接做我想做的事情,或者可以做些什么(除了sxstrace之外)来更深入地诊断问题。
    我几乎可以肯定,有可能提供这样的部署。

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

  • 更新:
    今天,我进行了进一步的研究,我意识到(尽管术语非常相似),使用私有(private)SxS解决ActivationContext和解决用于COM可调用包装实例的.NET DLL的位置是两种完全不同且分离的机制。我主要从Jufeng Zhang's的这2本书中获得了更多,并深入解释了博客文章:
  • "COM activation, Registration Free COM activation, COM/.Net Interop, Registration Free COM/.Net Interop"
  • "Registration Free COM/.Net interop"

  • 与未注册的.NET程序集(托管的COM服务器DLL)的定位有关的问题是,这只会在应用程序部署目录及以下目录中发生。
    使用任何方法,例如在配置<codebase>节中指定<probing><runtime>元素,使其指向部署.config文件的目录之外,这根本行不通。
    我已验证使用Sysinternals Process Monitor和Fusion日志查看器工具2。
    我没有将其发布为最终答案,因为我接下来将尝试以某种方式欺骗.NET机制,以使用程序集 list 或指定依赖项和<probing>/<codebase>元素进行重定向的本地DLL来定位托管COM服务器DLL。定位机制。
    作为最后的手段(原文如此!),似乎甚至有可能在appDomainManagerAssembly元素下的应用程序配置中提供自己的自定义appDomainManagerType<runtime>

    更新二:
    恐怕我们必须使用本地CLR主机的CLR API自己管理AppDomain
    需要进一步调查。我在这里找到了一个很有前途的资源:
    "Customizing the Microsoft .NET Framework Common Language Runtime"

    1)
    请原谅德语错误消息。我手头没有英文版本的编译器。但是谷歌给出的翻译应该很好用。
    2)
    因此,有关用于诊断问题的更好工具的问题可以视为已解决。

    最佳答案

    • Is it even possible to provide a deployment structure like mentioned above, and maintain certain .NET COM server DLLs outside the referring executables locations?


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

    您可以使用
    <probing privatePath="<some directory below your executable's location>" />`
    

    但是<probing>标记对SxS解析(显示在 list <windows>标记下)和CLR的用于实例化<runtime>标记下的COM可调用包装器的机制的工作方式不同。

    它甚至没有记录,但是指定
    <windows>
        <probing privatePath="../<xxx>" />
    </windows>
    

    用于解析SxS依赖项的<xxx>的相对路径最多支持3个../父目录级别,从您可执行文件的位置适用于任何 native COM服务器,而
    <runtime>
        <probing privatePath="../<xxx>" />
        <!--                  ^^^ -->
    </runtime>
    

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

    不允许您使用标准的Windows .NET机制来指定指向AppDomain托管目录外部向上位置的程序集位置,以解析要实例化为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类的实现应在.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(即对CoCreateInstance()的调用)访问某些COM接口(interface)(COM可调用包装程序),则MyCustomAppDomainMgr类将被实例化,并且首先调用InitializeNewDomain()函数。

    最少干扰的方法似乎是添加该委托(delegate)函数:
    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)必须放置在非托管可执行应用程序的下面。

    关于c# - 如何提供可以正确定位.NET Dll作为COM提供程序的私有(private)并排 list ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48272723/

    相关文章:

    c# - C# 编码期间出现 FatalExecutionEngineError

    c# - 为什么这段代码没有编译?

    .net - 如何打印StackOverflowException的堆栈跟踪

    c# - 当属性名称与类名称相同时,IntelliSense 不使用扩展方法

    Delphi 11 Alexandria PaintBox 在 RDP 中闪烁

    C# 如何处理可空类型

    javascript - 以全屏模式启动 Google Chrome

    delphi - Firemonkey 样式设计器中另一个列表框中的列表框

    delphi - 创建Firemonkey表单并按代码填充

    c# - 困难的设置和获取组合属性