情景:被调用者--COM组件;调用者---外部程序
作用:COM组件 到 外部程序 的消息传递
方法:
1.外部程序通过接口类对象,访问接口类的方法。COM对象通过连接点方式,进行消息的反向传递。
2.外部程序通过接口类对象,访问接口类的方法。外部程序对接口类设置回调指针,进行消息的回调。
本文讲第二种方法。
直接上代码:
1.添加新的接口类Iww,作为回调函数类。类似连接点对象的作用。
interface Iww : IUnknown{ [helpstring("method Fire_Result")] HRESULT Fire_Result([in] LONG nResult); };
2.原有COM对象接口类,添加一个设置回调函数的方法Advise。
interface Ivv : IUnknown{ [helpstring("method Advise")] HRESULT Advise(Iww* pCallBack, [out] LONG* pdwCookie); [helpstring("method UnAdvise")] HRESULT UnAdvise(LONG dwCookie); [helpstring("method Add")] HRESULT Add(LONG n1, LONG n2); };
接口类对象:
class ATL_NO_VTABLE Cvv : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<Cvv, &CLSID_vv>, public Ivv { public: Cvv() { for(int i=0; i<10; i++) // 初始化所有的回调接口为 NULL { m_pCallBack[i] = NULL; } } DECLARE_REGISTRY_RESOURCEID(IDR_VV) BEGIN_COM_MAP(Cvv) COM_INTERFACE_ENTRY(Ivv) END_COM_MAP() DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct() { return S_OK; } void FinalRelease() { } public: private: Iww * m_pCallBack[10]; public: STDMETHOD(Advise)(Iww* pCallBack, LONG* pdwCookie); STDMETHOD(UnAdvise)(LONG dwCookie); STDMETHOD(Add)(LONG n1, LONG n2); }; OBJECT_ENTRY_AUTO(__uuidof(vv), Cvv) STDMETHODIMP Cvv::Advise(Iww* pCallBack, LONG* pdwCookie) { // TODO: Add your implementation code here if( NULL == pCallBack ) return E_INVALIDARG; for( int i=0; i<10; i++) // 寻找一个保存该接口指针的位置 { if( NULL == m_pCallBack[i] ) // 找到了 { m_pCallBack[i] = pCallBack; // 保存到数组中 m_pCallBack[i]->AddRef(); // 指针计数器 +1 *pdwCookie = i + 1; // cookie 就是数组下标 // +1 的目的是避免使用0,因为0表示无效 return S_OK; } } return E_OUTOFMEMORY; // 超过10个连接,内存不够用啦 } STDMETHODIMP Cvv::UnAdvise(LONG dwCookie) { // TODO: Add your implementation code here if( dwCookie<1 || dwCookie>10 ) // 这是谁干的呀?乱给参数 return E_INVALIDARG; if( NULL == m_pCallBack[ dwCookie - 1 ] ) // 参数错误,或该接口指针已经无效了 return E_INVALIDARG; m_pCallBack[ dwCookie -1 ]->Release(); // 指针计数器 -1 m_pCallBack[ dwCookie -1 ] = NULL; // 空出该下标的数组元素 return S_OK; } STDMETHODIMP Cvv::Add(LONG n1, LONG n2) { // TODO: Add your implementation code here long nResult = n1 + n2; for( int i=0; i<10; i++) { if( m_pCallBack[i] ) //如果回调接口有效 m_pCallBack[i]->Fire_Result( nResult ); // 则发出事件/通知 } return S_OK; }
3.外部程序,新建一个类继承Iww,并实现raw_Fire_Result方法。
#import "..ee.tlb" no_namespace
class CSink :public Iww { public: CSink(void); ~CSink(void); STDMETHOD(QueryInterface)(const struct _GUID &iid,void ** ppv); ULONG __stdcall CSink::AddRef(void); ULONG __stdcall CSink::Release(void); STDMETHOD(raw_Fire_Result)(long); }; CSink::CSink(void) { } CSink::~CSink(void) { } // STDMETHODIMP 是宏,等价于 long __stdcall STDMETHODIMP CSink::QueryInterface(const struct _GUID &iid,void ** ppv) { *ppv=this; return S_OK; } ULONG __stdcall CSink::AddRef(void) { return 1; } // 做个假的就可以,因为反正这个对象在程序结束前是不会退出的 ULONG __stdcall CSink::Release(void) { return 0; } // 做个假的就可以,因为反正这个对象在程序结束前是不会退出的 STDMETHODIMP CSink::raw_Fire_Result(long nResult) { // 如果完成了连接,当计算有结果后,该函数会被调用。完成组件通知的功能 CString str; str.Format(_T("%d"),nResult); MessageBox(str); return S_OK; }
4.调用步骤
CSink m_sink; IvvPtr m_spCom; DWORD m_dwCookie; HRESULT hr = m_spCom.CreateInstance(__uuidof(vv) ); if( FAILED( hr ) ) { AfxMessageBox( _T("COM对象初始化失败") ); CDialog::OnCancel(); } hr = m_spCom->Advise( &m_sink, (long *)&m_dwCookie ); if( SUCCEEDED( hr ) ) { AfxMessageBox( _T("Advise调用成功。已经正确连接") ); } else { AfxMessageBox( _T("Advise 调用失败") ); } m_spCom->Add(1,5);