Как обеспечить частный параллельный манифест, который правильно определяет местоположение.NET Dll в качестве COM-провайдера?
Я исследую конфигурацию бесплатной WinSxS для частной регистрации с простым предоставлением файлов манифеста сборки, чтобы объединить исполняемые файлы Delphi (COM-клиенты) и.NET (C#) COM-видимые библиотеки DLL во время развертывания и выполнения.
Я уже изучил документацию, имеющуюся в MSDN "Взаимодействие с неуправляемым кодом", в частности, разделы " COM Callable Wrapper " и " Как: настроить COM-компоненты на основе.NET Framework для активации без регистрации ".
После более чем одной недели исследований и (пере) направленного в циклах недостаточной документации, я решил разместить здесь свой первый вопрос.
Планируемая структура развертывания выглядит следующим образом:
./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 COM-сервера C# .NET (я пропустил примеры для собственных 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
(встроен с идентификатором ресурса 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 на самом деле нужен для этого атрибута.
Как упоминалось в документации, это, естественно, кажется соответствующим "идентификатором класса co" (CLSID
) как указано вcomClass
элементыclsid
приписывать. - Я альтернативно попытался предоставить LIBID GUID из сгенерированного
,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++. Я звоню из Delphi, и это что-то другое.- Я не уверен, какой GUID на самом деле нужен для этого атрибута.
Еще одна попытка сослаться на
CSharpCOMDll.dll
явно (еще одна зависимость в исполняемом манифесте), и просто поместите его вSharedLibs
получил успешно созданный контекст активации, но завершился неудачей с немного другим исключением, чем раньшеEOleSysError: Cannot find file, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}
Кто-нибудь здесь знает, как сделать то, что я хочу прямо, или что можно сделать дополнительно (кроме sxstrace
) диагностировать проблему более подробно.
Я почти уверен, что такое развертывание должно быть возможным.
TL; DR;
- Возможно ли даже предоставить структуру развертывания, подобную упомянутой выше, и поддерживать определенные библиотеки DLL COM-сервера.NET вне соответствующих мест расположения исполняемых файлов?
Обновить:
В ходе дальнейших исследований сегодня я понял, что (несмотря на очень похожую терминологию) разрешение Activation Context с помощью частного SxS и определение местоположения DLL-библиотек.NET, которые служат для создания экземпляров вызываемой оболочки COM, представляют собой два совершенно разных и отдельных механизма. В основном я понял это из этих двух, а также из нескольких блестящих и подробно объясняющих статьи в блоге Джуфэна Чжана:
- "Активация COM, Регистрация Free COM активация, COM/.Net Interop, Регистрация Free COM /.Net Interop"
- "Регистрация Free COM/.Net interop"
Проблема с обнаружением незарегистрированных сборок.NET (управляемых библиотек DLL COM-сервера) заключается в том, что это происходит только в каталоге развертывания приложений и ниже.
Используя любой метод, например, указав <codebase>
или же <probing>
элемент внутри конфигурации <runtime>
раздел, указывающий вне каталога, где .config
Файл развернут, просто не работает.
Я проверил это с помощью Sysinternals Process Monitor и инструмента просмотра журнала Fusion 2.
Я не публикую это в качестве окончательного ответа, потому что затем я попытаюсь каким-то образом обмануть этот механизм.NET для определения местоположения DLL-файлов управляемого COM-сервера, используя манифест сборки или собственную DLL, указывающую зависимости и <probing>
/ <codebase>
элемент для перенаправления локационного механизма.
В крайнем случае (так!), Кажется, даже возможно предоставить свои собственные индивидуальные appDomainManagerAssembly
а также appDomainManagerType
в конфигурации приложения под <runtime>
элемент.
Обновление II:
Я боюсь, что мы должны пойти на управление AppDomain
мы сами используем CLR API с собственного CLR Host.
Необходимо дальнейшее расследование. Один многообещающий ресурс, как это сделать, я нашел здесь:
"Настройка общеязыковой среды выполнения Microsoft .NET Framework"
1) Извините немецкие сообщения об ошибках, пожалуйста. У меня нет компилятора английской версии. Но перевод на Google должен хорошо работать.
2) Таким образом, вопрос о лучших инструментах для диагностики проблем можно считать решенным.
1 ответ
- Возможно ли даже предоставить структуру развертывания, подобную упомянутой выше, и поддерживать определенные библиотеки DLL COM-сервера.NET вне соответствующих мест расположения исполняемых файлов?
Определенно невозможно (!) Разрешить любые сборки, предусмотренные для встроенного механизма хостинга CLR, за пределами AppDomain
Исполняемый каталог.
Вы можете использовать
<probing privatePath="<some directory below your executable's location>" />`
Но <probing>
тег работает по-разному для разрешения SxS (появляется под манифестом <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>
не позволит вам указывать места сборки, указывающие на места вверх за пределами хост -каталога вашего домена приложений, используя стандартные механизмы Windows .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, например, написанной на 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.
}
}
}
Как только ваше неуправляемое приложение пытается получить доступ к какому-либо 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
), должны быть размещены под неуправляемым исполняемым приложением.