我不知道是否有一个API使这成为可能,或者我是否必须自己动手。这就是我想要完成的。
我有一个应用程序,它连接到一个NT服务,以启动与另一个COM服务器的会话。
- 应用程序,客户端
- Broker NT Service;(系统帐号上下文).
- Session COM服务;(系统帐户上下文,将根据需要模拟用户)。
会话服务器将为连接到NT服务的每个应用程序实例提供一个正在运行的实例。应用程序可以请求会话服务器加载COM库dll,并从会话服务器中的这些dll中加载主机对象和服务。dll通过免注册激活进行注册。
从会话服务器创建对象并将其传递回应用程序,只要它们是IDispatch派生的,这是整个系统的要求,因为预计脚本语言可能会使用它,这是所请求的接口。c++应用程序也可以使用托管在会话服务器中的对象。但是在c++中处理IDispatch是一个过于冗长的接口。
我的问题是:
如果托管的dll具有应用程序不知道的双自定义接口,并且应用程序可以通过ITypeInfo读取这些接口的类型信息;是否有一个API,在运行时将创建一个代理来模仿原始的自定义接口,如果我可以提供IDispatch接口,它也携带ITypeInfo信息。代理所需要的只是调用IDispatch接口,但在c++中表现为自定义接口。更优的解决方案是使用dll在其清单中注册的相同代理,即默认的OLE Automation代理。
我不能为dll注册代理/存根,因为多个应用程序可能有相同的模块,但版本不同,因此使用免注册激活。
类型库中描述的任何[oleautomation]
接口(和任何[dual]
接口)都可以使用类型库封送器。
在这里,您将查找代理存根DLL的麻烦换成了查找类型库的麻烦。因此,您在程序集的清单(直接在assembly
元素下)中声明接口和类型库,如下所示:
<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>
如果您的接口不是[oleautomation]
接口,并且您想要隔离代理存根DLL,您将使用如下内容:
<file name="FooBarPS.dll">
<comInterfaceProxyStub name="IFooBar"
iid="{IIIIIIII-IIII-IIII-IIII-IIIIIIIIIIII}"
proxyStubClsid32="{PPPPPPPP-PPPP-PPPP-PPPP-PPPPPPPPPPPP}"
threadingModel="Both" />
</file>
comInterfaceProxyStub
与comClass
非常相似,但侧重于代理/存根,并且与接口相关联。
您可以使用一对comInterfaceExternalProxyStub
(在assembly
元素级别下)和comClass
(在file
元素级别下)实现相同的效果,如果您想测试是否有隔离的代理/存根DLL,通过(un)注释代理/存根file
部分:
<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>
我不确定,但是如果你的标准代理/存根DLL用于多个接口,你也必须使用这种方法。
编辑:这对你来说似乎并不新鲜。您的问题是,在会话服务中,即使激活了动态加载的库的清单,该状态也只保留在当前线程中。因此,COM工作线程(例如RPC线程)将不知道你的接口,协类和代理/存根。
您在代理服务(REGDB_E_IIDNOTREG
)中看到的错误可能源于从会话服务发回的封送处理。你不会在会话服务中得到这个错误,因为它发生在之后你的方法返回。
然而,它可能发生在代理服务中,因为它是自然的:它不加载任何库,更不用说它们的清单了。
我建议您采取的方法是使会话服务和代理服务具有依赖于您声明免注册COM信息的程序集的清单。这样,它将成为默认激活上下文的一部分,您不必做任何关于激活上下文的事情。
记住,你无法控制一个不属于你的线程的激活上下文,除非让默认的激活上下文包含你需要的内容。
关于你问题的这一部分:
我不明白你想说什么。如果您的模块是向后兼容的,则无需担心。如果不是,请使用不同的clsid/progid。我不能为dll注册代理/存根,因为多个应用程序可能有相同的模块,但版本不同,因此使用免注册激活。
我希望你的意思不是说你使用了相同的id和不同的接口,因为这违反了COM。解决这个问题的最好方法就是不去做。另一种方法是在会话服务和代理服务中使用具有专用激活上下文的专用线程,正如您可能只在会话服务中看到的,这是一种非常脆弱的方法。
在我看来,您可能根本不需要COM隔离。但是,如果您仍然需要它,则需要对服务、代理和会话都执行此操作,并从它们的清单中执行,而不是在运行时执行。
一种选择是放弃硬类型的双接口,并通过硬类型的编译时生成的智能指针包装器使用IDispatch
-only disinterfaces。您可以使用vc++ #import
与raw_dispinterfaces
和/或no_dual_interfaces
选项来生成这些。
COM封送器不需要类型库来封送IDispatch::Invoke
调用。但是,您需要根据将要并行运行的类型库/DLL的版本进行编译。或者至少确保在所有版本的COM DLL中,disid和方法签名保持相同。另外,生成的智能指针不使用IDispatch::GetIdsOfNames
,所以disid是硬编码的。
与直接双接口调用相比,IDispatch::Invoke
性能可能是次可选的,但我认为这并不重要,考虑到您所描述的进程间调用场景。封送的进程外COM调用比进程内IDispatch::Invoke
调用要昂贵得多。