如何同时拥有多个版本的Delphi COM Server?
我们有一个用 Delphi 10.2 Tokyo 编写的 COM 服务器,其中包含 4 个类,所有类都继承自 TAutoObject
,并且都有一个包含对 TAutoObjectFactory 的调用的
。initialization
部分.创建
在典型的安装中,我们有大约 60 个其他程序使用 COM 服务器中的类。这一切都很好。每个类都有自己的GUID,并且Delphi COM 服务器执行其通常的操作,确保它们都作为InitComServer
的一部分正确注册。从 Application.Initialize
运行的进程。
我们现在需要并行运行多个安装,其中它们可能是不同的版本。由于我们无法控制所有程序,因此我们无法为每次安装生成具有不同 GUID 的不同版本的 COM 服务器。
我找到了这个微软文档:
但这种方法不起作用。当主程序启动并创建第一个COM服务器类的实例时,我可以看到正在运行的是注册的EXE,而不是与主程序位于同一文件夹中的COM服务器EXE。这可能是因为它是 EXE 而不是 DLL,并且 Delphi 初始化绕过了 DLL/COM 重定向。
下面是使用 COM 类之一的示例:
function ProduceReport(const ATenantID, AReportID: Integer; const AFilter: String): String;
var
AReportServer: IReportServer;
begin
Result := '';
AReportServer := CoReportServer.Create;
try
if AReportServer.Connect(ATenantID) then
begin
if not AReportServer.Print(AReportID, AFilter) then
Result := AReportServer.GetLastError;
end
else
Result := AReportServer.GetLastError;
except on E: Exception do
Result := E.Message;
end;
AReportServer := nil;
end;
值得注意的主要事情是 CoReportServer.Create 在内部执行此操作:
Result := CreateComObject(CLASS_ReportServer) as IReportServer;
反过来又执行此操作:
OleCheck(CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IUnknown, Result));
我看不到 CoCreateInstance 是什么 正在做,但我想知道是否有办法调用 CoGetInstanceFromFile
来代替?也许可以通过更改 TLB 中的 CoReportServer.Create
来使用我自己的代码而不是 CreateComObject
?
更改对 TAutoObjectFactory.Create
的调用以使用不同的 TClassInstancing
(例如 ciSingleInstance
)会有什么不同吗?
我只是希望当程序调用 CoReportServer.Create 时,它会从程序文件夹中的 EXE 创建服务器实例,而不是通过查找 InprocServer > 来自注册表。我根本不想使用注册表。
[编辑]
由于我们还有一个需要免注册表的 OCX,因此我按照本文中讨论的步骤进行操作:
我有一个用于 OCX 测试应用程序的清单文件和一个 OCX 本身的清单文件,这两个文件都与测试位于同一文件夹中应用程序和OCX。清单文件指定的 OCX 版本号与文件夹中的版本号相同。
然后,我将旧版本的 OCX 复制到另一个文件夹,运行提升的 CMD 提示符,并使用 Regsvr32 在该文件夹中注册该 OCX。
运行 OCX 测试应用程序(其中包括主窗体上的 ActiveX 控件),我可以从 Process Explorer 中看到它正在使用注册的 OCX,而不是应用程序中的 OCX。同一个文件夹。
这可能表明 Delphi EXE 程序无法处理此清单方法,即使我在项目选项中包含自定义清单也是如此。
我们混合了 COM 服务器 EXE、ActiveX 可视化控件 OCX 和 ActiveX 类型库 DLL,所有这些都用 Delphi 10.2 Tokyo 编写,我们需要使其免于注册表。
[编辑 23/03/2022]
通过参考 免注册 COM/DLL?
我现在有一个 DLL 的清单文件,其中包含
标记、
标记,其中包含我的 DLL 中每个 CLSID 的
标记列表,以及
列表标签将 CLSID 和 IID 与接口名称链接起来。
主机 EXE 有一个清单文件,其中包含相同的 < assemblyIdentity> 标记信息,但包含在
< dependentAssembly > 标记内。
使用SxSTrace,我可以看到“信息:激活上下文生成成功。”,因此至少并行配置是正确的。
然而,当运行测试程序时,在调用 CoReportServer.Create 时,它会失败,并显示“加载类型库/DLL 时出错”消息。根据堆栈跟踪,这是来自第一个类实现的 initialization
部分的 TAutoObjectFactory.Create
过程中的 LoadTypeLibrary
调用。第一个界面。
由于我查看的 SO 答案提到确保它在注册 DLL 时能够正常工作,因此我尝试从提升的命令提示符中使用 regsvr32,但得到“运行时错误 217” > 弹出窗口,然后是“模块“reportserver.dll”加载失败。动态链接库 (DLL) 初始化例程失败。”
LoadTypeLibEx
的结果是-2147312566,这似乎与 TYPE_E_CANTLOADLIBRARY
结果不匹配。
所以看起来我可能已经让免注册表的 COM 部分可以工作了,但是进程外 COM 服务器到进程内 COM 库的转换是不正确的。
所以我回到 COM 服务器 EXE,并重新注册它以确认测试程序仍然有效。确实如此。然后我更改了测试程序清单以指向 COM 服务器 EXE 而不是我的试用转换 DLL,为 COM 服务器 EXE 创建了一个新的清单文件,其中包含所有 IID 和 CLSID,并取消注册了 EXE。
测试程序报告“类未注册”,因此几乎证实了激活上下文方法仅适用于 DLL。
这让我不得不弄清楚为什么 DLL 无法加载,尽管我刚刚想到的另一个问题是 DLL 是进程内的,这意味着每个使用该库的程序都必须创建它自己的实例。这可能非常耗费资源。
[编辑 25/03/2022]
暂时跳过进程外 COM 服务器,我现在看一下我们的 ActiveX 控件,它是 TProgressBar
的后代,并使用要么在VCL表单上,要么在运行时创建。
按照与定义清单文件相同的方法,我创建了
、
和
标签,但我注意到在_TLB.pas中,有一个额外的TGUID
用于DIID_IReportControlEvents
。
查看 Microsoft 文档中的清单文件,我看不到任何对接口事件类型的引用,因此我不确定该接口将如何工作。
无论如何,清单文件不适用于 OCX。我刚刚收到“类未注册”错误。
SxStrace 文件显示“信息:激活上下文生成成功。”,但它不包含有关 OCX 清单的任何信息,因此它根本不加载它。
这可能是因为 OCX 的 _TLB.pas 显示它使用的是 OleControl 而不是 COM。从 DPR 中删除 {$E ocx}
意味着 Delphi 会创建一个 DLL,但这也会失败。
We have a COM server written in Delphi 10.2 Tokyo which contains 4 classes, all of which inherit from TAutoObject
, and all have an initialization
section containing the call to TAutoObjectFactory.Create
.
In a typical installation, we have approximately 60 other programs that make use of the classes in the COM server. This all works fine. Each of the classes has their own GUID and the Delphi COM server does its usual thing of ensuring they are all registered correctly as part of the InitComServer
process that runs from Application.Initialize
.
We now have a need to run multiple installations side-by-side, where they may be different versions. Since we don't control all of the programs, we can't produce a different version of the COM server with different GUID's for every installation.
I had found this Microsoft document:
DLL/COM Redirection on Windows
But that approach does not work. When the main program starts and creates an instance of the first COM server class, I can see that it is the registered EXE that is running, not the COM server EXE in the same folder as the main program. This might be because it's an EXE and not a DLL, and Delphi's initialization is circumventing the DLL/COM redirection.
Here's an example of using one of the COM classes:
function ProduceReport(const ATenantID, AReportID: Integer; const AFilter: String): String;
var
AReportServer: IReportServer;
begin
Result := '';
AReportServer := CoReportServer.Create;
try
if AReportServer.Connect(ATenantID) then
begin
if not AReportServer.Print(AReportID, AFilter) then
Result := AReportServer.GetLastError;
end
else
Result := AReportServer.GetLastError;
except on E: Exception do
Result := E.Message;
end;
AReportServer := nil;
end;
The main thing of note is that the CoReportServer.Create
does this internally:
Result := CreateComObject(CLASS_ReportServer) as IReportServer;
which in turn is doing this:
OleCheck(CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IUnknown, Result));
I can't see what CoCreateInstance
is doing, but I am wondering if there is a way to call CoGetInstanceFromFile
instead? Perhaps by changing the CoReportServer.Create
in the TLB to use my own code instead of the CreateComObject
?
Would changing the call to TAutoObjectFactory.Create
to use a different TClassInstancing
, such as ciSingleInstance
, make any difference?
I just want it so that when a program calls CoReportServer.Create
, it creates an instance of the server from the EXE it has in the program's folder, not by looking up the InprocServer from the Registry. I don't want the Registry to be used at all.
[Edit]
Since we also have an OCX that will need to be registry-free, I followed the steps discussed in this post:
Generate manifest files for registration-free COM
I have a manifest file for the OCX test app and a manifest file the OCX itself, both of which are in the same folder as the test app and the OCX. The manifest file specifies the same version number of the OCX as the one in the folder.
I then copied an old version of the OCX to a different folder, ran an elevated CMD prompt, and used Regsvr32 to register that OCX in that folder.
Running the OCX test app, which includes the ActiveX control on the main form, I can see from Process Explorer that it is using the registered OCX, and not the OCX in the same folder.
This may indicate that Delphi EXE programs can't handle the manifest approach to this, even if I include the custom manifest in the Project Options.
We have a mix of COM server EXE, ActiveX visual control OCX, and ActiveX type library DLL, all written in Delphi 10.2 Tokyo, that we need to make registry-free.
[Edit 23/03/2022]
I've made some progress on this by referencing the information contained in the answer for Registration-free COM/DLL?
I now have a manifest file for the DLL containing an <assemblyIdentity>
tag, a <file>
tag containing a list of <comClass>
tags for each CLSID in my DLL, and a list of <comInterfaceExternalProxyStub>
tags to link the CLSID's and IID's with the interface name.
And there is a manifest file for the host EXE, that includes the same <assemblyIdentity>
tag information, but contained within a <dependentAssembly>
tag.
Using SxSTrace, I can see "INFO: Activation Context generation succeeded.", so at least the side-by-side configuration is correct.
However, when running the test program, at the point it is calling CoReportServer.Create
, it is falling over with a "Error loading type library/DLL" message. Based on the stack trace, this is from the LoadTypeLibrary
call inside the TAutoObjectFactory.Create
process from the initialization
section of the first class implementation of the first interface.
Since the SO answer I looked at mentions making sure it will work when the DLL is registered, I tried using regsvr32 from an elevated command prompt, but get a "Runtime error 217" popup, followed by "The module "reportserver.dll" failed to load. A dynamic link library (DLL) initialization routine failed."
The Result from LoadTypeLibEx
is -2147312566, which doesn't seem to match the TYPE_E_CANTLOADLIBRARY
result.
So it looks like I may have got the registry-free COM part to work, but the conversion of the out-of-process COM server to an in-process COM library isn't right.
So I went back to the COM server EXE, and re-registered that to confirm the test program still works. It did. Then I altered the test program manifest to point to the COM server EXE instead of my trial conversion DLL, made a new manifest file for the COM server EXE, containing all the IID's and CLSID's, and un-registered the EXE.
The test program reports "Class not registered", so that pretty much confirms the activation context approach only works on DLL's.
That leaves me with figuring out why the DLL will not load, although another issue I just thought of is that the DLL is in-process, meaning every program that uses the library has to create it's own instance. That could be very resource intensive.
[Edit 25/03/2022]
Skipping the out-of-process COM server for the moment, I've now taken a look at our ActiveX Control, which is a descendent of a TProgressBar
and is used either on a VCL form or created at runtime.
Following the same approach for defining the manifest file, I've created the <assemblyIdentify>
, <file>
and <comInterfaceExternalProxyStub>
tags, but I noticed that in the _TLB.pas, there is one extra TGUID
for DIID_IReportControlEvents
.
Reviewing the Microsoft documentation for manifest files, I cannot see any reference to an events type of interface, so I'm not sure how that one will work.
In any case, a manifest file doesn't work for an OCX. I just get the "Class not registered" error.
The SxStrace file shows "INFO: Activation Context generation succeeded." but it doesn't include any information about the manifest for the OCX, so it is not loading it at all.
This is probably because the _TLB.pas for the OCX shows it is using OleControl rather than COM. Removing the {$E ocx}
from the DPR means Delphi creates a DLL instead, but that also fails.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论