如何拦截进程外桌面 STA COM 到 STA COM 通信或引用计数递减?

Posted

技术标签:

【中文标题】如何拦截进程外桌面 STA COM 到 STA COM 通信或引用计数递减?【英文标题】:How to intercept out-of-proc desktop STA COM to STA COM communication or reference counting decrementing? 【发布时间】:2012-03-21 08:57:10 【问题描述】:

我的例子: PowerPoint 在插入嵌入工作簿时启动 Excel。 我继续使用 PowerPoint。 Excel 保持隐藏进程。 当我关闭 PowerPoint 时,它会告诉 Excel 自行关闭(Excel 稍后关闭 PowerPoint)。 当我杀死 PowerPoint 时,Excel 变成了僵尸。 我的加载项已加载到 Excel 进程中。

什么消息(我读到 COM STA 对象通过 Windows 消息传递)我应该拦截以收到有关此类事件的通知?我应该使用什么钩子?有什么方法可以通知对 COM 对象更改的引用计数?

更新

我的问题似乎模棱两可。当我杀死 PowerPoint 时,我不想处理案例。它只是为了证明 PowerPoint 在正常关闭时会向 Excel 发送一些消息。我试图拦截这条消息是有充分理由的。

更新2

我无法使用 API - 因为 API 失败。我正在尝试解决Creating class in new thread in new app domain and providing it with Office COM object in embedded mode leads to zombie process

【问题讨论】:

进程外 COM 中没有很好的机制来处理意外的进程终止。捕获硬崩溃并使用 RPC_E_SERVERFAULT 错误代码向客户端报告。服务器保持运行。如果你自己朝头部开枪,那么你的工作就是收拾烂摊子。 “我试图拦截这条消息是有充分理由的。”没有充分的理由这样做。你实际上想做什么 我添加了 UPDATE2 来描述我的问题。 我查看了您的问题,我怀疑问题在于您从未释放 Excel 应用程序对象。当你完成一个对象时,你需要调用 Marshal.ReleaseComObject - 记住 GC 不是确定性的,并且由于某种原因,Interop 对象不支持 using/IDisposable,所以你甚至不能使用它。 另外,为什么要在不同的线程上使用 Excel 应用程序对象?你想做什么让你认为这是一个解决方案? 【参考方案1】:

如果您终止“PowerPoint”,则 Excel 中的引用计数不会发生变化。这就是为什么它没有被卸载。

我相信您可以简单地将 WM_QUIT 消息发布到 Excel 进程的主窗口。您可以使用“Spy++.exe”找到它,这是一个与 Visual Studio 捆绑在一起的工具。此外,您可能会在 Excel 中获取某个对象的类工厂并使用 fLock = FALSE 调用“LockServer”方法。这将减少 Excel 服务器的引用计数。

但请将此视为 hack,因为加载项不应影响主机应用程序的行为。

【讨论】:

我认为使用一些本机 API 过早地“发布”Excel 可能很酷。我会尝试 IClassFactory。【参考方案2】:

无。 本地服务器上的 COM 确实使用 windows 消息,但它使用私有消息,这是一个实现细节。它真正做的只是将消息发布到一个隐藏的窗口,意思是“你的公寓里有新的 COM 事件”。隐藏窗口将此传递给 COM 库,该库使用(我相信)共享内存与其他进程通信,并完成其余工作,例如找出事件是什么(调用、调用返回等)。

换句话说,没有具体的信息说“excel go away”,即使有你也不能依赖它,因为它是一个可以改变的实现细节。

当您关闭包含嵌入电子表格的 PowerPoint 文档时,PowerPoint 将对它保存的所有对象调用 IUnknown::Release()。 Excel 会在内部跟踪未完成的对象,并在它们全部消失时自行关闭。

如果你在excel进程中有加载项,想知道excel什么时候关闭,使用加载项api来了解。

根据加载项的类型,有不同的 API。 COM 加载项具有您可以注册的事件。但这是一个不同的问题:“我的 Excel 加载项如何获得通知,告知不可见的 Excel 实例正在关闭”。

