16
votes

TL;DR Do all registry entries produced by regsvr32 need to be present in a SxS reg-free-COM manifest and vice versa?


I'm trying to get registration free COM going for a third party component.

Reading up on the subject, I find that there are several elements mentioned, that can be put into a manifest:

From the docs, we can add the following tags to a manifest to describe a COM component:

  • assemblyIdentity - which really just describes the "abstract assembly" as far as I can tell
  • comClass - describes the COM class (IID Interface). It would appear, this is always needed.
  • typelib - when?
  • comInterfaceExternalProxyStub - when?
  • comInterfaceProxyStub - when?

From the other docs for HKEY_LOCAL_MACHINE\SOFTWARE\Classes we can observe that there are a few categories for the COM registry entries:

Using regsvr42 to extract the stuff the dll I'm trying to regfree yields a manifest that only contains comClass entries, no typelib or ProxyStub entries. (And I cross checked the keys written, the DLL in question, pdm.dll, MS's Process Debug Manager only writes those keys, that is, there is no type library or proxy stub info apparent in the registry.)

If the registry only contains the info that pertains to comClass does this then mean that this info will be sufficient in the SxS manifest, or may additional info be needed in the manifest?


As an aside I noticed that the registry contains a VersionIndependentProgId and a ProgId that has a version number appended at the end. The manifest only has a ProgId entry, and the docs state:

progid : Version-dependent programmatic identifier associated with the COM component. The format of a ProgID is <vendor>.<component>.<version>.

But the docs also state

The comClass element can have <progid>...</progid> elements as children, which list the version dependent progids.

and they say that the progid attribute should be the version independent one.

So, what to put here? And does it even matter when the client doesn't request a specific version?

1
PDM won't have a type library, it doesn't have IDispatch interfaces and you must use CoCreateInstance(). And it quacks like a free-threaded in-process component so doesn't need a proxy/stub. What actually goes wrong here?Hans Passant
@Hans, thanks. Nothing goes wrong as such, but I wasn't sure if there were any potential pitfalls. (COM always makes me feel like I'm missing something.) If you could help unravel my ramblings above into two or three actually useful questions I'd appreciate that. Otherwise I'm happy with that you seem to think everything is i order :-)Martin Ba

1 Answers

16
votes

The assemblyIdentity element is always required, part of the manifest plumbing. You must always provide the comClass element, it substitutes the HKLM\Software\Classes\CLSID registry key and is used to make the client's CoCreateInstance() call work. The file element names the COM server executable file.

The rest of the keys are optional, they are needed to make marshaling work. Marshaling occurs when the client call needs to be made on a different thread. That will always happen when the server and the client are in different processes, the case for an out-of-process server or when the server runs on another machine. And it can happen when the ThreadingModel specified in the comClass element demands it. In other words, when the COM object was created on one thread but is called on another and the server is not thread-safe.

RPC implements the marshaling but it has one job to do that it needs help with. It needs to know what the arguments for the function are, as well as the return type. So that it can properly serialize their values into a data packet that can be transmitted across a network or passed to the code in another thread that makes the call. This is the job of the proxy. The stub runs at the receiving end and deserializes the arguments to build the stack frame and makes the call. The function return value as well as any argument values passed by reference then travel back to the caller. The code that makes the call otherwise has no awareness at all that it didn't call the function directly.

There are four basic cases:

  • The COM server doesn't support being called that way at all and must always be used from the same thread it was created on. Stop there, no need to add anything to the manifest.

  • The COM server implements the IMarshal interface. Automatically queried by COM when it cannot find another way to marshal the call. This is quite rare, except for a case where the COM server aggregates the free-threaded marshaller. In other words, is completely thread-safe by itself without needing any help and always runs in-process. PDM is likely to work that way. Stop there, no need to add anything to the manifest.

  • The COM server author started his project by writing the interface description of the server in the IDL language. Which was then compiled by MIDL. One option it has available is to auto-generate code from the IDL declarations, code that can be used to build a separate DLL that implements the proxy and the stub. IDL is sufficiently rich to describe details of the function argument types and usage to allow the marshaling to be done by this auto-generated code. Sometimes IDL attributes are not sufficient, the COM author then writes a custom marshaller. COM loads that DLL at runtime to create the proxy and stub objects automatically.

  • Specific to the COM Automation subset (IDispatch interface), Windows has a built-in marshaller that knows how to marshal calls that meet the subset requirements. Very common. It uses the type library to discover the function declaration.

The latter two bullets require using HKLM\Software\Classes\Interface, it has entries for the IID for every interface. That's how COM finds out how to create the proxy and the stub for the interface. If it cannot find the key then it falls back to IMarshal. You must use the comInterfaceExternalProxyStub element to substitute the registry key. Using comInterfaceProxyStub is a special case, that's when the proxy and stub code is included with the COM server executable instead of being a separate file. An option in ATL projects for example, turned on with the "Allow merging of proxy/stub" wizard selection.

The last bullet also requires using the typelib element, required so the built-in marshaller can find the type library it needs.

The progId is required when the COM client uses late binding through IDispatch, the CreateObject() helper function in the client's runtime support library is boilerplate. Used in any scripting host for example.

Having some insider knowledge of how the COM server was created certainly helps, always contact the vendor or author for advice. It can be reverse-engineered however by observing what registry keys are written when the server is registered, SysInternals' ProcMon tool is the best way to see that. Basic things to look for:

  • If you see it write the HKLM\Software\Classes\Interface key then you can assume that you must provide the comInterface|External|ProxyStub element

  • If you see it write {00020420-0000-0000-C000-000000000046} for the ProxyStubClsid32 key then you can assume it is using the standard marshaller and you must use comInterfaceExternalProxyStub element as well as the typelib element. You should then also see it write the IID's TypeLib registry key as well as the entry in the HKLM\Software\Classes\Typelib registry key. The latter gives the path of the type library. Almost always the same as the COM server, embedding the type library as a resource is very common. If it is separate (a .tlb file) then you must deploy it.

  • If the ProxyStubClsid32 key value is another guid then you can assume it is uses its own proxy/stub DLL. You should then also see it write the CLSID key for the proxy, its InProcServer32 key gives you the path to the DLL. If that file name matches the server's file name then you can assume that the proxy/stub code was merged and you must use the comInterfaceProxyStub element instead. If not then comInterfaceExternalProxyStub is required and you must deploy the DLL

  • If you see it write the ProgID in HKLM\Software\Classes then use the progid element, exactly as shown in the trace.