5

I'm struggling trying to modify an existing set of program that uses a COM object instance for communication.

The COM server is actually and (optional) extension for the main application. Both are deployed to target systems by simple copy.

Currently, both applications are deployed only locally to our offices (although the main application, the COM client, is deployed more widely at our customer's) and we manually register the server. We need to redesign that for a deployment on a cloud service and therefore, I'm looking into registration-free COM.

So far, I have tried:

  • Writing manifests for the client and the server. Unfortunately, we cannot deploy this solution since the client is then strongly linked with the server app and cannot be deployed separately (which is the case at our customers).
  • Creating a new activation context and reading the server's manifest from the resource. This works but attempting to instantiate the object results in a "Error in the Dll" OLE exception. Some googling tells me this is because DllgetClassObject export is missing.
  • Exporting the DllGetClassObject from the executable (simply adding the export clause to the project source, using the built-in implementation from System.Win.ComServ. This results in an Access Violation when called (either directly or through the activation context). I have not been able to figure out where the AV occurs .

here is the manifests I used (I left the various attempts in the code in form of comments):

Server application manifest

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
name="wgaticket.exe"
type="Win32"
version="1.0.0.0"
/>

<file name = "wgaticket.exe">

<comClass
    clsid="{E33A1F59-CEA2-463E-97B2-1CCDA66DA984}"
    />
<!-- comClass
    clsid="{E33A1F59-CEA2-463E-97B2-1CCDA66DA984}"
    threadingModel = "Apartment"
    /-->

<typelib tlbid="{414AE7FB-3025-40D8-B14C-2A29B6E42C29}"
       version="1.0" helpdir=""/>

</file>

<!--comInterfaceExternalProxyStub
    name="INewTicket"
    iid="{740BF585-3246-483E-9146-B6A8E49400B5}"
    proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
    baseInterface="{00000000-0000-0000-C000-000000000046}"
    tlbid = "{414AE7FB-3025-40D8-B14C-2A29B6E42C29}" /-->

  <dependency>
    <dependentAssembly>
      <assemblyIdentity
        type="win32"
        name="Microsoft.Windows.Common-Controls"
        version="6.0.0.0"
        publicKeyToken="6595b64144ccf1df"
        language="*"
        processorArchitecture="*"/>
    </dependentAssembly>
  </dependency>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel
          level="asInvoker"
          uiAccess="false"/>
        </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

The client code is a bit more complex because I'm using the Spring4D framework but here are the main elements:

Client activation context creation

function getActivationContext: IActivationContext;
var
  actCtx: TActCtx;
begin
  result := TActivationContext.Create;
  zeroMemory(@actCtx, SizeOf(actCtx));
  actCtx.cbSize := SizeOf(actCtx);
  actCtx.lpSource := 'wgaticket.exe';

  actCtx.lpResourceName := MakeIntResource(1);
  actCtx.lpAssemblyDirectory := PChar(ModulePath);
  actCtx.dwFlags := ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID or ACTCTX_FLAG_RESOURCE_NAME_VALID;
  result.Handle := CreateActCtx(actCtx);
  if result.Handle = INVALID_HANDLE_VALUE then
  begin
    RaiseLastOSError;
  end;
  result.Cookie := 0;
end;

Client activation context activation

procedure TActivationContext.Activate;
begin
  CriticalSection.Enter;
  try
    if (FHandle <> INVALID_HANDLE_VALUE) and (Cookie = 0) then
    begin
      if not ActivateActCtx(FHandle, FCookie) then
        RaiseLastOSError;
    end;
  finally
    CriticalSection.Leave;
  end;

end;

New instance creation in client

class function CoNewTicket.CreateAsClient: INewTicket;
begin
  // GActContext is a global, lazy interface variable. It will be auto-created the first time GActContext.Value is referenced
  GActContext.Value.Activate;
  result := CreateComObject(CLASS_NewTicket) as INewTicket;
end;
Stephane
  • 3,173
  • 3
  • 29
  • 42
  • 1
    Two fundamental problems. The manifest entries are required to help the client start the server. They must therefore be embedded in the client exe, not the server. Looks like you already figured this out or you wouldn't have run into the next problem, the show-stopper. It only works for in-process servers. DLLs. Which is why it looked like you had to export DllGetClassObject() But an EXE cannot be loaded like a DLL. Everything is ten times harder with an out-of-process server, you have to use the registry. – Hans Passant Jul 26 '16 at 10:43
  • Thank you for your comment. As you've noticed, I can't place the registration in the client as it wouldn't run anymore if the server isn't in the same folder. nevertheless, even if forgoes the creation of an activation contest and uses just manifests, the problem remains the same: SxS attempts to call the (non-existant) dllgetClassobject export in my module. – Stephane Jul 26 '16 at 15:26
  • Hmm, last time I tried, I didn't need an activation context. I wrote about it [here](http://yoy.be/DllGetClassObject.html). – Stijn Sanders Jul 27 '16 at 19:33
  • I tried to get this working some time ago, using [this](https://social.msdn.microsoft.com/Forums/vstudio/en-US/3849f22d-c8b8-429e-a03a-37601411acec/registrationfree-exe-com-server-?forum=vcgeneral) link, unfortunately to no avail. If there really is a possibility to create an instance from a non-registered out-of-process COM server, I'd be interested in a solution as well. – Aurora Jul 27 '16 at 20:19
  • @StijnSanders I'm afraid you've misunderstood my question. With in-process COM, it is easy (although what you're doing might not be safe depending on the threading model). Out-of-process servers do not expose a DllgetClassObject entry point, as I explained in my question. – Stephane Jul 28 '16 at 07:42

1 Answers1

0

You could create a "dummy" version of your COM extension which has the same public interface as the real extension, but no real functionality. Add to that interface a property you can check to see if it is the real one or not. Always deploy the dummy. Replace the dummy with the real extension whenever that is needed.

This would seem to decouple your deployment strategy from how the activation context is established at runtime. i.e., a single fixed manifest file (or embedded resource, whichever) would always work.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
  • That would be a solution if the problem was that I couldn't create an activation context. Unfortunately, even if I use manifests, I cannot find any way to create an instance of an out-of-process COM object without registering it. – Stephane Jul 29 '16 at 14:00
  • In the question you stated that you tried manifests but could not use them because it "strongly linked" the client and server. My suggestion is a way to take that (relatively simple / non programmatic) approach. If in that approach the manifest is not working, you should debug the manifest. Note the accepted answer to http://stackoverflow.com/questions/741726/diagnosing-windows-application-manifests for instance. – StayOnTarget Jul 29 '16 at 15:42