简体   繁体   中英

Using a COM object from an executable registration-free way

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;

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. ie, a single fixed manifest file (or embedded resource, whichever) would always work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM