调用 IConnectionPoint::Advise 导致崩溃

Posted

技术标签:

【中文标题】调用 IConnectionPoint::Advise 导致崩溃【英文标题】:Call IConnectionPoint::Advise cause a crash 【发布时间】:2015-06-10 08:17:13 【问题描述】:

在我的项目中,我编写了一个将在服务(COM 服务器)中调用的组件。还有一个进程会得到这个组件的接口,并通过连接点注册一个回调接口。所以服务可以使用回调接口做一些反馈。

但我发现当我使用 IConnectionPoint::Advise 注册我的回调接口时会导致崩溃。

我手动实现连接点。以下是我的部分代码:

class ATL_NO_VTABLE CUploadManager :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CUploadManager, &CLSID_UploadManager>,
public IUploadManager, 
public IConnectionPointContainer, 
public IConnectionPoint

...

// IConnectionPointContainer Functions
STDMETHODIMP EnumConnectionPoints(IEnumConnectionPoints **ppEnum);
STDMETHODIMP FindConnectionPoint(REFIID riid, IConnectionPoint **ppCP);

// IConnectionPoint Functions
STDMETHODIMP GetConnectionInterface(IID *pIID);
STDMETHODIMP GetConnectionPointContainer(IConnectionPointContainer **ppCPC);
STDMETHODIMP Advise(IUnknown *pUnkSink, DWORD *pdwCookie);
STDMETHODIMP Unadvise(DWORD dwCookie);
STDMETHODIMP EnumConnections(IEnumConnections **ppEnum);

在客户端,我已经得到了组件接口,然后我得到连接点并尝试调用函数Advise:

...
CComPtr<IConnectionPointContainer> pCPC;
hr = pUnknown->QueryInterface(IID_IConnectionPointContainer, (LPVOID *)&pCPC);
if (FAILED(hr))

    return ;


CComPtr<IConnectionPoint> pConnectionPoint;
hr = pCPC->FindConnectionPoint(__uuidof(_IEvents), &pConnectionPoint);
if (SUCCEEDED(hr))

    DWORD dwCookie = 0;
    CDataCallback* pCallback = new CDataCallback();
    IUnknown* pUnknown = NULL;
    pCallback->QueryInterface(IID_IUnknown, (void**)&pUnknown);
    hr = pConnectionPoint->Advise(pUnknown, &dwCookie);

...

可以成功获取所有接口指针,但是当我调用 Advise 时,发生崩溃。奇怪的是,崩溃并没有发生在Advise实现中,似乎发生在RPC调用过程中,调用堆栈是这样的:

003d0070() <----crash here
combase.dll!76eea3ab()
[Frames below may be incorrect and/or missing, no symbols loaded for combase.dll]
combase.dll!76ee9d00()
rpcrt4.dll!7612a53d()
mfc90ud.dll!CDialog::OnCmdMsg
...

但是如果Advise的第一个参数为NULL,则Advise函数可以被执行。

这是客户端中实现的 Sink 代码:

class CDataCallback : public _IEvents

public:
    CDataCallback() 
    ~CDataCallback() 

    HRESULT __stdcall QueryInterface(REFIID iid, LPVOID* ppInterface)
    
        *ppInterface = this;
        return S_OK;
    

    ULONG STDMETHODCALLTYPE AddRef()
    
        return 1;
    

    ULONG STDMETHODCALLTYPE Release()
    
        return 0;
    

    HRESULT __stdcall TestFunc()
    
        ...
        return S_OK;
    
;

接口 _IEvents 在组件中生成,将用作连接点中的回调。我只是用这个类来测试连接点,所以实现很简单。

我不知道这个崩溃是如何发生的。 感谢您的所有帮助!

【问题讨论】:

如果你构建了一个调试版本并在调试器中运行,你可能能够捕捉到正在发生的崩溃。 也许问题出在你的CDataCallback 上(特别是如果你不传递它的接口指针 - 你就不会崩溃),而且你没有展示你是如何实现它的. 我用的是debug版本,用VS2008调试全过程,但是好像0x3d0070地址无效,combase.dll组件没有开源,不知道怎么办更多调查。 我已经在客户端粘贴了Sink的代码,但是我觉得可能和Sink没有关系。 【参考方案1】:

问题可能出在您的CDataCallback 班级。它的QueryInterface 实现不安全并且错误地返回接口指针。

class CDataCallback : public _IEvents

HRESULT __stdcall QueryInterface(REFIID iid, LPVOID* ppInterface)

  *ppInterface = this;
  return S_OK;

如果请求的接口不是_IEvents,您必须检查iid 并返回E_NOINTERFACE。基本上你可以在那里设置一个断点,看看你在崩溃之前是否有一个调用并检查参数。

您的接收器可能会被查询其他接口(@98​​7654327@ 等),您必须正确指出您没有实现它们,而不是返回不匹配的指针,使用它会导致未定义的行为。

【讨论】:

非常感谢您的帮助!这是根本原因。我在这个问题上迷失了好几天。谢谢大家。

以上是关于调用 IConnectionPoint::Advise 导致崩溃的主要内容,如果未能解决你的问题,请参考以下文章

java三种调用方式(同步调用/回调/异步调用)

LINUX系统调用

引用调用 vs 复制调用调用

RPC 调用和 HTTP 调用的区别

js方法调用

深入理解Java虚拟机——方法调用(解析调用)