1

I don't know if there is an API that makes this possible or if I would have to roll my own. Here is what I'm trying to accomplish.

I have an application that connects to an NT service to start a session with another COM server.

  • Application, the client.
  • Broker NT Service; (system account context).
  • Session COM Service; (system account context, will impersonate user as needed).

The session server will have a running instance for every application instance that connects to the NT service. The application can request that the session server load COM library DLLs and host objects and services from those DLLs in the session server. The DLLs register via registration-free activation.

Creating objects from the session server and passing them back to the application works fine as long as they are IDispatch derived, which is a requirement of entire system since it is expected that scripting languages may use this, and that is the interface requested. C++ application may also use objects hosted in the session server. But IDispatch is an overly verbose interface to deal with in C++.

My question is this:

Given that the DLLs being hosted have dual custom interfaces that the application does know about, and type information about those interface can be read by the application via ITypeInfo; Is there an API that at runtime will create a proxy to mimic the original custom interface if I can provide it the IDispatch interface, which also carries the ITypeInfo information. All the proxy needs is call the IDispatch interface, but appear to C++ as the custom interface. A more optimal solution would be to use the same proxy, the default OLE Automation proxy, that the DLLs registered in its manifest.

I cannot register the proxy/stubs for the DLLs since multiple application may have the same modules, but differing in version, hence the use of registration-free activation.

