我正在研究私有注册免费 WinSxS 的配置,并提供简单的程序集清单文件,以便在部署和运行时将 Delphi 可执行文件(COM 客户端)和 .NET (C#) COM 可见 DLL 拼接在一起。
我已经研究了 MSDN 上提供的文档“与非托管代码互操作” https://learn.microsoft.com/en-us/dotnet/framework/interop/,有关的部分"COM 可调用包装器" https://learn.microsoft.com/en-us/dotnet/framework/interop/com-callable-wrapper and "如何:配置基于 .NET Framework 的 COM 组件以进行免注册激活" https://learn.microsoft.com/en-us/dotnet/framework/interop/configure-net-framework-based-com-components-for-reg尤其。
经过一个多星期的研究并在文档不足的循环中被(重新)引导,我决定将我的第一个问题放在这里。
计划的部署结构如下所示:
./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 组件:演练” https://msdn.microsoft.com/en-us/library/ms973913.aspx。主要区别在于我使用 Delphi 而不是 C、C++ 或旧的 VB 作为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
(嵌入资源 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
value
Error in the DLL (Exception from HRESULT: 0x800401F9 (CO_E_ERRORINDLL))
-
我提供一个proxyStubClsid32
全局清单中的属性:
- 我不确定该属性实际需要哪个 GUID。
正如文档中提到的,它自然似乎是相应的“co 类 ID”(CLSID
)如中提到的comClass
元素clsid
属性。
- 我也尝试从生成的文件中提供 LIBID GUID
,pas
文件在那里。
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.
请注意,没有任何简洁的错误/信息消息,例如
... cannot resolve assembly XY ...
之前激活上下文生成搞砸了。有大量参考资料表明了这种特定的错误情况。
还有无处不在的失踪Visual C++ 可再发行框架在这里没有帮助。我从德尔福打电话,情况有所不同。
-
另一种尝试参考CSharpCOMDll.dll
明确地(可执行清单中的另一个依赖项),然后将其放入SharedLibs
已成功创建激活上下文,但失败并出现与以前略有不同的异常
EOleSysError: Cannot find file, clsid = {4CD39F25-0EB9-4CD0-9B4C-6F5DB5C14805}
这里有人知道如何直接做我想做的事情,或者还可以做些什么(除了sxstrace
)更深入地诊断问题。
我几乎可以肯定一定可以提供部署像这样。
TL;DR;
- 是否甚至可以提供如上所述的部署结构,并在引用的可执行文件位置之外维护某些 .NET COM 服务器 DLL?
Update:
今天进一步研究,我意识到(尽管术语非常相似),解决了激活上下文使用私有 SxS 和解析用于 COM 可调用包装器实例化的 .NET DLL 的位置是两种完全不同且独立的机制。我主要是从这两个和更多的张巨峰的 https://blogs.msdn.microsoft.com/junfeng/精彩且深入的解释博客文章:
- “COM激活、免注册COM激活、COM/.Net互操作、免注册COM/.Net互操作” https://blogs.msdn.microsoft.com/junfeng/2006/04/20/com-activation-registration-free-com-activation-com-net-interop-registration-free-com-net-interop/
- “免注册 COM/.Net 互操作” https://blogs.msdn.microsoft.com/junfeng/2006/05/17/registration-free-com-net-interop/
定位未注册的 .NET 程序集(托管 COM 服务器 DLL)的问题是,这只发生在应用程序部署目录及其以下目录中。
使用任何方法,例如指定<codebase>
or <probing>
配置内的元素<runtime>
指向目录外部的部分.config
文件已部署,但根本不起作用。
I verified that using the Sysinternals Process Monitor and the Fusion log viewer tool2.
我不会将其作为最终答案发布,因为接下来我将尝试以某种方式欺骗 .NET 机制来定位托管 COM 服务器 DLL,使用程序集清单或指定依赖项的本机 DLL 和<probing>
/ <codebase>
元素重定向定位机制。
作为最后的手段(原文如此!),似乎甚至可以提供您自己的定制appDomainManagerAssembly
and appDomainManagerType
在应用程序配置下<runtime>
元素。
更新二:
恐怕我们必须去管理AppDomain
我们自己使用来自本机 CLR 主机的 CLR API。
需要进一步调查。我在这里找到了一个很有前景的资源:
“自定义 Microsoft .NET Framework 公共语言运行时” http://flylib.com/books/en/4.331.1/
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.