如何提供可以正确定位.NET Dll作为COM提供程序的私有并排清单? [英] How to provide a private Side by Side manifest that correctly locates a .NET Dll as COM Provider?

查看:166
本文介绍了如何提供可以正确定位.NET Dll作为COM提供程序的私有并排清单?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究通过提供程序清单文件的方式免费提供免费注册的WinSxS的配置,以便在部署和运行时将Delphi可执行文件(COM客户端)和.NET(C#)COM可见DLL缝合在一起.

我已经研究了MSDN上可用的文档 "与非托管代码" ,有关 COM可调用包装器" 如何:在其中配置基于.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作为 native 客户端.

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.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>


最后,从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部署在可执行文件旁边而不是引用解析的清单目录就可以了,并且按预期进行了工作):

  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 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打来的,那是另一回事.

  3. 另一种尝试显式引用CSharpCOMDll.dll的方法(可执行清单中的另一个依赖项),然后将其放入SharedLibs中,成功创建了 Activation Context ,但失败了异常与以前略有不同

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

这里有没有人知道如何做我想做的简单的事情,或者可以做些什么(除了sxstrace之外)来更深入地诊断问题.

我几乎可以肯定,有可能提供这样的部署.


TL; DR;

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

更新:

今天进一步研究,我意识到(尽管术语非常相似),使用私有SxS解决 ActivationContext 和解决用于COM可调用包装实例的.NET DLL的位置是两个完全不同和分离的机制.我主要是从这两个以及 张丰峰的 中获得的信息精彩而深入的博客文章解释:

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

使用任何方法,例如在配置<runtime>部分中指定<codebase><probing>元素,该元素指向.config文件的部署目录之外,根本行不通.

我已验证使用Sysinternals Process Monitor和Fusion日志查看器工具 2 .

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

作为最后的手段(原文如此!),似乎甚至有可能在应用程序配置中的<runtime>元素下提供自己的自定义appDomainManagerAssemblyappDomainManagerType.


更新II:

恐怕我们不得不使用来自本地CLR主机的CLR API自己管理AppDomain.

需要进一步调查.我在这里找到了一个很有前途的资源:

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



1) 请原谅德语错误消息.我手头没有英文版本的编译器.但是google提供的翻译应该能很好地工作.

2) 因此,有关用于诊断问题的更好工具的问题可以视为已解决.

解决方案

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

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

您可以使用

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

但是<probing>标记对于SxS解析的工作方式不同(显示在清单<windows>标记下),并且CLR的实例化 COM可调用包装器的机制出现在<runtime>标记下./p>


甚至没有记录,但指定了

<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机制来指定指向 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(即对CoCreateInstance()的调用)访问某个COM接口(COM可调用包装程序),则将实例化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)必须放置在非托管可执行应用程序的下方.


I'm researching about the configuration of a private registration free WinSxS with the plain provision of assembly manifest files, to stitch Delphi executables (COM clients) and .NET (C#) COM visible DLLs together at deployment and runtime.

I already studied the documentation available at MSDN "Interoperating with Unmanaged Code", the sections about "COM Callable Wrapper" and "How to: Configure .NET Framework-Based COM Components for Registration-Free Activation" in particular.

After even more than one week of research and being (re-)directed in cycles of insufficient documentation, I decided to place my 1st question ever here.

The planned deployment structure looks as follows:

./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


Here's a short sketch about the implementation of the implementation of the Delphi native executables and the C# .NET COM server DLLs (I left out the examples for the native COM Servers, since this stuff already works well and is out of question).
I mainly followed what was provided at "Registration-Free Activation of COM Components: A Walkthrough". The main difference is that I'm utilizing Delphi rather than C, C++ or old VB as a native client.

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