Matthew
  • 681
  • 6
  • 14
  • 1
    I'm not sure if there's way to use dual interfaces directly under this particular reg-free scenario. But you can automatically generate **strong-typed** `IDispatch` wrappers via [`#import`](https://msdn.microsoft.com/en-us/library/298h7faa.aspx) with `raw_dispinterfaces` and/or `no_dual_interfaces` options, to be used from the client side. – noseratio Aug 01 '15 at 01:18
  • Interesting idea, I'll give it a try. – Matthew Aug 03 '15 at 13:39
  • 1
    @Noseratio; This does exactly what I wanted, though not the most optimal as I knew asking about it. It still produces a perfect mock over the IDispatch interface as needed. Can you please post a proper answer with examples though so I can mark this as answered? Also include the fact that a normal C/C++ cast must be used to switch it from IDispatch to the mock interface since QueryInterface will not work. – Matthew Aug 03 '15 at 16:54

2 Answers2

2

Any [oleautomation] interface (and any [dual] interface) described in a type library can use the type library marshaler.

Here, you trade the trouble of finding a proxy-stub DLL to that of finding a type library. So, you declare the interface and the type library in your assembly's manifest (directly under the assembly element) like this:

<comInterfaceExternalProxyStub name="IFooBar"
                               iid="{IIIIIIII-IIII-IIII-IIII-IIIIIIIIIIII}"
                               proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
                               tlbid="{TTTTTTTT-TTTT-TTTT-TTTT-TTTTTTTTTTTT}" />

<!-- This also works for a type library embedded in a DLL -->
<file name="FooBar.tlb">
    <!-- If you have multiple embedded type libraries, use the resourceid attribute -->
    <typelib tlbid="{TTTTTTTT-TTTT-TTTT-TTTT-TTTTTTTTTTTT}"
             version="1.0" />
</file>

If your interface were not an [oleautomation] interface, and you'd want to isolate the proxy-stub DLL, you'd use something like this:

<file name="FooBarPS.dll">
    <comInterfaceProxyStub name="IFooBar"
                           iid="{IIIIIIII-IIII-IIII-IIII-IIIIIIIIIIII}"
                           proxyStubClsid32="{PPPPPPPP-PPPP-PPPP-PPPP-PPPPPPPPPPPP}"
                           threadingModel="Both" />
</file>

A comInterfaceProxyStub is much like a comClass, but focused on proxy/stubs and it gets associated with an interface.

You can accomplish the same effect with a pair of comInterfaceExternalProxyStub (under an assembly element level) and comClass (under a file element), in case you want to test with and without an isolated proxy/stub DLL, by (un)commenting the proxy/stub file section:

<comInterfaceExternalProxyStub name="IFooBar"
                               iid="{IIIIIIII-IIII-IIII-IIII-IIIIIIIIIIII}"
                               proxyStubClsid32="{PPPPPPPP-PPPP-PPPP-PPPP-PPPPPPPPPPPP}" />

<file name="FooBarPS.dll">
    <comClass description="PSFactoryBuffer"
              clsid="{PPPPPPPP-PPPP-PPPP-PPPP-PPPPPPPPPPPP}"
              threadingModel="Both" />
</file>

I'm not sure, but if your standard proxy/stub DLL is used for more than one interface, you must use this approach as well.


EDIT: It seems none of this is new to you. Your problem is that in the session service, even though you activate the dynamically loaded libraries' manifest, that state remains only in the current thread. So, COM worker threads (e.g. RPC threads) will not know about your interfaces, coclasses and proxy/stubs.

The error you see in the broker service (REGDB_E_IIDNOTREG) might originate in the marshaling back from the session service. You don't get that error in the session service because it happens after your methods return.

However, it might be happening in the broker service, as it's natural: it doesn't load any libraries, much less their manifests.

The approach I suggest you take is to make the session service and the broker service have manifests that depend on the assemblies where you declare the registration-free COM information. This way, it'll all be part of the default activation context and you don't have to do anything regarding activation contexts.

Remember, you have no control over the activation context of a thread you don't own, other than having the default activation context include what you need upfront.

Regarding this part of your question:

I cannot register the proxy/stubs for the DLLs since multiple application may have the same modules, but differing in version, hence the use of registration-free activation.

It's not clear to me what you're trying to say. If your modules are backwards compatible, you don't need to worry. If they're not, use different CLSIDs/ProgIDs.

I hope you don't mean you're using the same IIDs with actually different interfaces, as that is a violation of COM. The best way to solve this problem is to not do it, period. Another way is to have dedicated threads with dedicated activation contexts, both in the session service and in the broker service, which as you've probably seen with only the session service, this is a very brittle approach.

As I see it, you may have no need for COM isolation at all. But if you still want it, you need to do it for both services, broker and session, and do it from their manifests, instead of at runtime.

acelent
  • 7,439
  • 17
  • 38
  • All interface are [oleautomation] marked and compliant. All of this information is in the manifest of the DLLs being hosted in the session server. I've even tried loading the manifest of that DLL in the application too, which does not fail, and activating it before calling out to the broker service to have the DLL registered to the session server. I also leave the manifest activated when asking the session service to create an object from that DLL. The result when querying the custom interface is still E_NOINTERFACE. – Matthew Aug 03 '15 at 16:28
  • I should note that all calls to register DLLs and create objects still have to pass through the broker service. – Matthew Aug 03 '15 at 16:31
  • COM may use worker threads on which you have no control over their current activation contexts, which most probably is the default activation context. Your session service's default activation context only contains information from its own manifest, if there's one, it will not contain the manifests of the dynamically loaded libraries. To solve this, you could refer to your assemblies in the session service's manifest, or your DLLs could use `CoRegisterPSClsid` (check with `CoGetPSClsid` if you need to do so). Registration-free should really be **global**-registration-free. – acelent Aug 03 '15 at 18:15
  • `CoRegisterPSClsid`, I've tried that and it doesn't take affect. Probably becuase the broker service has no idea what interface is being passed through when it get the object from the session server and passed it to the application. – Matthew Aug 04 '15 at 13:33
  • Then, the broker service too needs COM isolation or proxy/stubs registered at runtime. Actually, I don't see a need for isolation. You can have modules with different versions using the same interfaces. One of COM's rules is that interfaces don't change, and if you violate that, you can't reliably solve your problem, with or without COM isolation, e.g. what happens when the session service loads conflicting module versions, which use the same IIDs with actually different interfaces? – acelent Aug 04 '15 at 13:52
  • Alright, I have tried activating the Activation Context from DLL in the broker service just like is done in the session server. Added `CoRegisterPSClsid` calls to the session server, broker service, and application. The register call is within the activated ActCtx for the DLL in both the broker and session. The session server has no issues with the registration or creating the objects, but the broker is getting a REGDB_E_IIDNOTREG when the call to the session server from the broker returns. Which is also within the activated ActCtx for the DLL. – Matthew Aug 04 '15 at 15:29
  • That error might actually originate in the session server, when trying to marshal the interface pointer back to the broker service. I'd try COM isolation instead of `CoRegisterPSClsid`, to have the interfaces registered in the default activation context. Remember that with `ISOLATION_AWARE_ENABLED`, your DLL's activation context will be temporarily setup only around several Win32 functions, so it won't work properly if you need the activation context to be (or remain) active in threads you don't control. The same goes for manual activation context switching. – acelent Aug 04 '15 at 17:12
  • So, COM worker threads don't know where your proxy/stub is, as your activation context is not active. – acelent Aug 04 '15 at 17:12
  • The only way I can see fixing this is to pollute the Activation Context stack and leave them active when leaving a call in a COM object. This doesn't sit well with me. Unless some other solution can help deal with these dynamically loaded DLLs, I think the solution @Noseratio proposed meets my requirements. – Matthew Aug 04 '15 at 19:51
  • 1
    What I just told you is that leaving activation contexts in the stack doesn't work beyond the current thread. So, either having the proxy/stubs declared in the dependencies of the services' manifests or using a globally registered interface (e.g. the direct interfaces, `IDispatch`) will work. The `IDispatch` approach side-steps the need for registering proxy/stubs, even for `[oleautomation]` interfaces, at the cost of performance, but I think you're not expecting a great load of calls anyway, since you make it consumable for scripts. – acelent Aug 05 '15 at 00:05
  • The `IDispatch` approach will work, but it seems you haven't tried having the P/Ss in the startup manifest(s), or that you haven't stated clearly what you mean by "same modules, but differing in version". – acelent Aug 05 '15 at 00:08
  • From the start I've had all the COM registration information in a manifest for the DLLs and have created and activated Activation Contexts from that manifest in the application, broker service, and session server. While the activated, everything works fine within the session server process, but the broker service just returns REGDB_E_IIDNOTREG when a call is returned to it with an object. :( – Matthew Aug 05 '15 at 13:23
  • Also, all objects that the session service creates must be `[dual]` interfaces, which implies `[oleautomation]`. But, as far as registration goes, I need to support global system Registry based registration and Registration-Free Activation registration. If the reg-free activation has this limitation that requires using the type library to generate a IDispatch based proxy interface, then so be it. That's not big deal. I've tested with system-wide registration, and everything does work as expect. My question only pertains to Registration-Free Activation. – Matthew Aug 05 '15 at 13:38
1

One option is to give up on the hard-typed dual interface and use IDispatch-only dispinterfaces via hard-typed, compile-time generated smart pointer wrappers. You'd use VC++ #import with raw_dispinterfaces and/or no_dual_interfaces options to generate those.

The COM marshaler doesn't need a type library to marshal IDispatch::Invoke calls. You'd however need to compile against the version of the type library/DLL you're going to run side-by-side. Or at least make sure the DISPIDs and method signatures remain the same across all versions of the COM DLL. AFAIR, the generated smart pointers don't use IDispatch::GetIdsOfNames, so DISPIDs are hard-coded.

The IDispatch::Invoke performance might be sub-optional compared to direct dual interface calls, but I don't think it matters, giving the inter-process call scenario you've described. A marshaled out-of-proc COM call is much more expensive than a in-process IDispatch::Invoke call.

noseratio
  • 56,401
  • 21
  • 172
  • 421
  • 1
    Direct calls to the original dual interface is actually pretty optimal due to Windows using Local Procedure Call (LPC) for out-of-process COM servers. Though not as much as a in-proc free-threaded COM object, this solution seems to have the same performance characteristics of using IDispatch in-proc, with the added time for a context switch of the LPC call. – Matthew Aug 06 '15 at 20:15