为未提出的实际问题/问题添加了解决方案

@asd.and.Rizzo,这是因为您坚持使用 Excel 创建的对象。这意味着 Excel 认为它无法退出,因为有人正在使用这些对象。 Excel 是正确的,因为该 somone 是您的加载项。

在创建任何 Office 或 Excel 加载项时,这种生命周期问题很常见,并不是 .Net 加载项所独有的。

您想通过欺骗 Excel 的引用计数来解决问题。但它在你手中:引用计数只是因为你持有对象而增加!如果您释放它们,计数将下降到零而不会作弊。

解决方案

最好的解决方案是避免下沉事件并避免抓住任何物体。根据您的外接程序执行的操作,这可能是不可能的。

如果不能,则必须接收文档关闭事件。在每个中,检查打开了多少文档。当计数达到零时,您应该删除您的事件处理程序,对您曾经持有的每个对象调用 Marshall.ReleaseComObject,然后卸载您的加载项。 Excel 将退出。

但请注意:您必须对您曾经持有的每个对象调用 Marshall.ReleaseComObject。如果不这样做,Excel 将永远不会退出。如果您卸载 AppDomain,这应该会发生。

【讨论】:

有什么方法可以拦截在 Excel 加载项中与 Excel 相关的 IUnknown::Release() 调用? @asd.and.Rizzo,你在问最好的方法来切断你的脚趾以适合鞋子。 不要那样做。你需要一双不同的鞋子。你为什么不告诉我们你真正想要解决的问题是什么?我们中的一些人已经编写了 Excel 加载项并且知道一点 COM,所以也许可以提供帮助。 我在这个论坛上删除了我以前的帖子没有答案。【参考方案3】:

接下来我发现了(从将 COM 接口解释为纯 C 结构 + 函数和 Direct X hacks)。工作,但由于某种原因不能解决问题。我应该对工作簿、工作表做同样的事情......

声明:


typedef HRESULT STDMETHODCALLTYPE QueryInterfacePtr(  REFIID, void **);
typedef ULONG STDMETHODCALLTYPE AddRefPtr( );
typedef ULONG STDMETHODCALLTYPE ReleasePtr( );

 typedef ULONG STDMETHODCALLTYPE GetTypeInfoCountPtr( UINT *) ;
 typedef  ULONG STDMETHODCALLTYPE GetTypeInfoPtr     ( UINT, LCID, ITypeInfo **) ;
  typedef ULONG STDMETHODCALLTYPE GetIDsOfNamesPtr   ( REFIID, LPOLESTR *, 
                                        UINT, LCID, DISPID *) ;
  typedef ULONG STDMETHODCALLTYPE InvokePtr          ( DISPID, REFIID, 
                                        LCID, WORD, DISPPARAMS *,
                                        VARIANT *, EXCEPINFO *, UINT *) ;

typedef struct 
       // IUnknown functions
    QueryInterfacePtr  * QueryInterface;
    AddRefPtr          * AddRef;
    ReleasePtr         * Release;
       // IDispatch functions
 GetTypeInfoCountPtr* GetTypeInfoCount;
 GetTypeInfoPtr* GetTypeInfo;
 GetIDsOfNamesPtr* GetIDsOfNames;
 InvokePtr*  Invoke;


 IDispatchInterceptor;



typedef ULONG __stdcall releasePTR(IDispatch *self);
typedef ULONG __stdcall addrefPTR(IDispatch *self);

接下来我为 Excel 做的:

static IDispatch * application = NULL;
static releasePTR* realRelease = NULL;
static addrefPTR* realAddRef = NULL;
static ULONG wasAdd = 0;
static ULONG oldCount = 0;

HRESULT STDMETHODCALLTYPE QueryInterfaceNativeOutOfProcSrv(REFIID riid, void **ppv) 返回应用程序->QueryInterface(riid,ppv);

ULONG STDMETHODCALLTYPE AddRefNativeOutOfProcSrv() 返回应用程序->AddRef();

ULONG STDMETHODCALLTYPE ReleaseNativeOutOfProcSrv() 返回应用程序->释放();

ULONG STDMETHODCALLTYPE GetTypeInfoCountSrv(UINT * count) 返回应用程序->GetTypeInfoCount(count); ULONG STDMETHODCALLTYPE GetTypeInfoSrv (UINT n, LCID id, ITypeInfo ** inf) 返回应用程序->GetTypeInfo(n,id,inf); ULONG STDMETHODCALLTYPE GetIDsOfNamesSrv ( REFIID a, LPOLESTR * b, UINT c, LCID d, DISPID * e) 返回应用程序->GetIDsOfNames(a,b,c,d,e); ULONG STDMETHODCALLTYPE InvokeSrv ( DISPID a, REFIID b, LCID c, WORD d, DISPARAMS * e, VARIANT * i, EXCEPINFO * j, UINT *k) 返回应用程序->调用(a,b,c,d,e,i,j,k);

静态 IDispatchInterceptor 拦截器 = QueryInterfaceNativeOutOfProcSrv,AddRefNativeOutOfProcSrv,ReleaseNativeOutOfProcSrv, GetTypeInfoCountSrv、GetTypeInfoSrv、GetIDsOfNamesSrv、InvokeSrv ;

ULONG __stdcall 释放(IDispatch *self)

ULONG c = realRelease(self);
    Log->logWrite("release %d",c);

    if (  c == 1) 
    
        if (instance != NULL) 
       

          instance->OnBeginShutdown(NULL);
          Log->logWrite("OnBeginShutdown %d",c);
          instance->OnEmbeddedDisconnection();
           Log->logWrite("OnEmbeddedDisconnection %d",c);
           instance = NULL;
        
    
//if (c == 2) 
//  c = realRelease(self);
//    c = realRelease(self);
//

return c;

ULONG __stdcall addref(IDispatch *self) ULONG c = oldCount; if (wasAdd == 0) c = realAddRef(self); 旧计数 = c; 是加++; 否则 if (wasAdd == 1) 日志->logWrite("ADDREF FAKE %d",c); 是加++; 否则 if (wasAdd == 2) 日志->logWrite("ADDREF FAKE %d",c); 是加++; 别的 c = realAddRef(self); 日志->logWrite("ADDREF %d",c); 返回 c;

void InterceptRelease(IDispatch obj) void iunknown_vtable= (void*)((unsigned int)obj); void* idispatch_vtable = (void*)(((unsigned int)iunknown_vtable)+8); unsigned int* v1 = (unsigned int*)idispatch_vtable; realRelease = (releasePTR*)*v1; DWORD 旧; VirtualProtect(v1,4,PAGE_EXECUTE_READWRITE,&old);

*v1 = (unsigned int) release;

//while(obj->Release() > 0);

void InterceptAddRef(IDispatch obj) void iunknown_vtable= (void*)((unsigned int)obj); void* idispatch_vtable = (void*)(((unsigned int)iunknown_vtable)+4); unsigned int* v1 = (unsigned int*)idispatch_vtable; realAddRef = (addrefPTR*)*v1; DWORD 旧; VirtualProtect(v1,4,PAGE_EXECUTE_READWRITE,&old); *v1 = (unsigned int) addref; 申请:


 IDispatch * app  = Application;
 InterceptRelease(app);
 InterceptAddRef(app);

【讨论】:

以上是关于如何拦截进程外桌面 STA COM 到 STA COM 通信或引用计数递减?的主要内容,如果未能解决你的问题,请参考以下文章

如何将消息发布到运行消息泵的 STA 线程?

Openwrt开发之wifi sta设置

公寓是在服务器端还是在客户端“生活”在进程外环境中?

STA之RC网

选择目录,选择文件夹的COM组件问题。在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式。请确保您的 Main 函数带有 STAThreadAttribute 标记。 只有将调试器

你能解释一下STA和MTA吗?