1 year ago
#273195
SiBrit
How to have multiple versions of a Delphi COM Server at the same time?
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.
delphi
com
regfreecom
0 Answers
Your Answer