我正在研究一个免费的私有注册WinSxS的配置,它提供了简单的程序集清单文件,在部署和运行时将Delphi可执行文件(COM客户端)和.NET(C#)COM可见DLL组合在一起.
我已经研究了MSDN "与非托管代码互操作"中提供的文档,特别是关于" COM Callable Wrapper "和" 如何:为无注册激活配置基于.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
Run Code Online (Sandbox Code Playgroud)
下面是关于Delphi本机可执行文件和C#.NET COM服务器DLL实现的实现的简短草图(我省略了原生COM服务器的示例,因为这些东西已经运行良好且不可能).
我主要遵循"注册 - 免费激活COM组件:演练"中提供的内容.主要区别在于我使用Delphi而不是C,C++或旧VB作为本机客户端.
TestDllConsoleApp.exeTestDllConsoleApp.dprprogram 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.
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
TestDllConsoleApp.exe.config(部署在与可执行文件相同的文件位置)
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="..\..\SharedLibs"/>
</assemblyBinding>
</runtime>
</configuration>
Run Code Online (Sandbox Code Playgroud)
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")]
Run Code Online (Sandbox Code Playgroud)
// 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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
// 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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
最后,从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>
Run Code Online (Sandbox Code Playgroud)
看来我已经到了一半,但仍无法诊断实际问题.
现在有两种失败的变种(请注意,在可执行文件旁边部署托管COM服务器DLL而不是引用已解析的清单目录,只是按预期正常工作):
我完全删除proxyStubClsid32了全局清单中的属性:
EOleSysError: Error in dll, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}调试异常会导致HRESULT值
Error in the DLL (Exception from HRESULT: 0x800401F9 (CO_E_ERRORINDLL))
Run Code Online (Sandbox Code Playgroud)我proxyStubClsid32在全局清单中提供了一个属性:
CLSID提到的相应的"co class ID"().comClassclsid,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.
Run Code Online (Sandbox Code Playgroud)
请注意,没有任何简洁的错误/信息消息
Run Code Online (Sandbox Code Playgroud)... cannot resolve assembly XY ...
在激活上下文生成之前搞砸了.有很多参考资料表明这种特殊的错误.
此外,无处不在的提到缺少Visual C++可再发行框架并没有帮助.我是从Delphi打来的,那是不同的.
另一种尝试引用CSharpCOMDll.dllexplicitely(可执行文件清单中的另一个依赖项),并将其放入已SharedLibs成功创建的激活上下文中,但失败时的异常略有不同之前
EOleSysError: Cannot find file, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}
Run Code Online (Sandbox Code Playgroud)这里有没有人知道如何做我想要的直截了当,或者可以做什么另外(除了sxstrace)更深入地诊断问题.
我几乎可以肯定必须提供这样的部署.
今天进一步研究,我意识到(尽管术语非常相似),使用私有SxS 解析ActivationContext并解析用于COM可调用包装器实例化的.NET DLL的位置是两个完全不同且分离的机制.我大部分时间都是从这两个人那里得到的,还有一些张巨峰的精彩和深入解释的博客文章:
定位未注册的.NET程序集(托管COM服务器DLL)的问题是,这只会发生在应用程序部署目录及其下方.
使用任何方法(如在配置部分中指定一个<codebase>或<probing>元素<runtime>指向.config部署文件的目录之外)根本不起作用.
我验证了使用Sysinternals Process Monitor和Fusion日志查看器工具2.
我不会将其作为最终答案发布,因为我将尝试下一步以某种方式欺骗.NET机制来定位托管COM服务器DLL,使用程序集清单或指定依赖项和<probing>/ <codebase>元素的本机DLL 来重定向定位机制.
作为最后的手段(原文!),似乎甚至可以在元素下提供您自己的自定义appDomainManagerAssembly和appDomainManagerType应用程序配置<runtime>.
我担心我们必须AppDomain使用来自本机CLR主机的CLR API 来管理自己.
需要进一步调查.一个有前途的资源如何做到这一点我在这里找到:
"自定义Microsoft .NET Framework公共语言运行时"
1) 请原谅德语错误消息.我手头没有英文版编译器.但谷歌的翻译应该运作良好.
2) 因此,关于更好的诊断问题的工具的问题可以被认为已经解决了.
小智 6
- 是否有可能提供如上所述的部署结构,并在引用的可执行文件位置之外维护某些 .NET COM 服务器 DLL?
这是绝对不可能的(!) ,以解决提供托管外部机制的内在CLR任何组件AppDomain的可执行文件的目录。
您可以使用
<probing privatePath="<some directory below your executable's location>" />`
Run Code Online (Sandbox Code Playgroud)
但是<probing>对于 SxS 解析(出现在清单<windows>标签下)和 CLR 的机制来实例化出现在标签下的COM 可调用包装器,该标签的工作方式有所不同<runtime>。
它甚至没有记录,但指定
<windows>
<probing privatePath="../<xxx>" />
</windows>
Run Code Online (Sandbox Code Playgroud)
用于解析 SxS 依赖项,支持从可执行文件位置开始的<xxx>最多 3 个../父目录级别的相对路径适用于任何本机 COM 服务器,而
<runtime>
<probing privatePath="../<xxx>" />
<!-- ^^^ -->
</runtime>
Run Code Online (Sandbox Code Playgroud)
或者
<runtime>
<codebase href="../<xxx>/xyz.dll" version="1.0.0.0"/>
<!-- ^^^ -->
</runtime>
Run Code Online (Sandbox Code Playgroud)
不允许您使用标准的 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>
Run Code Online (Sandbox Code Playgroud)
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.
}
}
}
Run Code Online (Sandbox Code Playgroud)
一旦您的非托管应用程序尝试通过 CLR(即调用CoCreateInstance())访问某个 COM 接口(COM Callable Wrapper ),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
}
Run Code Online (Sandbox Code Playgroud)
生成的程序集 ( MyAppDomainMgr.dll) 必须放置在非托管可执行应用程序下方。