(embedded with resource 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

(deployed at the same file location as the executable)

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


CSharpCOMDll.dll

(will be deployed at the SharedLibs\MyCompany.Libs.Set1 directory)

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

(Embedded into the DLL with resource ID 2)

<?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>


And finally the assembly manifest as resolved from the TestDllConsoleApp.manifest dependency entries:

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>


It seems I'm halfway there, but still can't diagnose the actual problem.

There are two variants of failure right now (Please note, that deploying the managed COM server DLLs beside the executable instead of referring to the resolved manifests directory just works fine and as intended):

  1. I completely remove the proxyStubClsid32 attribute in the global manifest:

    • Starting the executable ends up with an exception
      EOleSysError: Error in dll, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}

    • Debugging the exception leads to a HRESULT value

         Error in the DLL (Exception from HRESULT: 0x800401F9 (CO_E_ERRORINDLL))
      

  2. I provide a proxyStubClsid32 attribute in the global manifest:

    • I'm not sure wich GUID is actually needed for that attribute.
      As it's mentioned in the documentation it naturally seems to be a corresponding "co class ID" (CLSID) as mentioned in the comClass elements clsid attribute.
    • I alternatively tried to provide the LIBID GUID from the generated ,pas file there.

    Both variants leave me with a pretty useless error traceable with the sxstrace tool1:

     ...
     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.
    

    Note that there wasn't any concise error/info message like

      ... cannot resolve assembly XY ...
    

    before the Activation Context Generation screwed up. There's plenty of references indicating this particular error situtation.
    Also the ubiquitous mentions of missing Visual C++ redistributable framework doesn't help here. I'm calling from Delphi, and that's something different.

  3. Another attempt to reference the CSharpCOMDll.dll explicitely (another dependency in the executable manifest), and just place it into the SharedLibs got a successfully created Activation Context, but fails with a slightly different exception than before

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

Does anyone here know how to do what I want straightforward, or what can be done additionally (besides the sxstrace) to diagnose the problem in more depth.

I'm almost sure it must be possible to provide a deployment like this.


TL;DR;

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

Update:

Researching further today, I realized that (despite the very similar terminology), resolving the ActivationContext with a private SxS and resolving the location of .NET DLLs which serve for a COM callable wrapper instantiation are two completely distinct and separated mechanisms. I mostly got that from these 2 and some more of Jufeng Zhang's brilliant and in depth explaining blog articles:

The problem with the locating of the unregistered .NET assemblies (managed COM server DLLs) is, that this will only happen inside the applications deployment directory and below.

Using any method like specifying a <codebase> or <probing> element inside the configuration <runtime> section pointing outside the the directory where the .config file is deployed, simply doesn't work.

I verified that using the Sysinternals Process Monitor and the Fusion log viewer tool2.

I'm not posting that as a final answer, because I'll try next somehow to trick that .NET mechanism to locate the managed COM server DLLs, using an assembly manifest, or native DLL specifiying the dependencies and <probing> / <codebase> element to redirect the locating mechanism.

As a last resort (sic!) it seems to be even possible to provide your own customized appDomainManagerAssembly and appDomainManagerType in the application configuration under the <runtime> element.


Update II:

I'm afraid we have to go for managing the AppDomain ourselves using the CLR API from a native CLR Host.

Needs further investigation. One promising resource how to do that I found here:

"Customizing the Microsoft .NET Framework Common Language Runtime"



1) Excuse the german error messages please. I don't have an english version compiler at hand. But the translation given at google should work well.

2) So the question about better tools for diagnosing the problems, can be considered as solved.

解决方案

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

It's definitely not possible(!) to resolve any assemblies provided for the intrinsic CLR hosting mechanism outside the AppDomain's executable directory.

You can use the

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

But the <probing> tag works differently for SxS resolving (appearing under the manifest <windows> tag), and the CLR's mechanism to instantiate COM Callable Wrappers appearing under the <runtime> tag.


It's even undocumented, but specifying

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

for resolving the SxS dependencies supports relative paths for <xxx> up to 3 ../ parent directory levels from your executable's location works for any native COM server, while

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

or

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

won't allow you to specify assembly locations pointing to locations upwards outside your AppDomain's hosting directory using the standard windows .NET mechanisms to resolve candidates to be instantiated as COM Callable Wrappers (hosted by the mscoreee.dll).
Descending deeper from your executable's deployment directory works well and as intended.


One way (probably the easiest) to intercept the CLR probing mechanism, is to provide a custom AppDomainManager implementation and specify it in the <appDomainManagerAssembly> and <appDomainManagerType> elements of the application configuration file:

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

The implementation of the MyAppDomainMgr.MyCustomAppDomainMgr class should be in a .NET assembly, e.g. written in 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.
         }
     }
 }

As soon your unmanaged application tries to access some COM interface (COM Callable Wrapper) through the CLR (i.e. a call to CoCreateInstance()), the MyCustomAppDomainMgr class will be instantiated and the InitializeNewDomain() function is called first.

The least intrusive way seems to be to add that delegate function:

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
}

The resulting assembly (MyAppDomainMgr.dll), must be placed beneath the unmanaged executable application.


这篇关于如何提供可以正确定位.NET Dll作为COM提供程序的私有并排清单?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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