Подтвердить что ты не робот

Как предоставить частный манифест Side by Side, который правильно находит .NET Dll в качестве поставщика услуг COM?

Я изучаю настройку бесплатной бесплатной WinSxS для бесплатной регистрации с простым предоставлением файлов манифеста сборки, для стежки исполняемых файлов Delphi (COM-клиентов) и COM-библиотек .NET(С#) совместно отображаемых DLL вместе при развертывании и времени выполнения.

Я уже изучил документацию, доступную в MSDN "Взаимодействие с неуправляемым кодом" , разделы " COM Callable Wrapper" и Как сделать: Настройка COM-компонентов на основе .NET Framework для активации без регистрации.

После более чем одной недели исследований и повторного обращения в циклы недостаточной документации я решил разместить здесь 1-й вопрос.

Планируемая структура развертывания выглядит следующим образом:

./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 и DLL С#.NET COM-сервера (я не использовал примеры для собственных COM-серверов, так как этот материал уже работает хорошо, и об этом не обошлось).
Я в основном следил за тем, что было предоставлено в "Без активации регистрации компонентов COM: пошаговое руководство" . Основное отличие заключается в том, что я использую Delphi, а не 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

(встроенный с идентификатором ресурса 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

(встроен в DLL с идентификатором ресурса 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>

И, наконец, манифест сборки разрешен из записей 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>

Кажется, я нахожусь на полпути, но до сих пор не могу диагностировать реальную проблему.

Сейчас есть два варианта сбоя ( Обратите внимание, что развертывание управляемых DLL-серверов COM-сервера рядом с исполняемым файлом вместо обращения к разрешенному файлу манифеста работает отлично и по назначению):

  • Я полностью удаляю атрибут proxyStubClsid32 в глобальном манифесте:

    • Запуск исполняемого файла заканчивается исключением
      EOleSysError: Error in dll, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}
    • Отладка исключения приводит к значению HRESULT

       Error in the DLL (Exception from HRESULT: 0x800401F9 (CO_E_ERRORINDLL))
      
  • Я предоставляю атрибут proxyStubClsid32 в глобальном манифесте:

    • Я не уверен, что GUID действительно необходим для этого атрибута.
      Как упоминалось в документации, естественно, это соответствует соответствующему "идентификатору класса" (CLSID), как указано в атрибуте comClass CLSID.
    • Я также попытался предоставить идентификатор LIBID из сгенерированного файла ,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 С++. Я звоню из Delphi, и что-то другое.

  • Еще одна попытка явно ссылаться на CSharpCOMDll.dll (другая зависимость в исполняемом манифесте) и просто поместить его в SharedLibs, получив успешно созданный контекст активации, но не выполняет несколько иное исключение, чем раньше

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

Кто-нибудь знает, как делать то, что я хочу просто, или что можно сделать дополнительно (помимо sxstrace), чтобы более подробно диагностировать проблему.

Я почти уверен, что возможно обеспечить такое развертывание.


TL; DR;

  • Возможно ли предоставить структуру развертывания, как упомянуто выше, и поддерживать определенные DLL-серверы .NET COM-сервера за пределами ссылочных мест исполняемых файлов?

Обновление:

Изучая дальнейшие сегодня, я понял, что (несмотря на очень похожую терминологию), разрешение ActivationContext с помощью частного SxS и разрешение размещения .NET DLL, которые служат для создания COM-конвейерной оболочки, представляют собой два совершенно разных и разделенных механизма. Я в основном получил это от этих 2 и еще несколько Jufeng Zhang's, блестящих и подробно объясняющих статьи в блогах:

Проблема с размещением незарегистрированных сборников .NET(управляемых COM-серверов DLL) заключается в том, что это произойдет только в каталоге развертывания приложений и ниже.

Используя любой метод, например указание элемента <codebase> или <probing> внутри раздела <runtime>, указывающего вне каталога, в котором развернут файл .config, просто не работает.

Я проверил, что с помощью Sysinternals Process Monitor и инструмента просмотра журнала Fusion 2.

Я не отправляю это как окончательный ответ, потому что я попытаюсь сделать что-то иначе, чтобы обмануть этот механизм .NET, чтобы найти управляемые DLL-серверы COM-сервера, используя манифест сборки или собственную DLL, специфицирующую зависимости, и <probing>/<codebase>, чтобы перенаправить механизм определения местоположения.

В качестве последнего средства (sic!), возможно, даже можно предоставить свои собственные настроенные appDomainManagerAssembly и appDomainManagerType в конфигурации приложения под элементом <runtime>.


Обновление II:

Я боюсь, что нам нужно пойти на управление AppDomain самостоятельно, используя CLR API из собственного CLR Host.

Требуется дальнейшее расследование. Один многообещающий ресурс, как это сделать, я нашел здесь:

" Настройка среды выполнения Common Language Runtime Microsoft.NET Framework



1) Извините, пожалуйста, немецкие сообщения об ошибках. У меня нет компилятора на английском языке. Но перевод, указанный в google, должен хорошо работать.

2) Таким образом, вопрос о лучших инструментах для диагностики проблем можно считать разрешенным.

4b9b3361

Ответ 1

  • Возможно ли предоставить структуру развертывания, как упомянуто выше, и поддерживать определенные DLL-серверы .NET COM-сервера за пределами ссылочных мест исполняемых файлов?

Невозможно (!) разрешить любые сборки, предоставленные для встроенного хост-механизма CLR вне исполняемого каталога AppDomain.

Вы можете использовать

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

Но тег <probing> работает по-разному для разрешения SxS (появляется под тегом manifest <windows>) и механизма CLR для создания экземпляров COM Callable Wrappers, отображаемых под тегом <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>

не позволит вам указывать места сборки, указывающие на местоположения вверх вне вашего хостингового каталога AppDomain, используя стандартные механизмы .NET.NET для разрешения кандидатов, которые должны быть созданы как COM Callable Wrappers (размещенные 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, например. написанный на С#:

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

Как только ваше неуправляемое приложение попытается получить доступ к некоторому COM-интерфейсу (COM Callable Wrapper) через CLR (т.е. вызов 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) должна быть помещена под неуправляемым исполняемым приложением.