如何提供将 .NET Dll 正确定位为 COM 提供程序的私有并排清单?
How to provide a private Side by Side manifest that correctly locates a .NET Dll as COM Provider?
我正在研究使用简单的程序集清单文件提供的私人注册免费 WinSxS 的配置,以将 Delphi 可执行文件(COM 客户端)和 .NET (C#) COM 可见 DLL 拼接在一起部署和运行时。
我已经特别研究了 MSDN "Interoperating with Unmanaged Code", the sections about "COM Callable Wrapper" and "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 本机可执行文件和 C# .NET COM 服务器 DLL 的实现的简短概述(我省略了本机 COM 服务器的示例,因为这些东西已经运行良好毫无疑问)。
我主要按照 "Registration-Free Activation of COM Components: A Walkthrough" 提供的内容进行操作。主要区别是我使用 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 而不是引用已解析的清单目录就可以正常工作,并且预期):
我完全删除了全局清单中的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。
正如文档中提到的那样,它自然似乎是 comClass
元素 clsid
属性中提到的相应“co class ID”(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.
请注意,没有像
这样简洁的error/info消息
... cannot resolve assembly XY ...
在激活上下文生成搞砸之前。有很多参考资料表明了这种特殊的错误情况。
此外,普遍提到缺少 Visual C++ 可再发行框架 在这里也无济于事。我是从 Delphi 打来的,那是不一样的。
另一次尝试显式引用 CSharpCOMDll.dll
(可执行清单中的另一个依赖项),并将其放入 SharedLibs
成功创建 激活上下文,但失败并出现与之前略有不同的异常
EOleSysError: Cannot find file, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}
这里有没有人知道如何直接做我想做的事情,或者可以做些什么(除了 sxstrace
)来更深入地诊断问题。
我几乎可以肯定,一定可以提供这样的 部署。
TL;DR;
- 甚至可以提供如上所述的部署结构,并在引用可执行文件位置之外维护某些 .NET COM 服务器 DLL 吗?
更新:
今天进一步研究,我意识到(尽管术语非常相似),用私有 SxS 解析 ActivationContext 并解析为 COM 服务的 .NET DLL 的位置可调用包装器实例化是两个完全不同且独立的机制。我主要是从这 2 篇和更多 Jufeng Zhang's 精彩而深入的解释博客文章中得到的:
- "COM activation, Registration Free COM activation, COM/.Net Interop, Registration Free COM/.Net Interop"
- "Registration Free COM/.Net interop"
未注册的 .NET 程序集(托管 COM 服务器 DLL)的定位问题在于,这只会发生在应用程序部署目录内及以下。
使用任何方法,例如在配置 <runtime>
部分中指定 <codebase>
或 <probing>
元素,指向部署 .config
文件的目录之外,根本不会没用。
我验证了使用 Sysinternals Process Monitor 和 Fusion 日志查看器工具2。
我不会将其作为最终答案发布,因为接下来我将尝试以某种方式欺骗 .NET 机制来定位托管 COM 服务器 DLL,使用程序集清单或指定依赖项的本机 DLL 和 <probing>
/ <codebase>
元素重定向定位机制。
作为最后的手段(原文如此!),似乎甚至可以在 <runtime>
元素下的应用程序配置中提供您自己的自定义 appDomainManagerAssembly
和 appDomainManagerType
。
更新二:
恐怕我们必须使用来自本机 CLR 主机的 CLR API 自己管理 AppDomain
。
需要进一步调查。我在这里找到了一个很有前途的资源:
"Customizing the Microsoft .NET Framework Common Language Runtime"
1)
请原谅德语错误消息。我手边没有英文版的编译器。但是 google 给出的翻译应该可以正常工作。
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 解析(出现在清单 <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 机制来指定指向 AppDomain 托管目录之外的位置的程序集位置,以将候选人解析为实例化为 COM Callable Wrappers(由 mscoreee.dll
托管)。
从您的可执行文件的部署目录向下深入,效果很好,符合预期。
拦截 CLR 探测机制的一种方法(可能是最简单的方法)是提供自定义 AppDomainManager
实现并在应用程序配置文件的 <appDomainManagerAssembly>
and <appDomainManagerType>
元素中指定它:
<configuration>
<runtime>
<appDomainManagerAssembly value="MyAppDomainMgr" />
<appDomainManagerType value="MyAppDomainMgr.MyCustomAppDomainMgr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</runtime>
<configuration>
MyAppDomainMgr.MyCustomAppDomainMgr
class 的实现应该在 .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 访问某些 COM 接口(COM 可调用包装器)(即调用 CoCreateInstance()
),MyCustomAppDomainMgr
class 将被实例化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
) 必须放置在非托管可执行应用程序之下。
我正在研究使用简单的程序集清单文件提供的私人注册免费 WinSxS 的配置,以将 Delphi 可执行文件(COM 客户端)和 .NET (C#) COM 可见 DLL 拼接在一起部署和运行时。
我已经特别研究了 MSDN "Interoperating with Unmanaged Code", the sections about "COM Callable Wrapper" and "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 本机可执行文件和 C# .NET COM 服务器 DLL 的实现的简短概述(我省略了本机 COM 服务器的示例,因为这些东西已经运行良好毫无疑问)。
我主要按照 "Registration-Free Activation of COM Components: A Walkthrough" 提供的内容进行操作。主要区别是我使用 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 而不是引用已解析的清单目录就可以正常工作,并且预期):
我完全删除了全局清单中的
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。
正如文档中提到的那样,它自然似乎是comClass
元素clsid
属性中提到的相应“co class ID”(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.
请注意,没有像
这样简洁的error/info消息... cannot resolve assembly XY ...
在激活上下文生成搞砸之前。有很多参考资料表明了这种特殊的错误情况。
此外,普遍提到缺少 Visual C++ 可再发行框架 在这里也无济于事。我是从 Delphi 打来的,那是不一样的。- 我不确定该属性实际需要哪个 GUID。
另一次尝试显式引用
CSharpCOMDll.dll
(可执行清单中的另一个依赖项),并将其放入SharedLibs
成功创建 激活上下文,但失败并出现与之前略有不同的异常EOleSysError: Cannot find file, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}
这里有没有人知道如何直接做我想做的事情,或者可以做些什么(除了 sxstrace
)来更深入地诊断问题。
我几乎可以肯定,一定可以提供这样的 部署。
TL;DR;
- 甚至可以提供如上所述的部署结构,并在引用可执行文件位置之外维护某些 .NET COM 服务器 DLL 吗?
更新:
今天进一步研究,我意识到(尽管术语非常相似),用私有 SxS 解析 ActivationContext 并解析为 COM 服务的 .NET DLL 的位置可调用包装器实例化是两个完全不同且独立的机制。我主要是从这 2 篇和更多 Jufeng Zhang's 精彩而深入的解释博客文章中得到的:
- "COM activation, Registration Free COM activation, COM/.Net Interop, Registration Free COM/.Net Interop"
- "Registration Free COM/.Net interop"
未注册的 .NET 程序集(托管 COM 服务器 DLL)的定位问题在于,这只会发生在应用程序部署目录内及以下。
使用任何方法,例如在配置 <runtime>
部分中指定 <codebase>
或 <probing>
元素,指向部署 .config
文件的目录之外,根本不会没用。
我验证了使用 Sysinternals Process Monitor 和 Fusion 日志查看器工具2。
我不会将其作为最终答案发布,因为接下来我将尝试以某种方式欺骗 .NET 机制来定位托管 COM 服务器 DLL,使用程序集清单或指定依赖项的本机 DLL 和 <probing>
/ <codebase>
元素重定向定位机制。
作为最后的手段(原文如此!),似乎甚至可以在 <runtime>
元素下的应用程序配置中提供您自己的自定义 appDomainManagerAssembly
和 appDomainManagerType
。
更新二:
恐怕我们必须使用来自本机 CLR 主机的 CLR API 自己管理 AppDomain
。
需要进一步调查。我在这里找到了一个很有前途的资源:
"Customizing the Microsoft .NET Framework Common Language Runtime"
1) 请原谅德语错误消息。我手边没有英文版的编译器。但是 google 给出的翻译应该可以正常工作。
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 解析(出现在清单 <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 机制来指定指向 AppDomain 托管目录之外的位置的程序集位置,以将候选人解析为实例化为 COM Callable Wrappers(由 mscoreee.dll
托管)。
从您的可执行文件的部署目录向下深入,效果很好,符合预期。
拦截 CLR 探测机制的一种方法(可能是最简单的方法)是提供自定义 AppDomainManager
实现并在应用程序配置文件的 <appDomainManagerAssembly>
and <appDomainManagerType>
元素中指定它:
<configuration>
<runtime>
<appDomainManagerAssembly value="MyAppDomainMgr" />
<appDomainManagerType value="MyAppDomainMgr.MyCustomAppDomainMgr, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</runtime>
<configuration>
MyAppDomainMgr.MyCustomAppDomainMgr
class 的实现应该在 .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 访问某些 COM 接口(COM 可调用包装器)(即调用 CoCreateInstance()
),MyCustomAppDomainMgr
class 将被实例化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
) 必须放置在非托管可执行应用程序之下。