<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    使用回調接口實現ActiveX控件和它的容器程序的通訊

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    本文閱讀基礎:有一定的C++基礎知識(了解繼承、回調函數),對MFC的消息機制有一定了解,對COM的基礎知識有一定了解,對ActiveX控件有一定了解。


    一. 前言


         ActiveX控件和它的容器程序如何通訊是一個值得研究的問題,因為這涉及到ActiveX控件和它的容器程序如何交互的問題。VC知識庫的楊老師寫了一系列博客介紹了一些通訊方式。鏈接如下:


    COM 組件設計與應用(十三)--事件和通知(VC6.0) 
    COM 組件設計與應用(十四)--事件和通知(vc.net) 

    COM 組件設計與應用(十五)--事件和通知(VC6.0) 

    COM 組件設計與應用(十六)--事件和通知(vc.net) 


           這些文章寫得真的很好,語言幽默風趣,深入淺出。我看后決心把它應用在ActiveX控件的回調實現上,經過實踐,覺得有些地方語焉不詳,自己做些摸索,寫就此文,算是對楊老師文章的一點補充。


    二.通知的方法


          ActiveX控件是一個窗口,它的容器程序自然也有一個父窗口;同時ActiveX控件是一個接口;ActiveX控件本質是一個COM組件,COM組件的客戶端和服務器端本身有自己的通訊方式。從這兩點我們可以想到二者之間的幾種通訊方式:


    info ways


          我和我的同事曾爭論ActiveX控件接口能否像一般C++的DLL那樣在導出函數參數列表里設置一個回調函數指針那樣實現回調,那時我認為是不行的。現在我看了ActiveX控件接口的參數類型,更加堅定了我的看法。其實從COM的初衷來看應該也是不行的,因為COM的初衷之一是提供一種跨語言的調用接口,而回調函數指針只對客戶端是C++程序是有意義,對于VB、C#則無回調函數指針一說的。


    三.實踐檢驗


          現在我們編一個這樣的小程序:在ActiveX控件上畫直線,在畫直線的同時把坐標傳給客戶端的視圖,在客戶端的視圖區上依據傳進來的坐標信息,繪制出相應的直線。

    在動手之前我簡要介紹我的思路:所謂基于COM的回調虛接口實現ActiveX控件和客戶端程序的通訊,大致是這樣的,就是在ActiveX工程的內部的idl文件定義一個虛接口,在客戶端程序定義一個虛接口的派生類來實現回調函數,在客戶端程序傳遞派生類對象指針給ActiveX控件,在控件內部調用這個虛接口的函數來激發客戶端程序的派生類的對應的回調函數。這里其實有一個關鍵問題,就是定義在idl文件中回調虛接口如何被ActiveX工程和客戶端程序識別,而不至于成為未定義類型(說實話,這個問題折磨了我兩個晚上,之所以這么麻煩,大概因為這個接口是定義在idl文件,而不是C++源文件中),下面我將介紹如何解決這個問題。


           首先我們創建一個MFC ActiveX Control的工程:DataX,具體如下圖:


    create activex


        接著在idl文件添加回調接口。這一步需要手動編輯idl文件。首先實現使用GUIDGEN.EXE(該工具在$/Microsoft Visual Studio 9.0/Common7/Tools路徑下,在VC6.0,VC 8.0都有這個工具)產生一個IID,生成時注意選擇是注冊表形式,具體如下圖:


    guidgen


           然后在idl文件的開頭下加入以下內容:


    [cpp] view plaincopy
    1. import "oaidl.idl";  
    2. import "ocidl.idl";  
    3. [  
    4.     object,  
    5.     uuid(87C3DA69-C915-41f5-8142-D77816F22004), // 這個IID 可以用GUDIGEN.EXE 產生  
    6.     helpstring("ICallBack 接口"),  
    7.     pointer_default(unique)  
    8. ]  
    9. interface ICallBack : IUnknown {  
    10.     [id(1),helpstring("回調接口,響應鼠標按下")] HRESULT FireLButtonDown([in] LONG x,LONG y);  
    11.     [id(2),helpstring("回調接口,響應鼠標移動")] HRESULT FireMouseMove([in] LONG x,LONG y);  
    12.     [id(3),helpstring("回調接口,響應鼠標按下")] HRESULT FireLButtonUp([in] LONG x,LONG y);  
    13. };  

     


          由于畫圖不是本文描述的重點,這里暫且略過。下面我們開始在ActiveX控件上實現繪圖和添加傳遞回調接口的函數。這里所說的傳遞回調接口實現的功能是把客戶端程序的回調接口派生類對象的指針傳到ActiveX控件內部。具體如下:


          這里介紹一下添加COM接口,首先在對應的接口選擇:Add Method,如下圖:


    add method


    然后開始填充返回類型、參數等信息:


    fill%20para


            我發現必須選擇下拉列表的參數類型,不選擇的話就會出錯:


    invalid%20para


          這時我們需要要SetCallBackPtr函數中的IUnknown全部替換為ICallBack。為什么要進行替換?一方面ICallBack*類型才是我們真正要傳遞的指針類型;另一方面如果不進行替換,外部的客戶端程序將不能識別ICallBack這個類型。我發現在idl文件定義的類型,似乎要在library ***
    {


    }


           這一段出現過的類型外部才能識別,不過這個沒有得到驗證,有空得研究一個idl文件。這里大致要替換的地方包括三處,控件窗口類中的頭文件的聲明、cpp文件中的定義以及在idl文件中的聲明。這一步算是解決了回調接口ICallBack的外部識別問題。


    現在我們在CDrawView定義一個
    ICallBack* m_pCallBack;的數據成員,以及它的賦值接口函數:


    [cpp] view plaincopy
    1.    void CDrawView::SetCallBack(ICallBack*  pCallBack)  
    2. {  
    3.      if (NULL!=pCallBack)  
    4.      {  
    5.              m_pCallBack = pCallBack;  
    6.      }  
    7. }  

     


          這里經常會出現的一個錯誤是:use of undefined type 'ICallBack'。這里的根源在于C++源碼文件并不能識別idl文件中的類型,就是一個內部識別問題。后來我發現其實編譯所有COM工程都有一步就是將idl文件的內容翻譯為C++能夠識別的頭文件。所以你會發現編譯后工程文件夾會多出一些頭文件和C文件,其中一個頭文件的命令比較有意思,規律大致是工程名+idl.h。這個頭文件其實就是編譯idl文件生成的。因此要使用回調基類接口,就必須包含這個頭文件。我這個工程的是:DataXidl.h。打開這個文件,可以從中看到COM的一些奧秘,里面有這樣一段代碼:


        

    [cpp] view plaincopy
    1. ICallBack : public IUnknown  
    2.  {  
    3.  public:  
    4.      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE FireLButtonDown(   
    5.          /* [in] */ LONG x,  
    6.          /* [in] */ LONG y) = 0;  
    7.        
    8.      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE FireMouseMove(   
    9.          /* [in] */ LONG x,  
    10.          /* [in] */ LONG y) = 0;  
    11.        
    12.      virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE FireLButtonUp(   
    13.          /* [in] */ LONG x,  
    14.          /* [in] */ LONG y) = 0;  
    15.        
    16.  };   

     



    看來在編譯過程中已經將idl接口的代碼轉換為C++代碼了。


          然后我們在控件窗口類里添加一個畫圖視圖的指針:


    [cpp] view plaincopy
    1. CDrawView *m_pView;  

     


    實現剛才添加的接口函數,把回調接口傳給視圖類:


    [cpp] view plaincopy
    1. void CDataXCtrl::SetCallBackPtr(ICallBack* pCallBack)  
    2. {  
    3.     AFX_MANAGE_STATE(AfxGetStaticModuleState());  
    4.     // TODO: Add your dispatch handler code here  
    5.     if (NULL!=m_pView)  
    6.     {  
    7.         m_pView->SetCallBack(pCallBack);  
    8.     }  
    9.       
    10. }  

     


    4. 在派生類中實現派生類接口


         現在我們開始新建一個單文檔程序:CallbackTest,VS 2005風格的。來測試這個ActiveX控件。
    我們首先將ActiveX接口導出,具體如下:


         在工程上添加新類,選擇"MFC Class From ActiveX Control":


    add%20acctivex%20class


    在注冊表上選擇DataX控件,再接口_DDataX從生成類,如下圖:


    choose%20activex%20from%20regedit


    再增加一個接口器類CSink,用于接收ActiveX控件發送過來的通知:


    add%20sink%20class


    fill%20sink%20class%20para



    這里會出現ICallBack未定義類型的問題。這時我只需要在stdafx.h把它的類型庫或ocx導入進來即可,代碼如下:



    [cpp] view plaincopy
    1. #ifdef _DEBUG  
    2. #import "../Intdir/Debug/DataX/DataX.tlb" no_namespace  
    3. #else  
    4.     #import "../Intdir/Release/DataX/DataX.tlb" no_namespace  
    5. #endif  

     


    你也可以這樣寫:


    [cpp] view plaincopy
    1. #ifdef _DEBUG  
    2. #import "../outdir/Debug/DataX.ocx" no_namespace  
    3. #else  
    4. #import "../outdir/Release/DataX.ocx" no_namespace  
    5. #endif  

     



          效果是一樣。我推薦你使用后一種用法,因為一般對ActiveX控件而言,別人可能會提供一個ocx文件給你,而不是一個tlb文件給你。

    接著我們定義一個停靠欄類class CDrawBar : public CDockablePane專門負責放置ActiveX控件。


          編譯一下,你會發現一下子出現了7個錯誤,本質上只有一個錯誤:error C2259: 'CSink' : cannot instantiate abstract class。CSink不能實例化對象。我們明白了,CSink派生自一個虛接口,卻沒有實現它的純虛函數。那么ICallBack類中有幾個我們還沒實現的純虛函數呢?我數了一下,有六個:IUnknown::QueryInterface、IUnknown::AddRef、IUnknown::Release、ICallBack::raw_FireLButtonDown、ICallBack::raw_FireMouseMove、ICallBack::raw_FireLButtonUp'。前三個函數我們很容易明白,ICallBack派生自IUnknown類,IUnknown的三個純虛函數自然也得實現(這三個函數的意義如果你沒搞懂,我推薦你看潘愛民的《COM原理與應用》)。至于后面三個函數,一開始我百思不得其解,我記得我在ICallBack類定義函數名稱是:FireLButtonDown、FireMouseMove和FireLButtonUp,怎么到了外部變成了:raw_FireLButtonDown、raw_FireMouseMove、raw_FireLButtonUp。后來我大致分析,可能是編譯器將名稱做了轉換,VS上的說法是:Raw methods provided by interface。這里的回調流程是怎樣的呢,我以下面的圖來說明:


          CallBack%20Mechanism%20photo



    其中傳遞回調接口的派生類工作放在應用程序類的InitInstance函數。
    首先在應用程序類定義一個保護數據成員:


    [cpp] view plaincopy
    1. CSink m_Sink;  

     


    再在InitInstance函數的最后面添加如下代碼:


    [cpp] view plaincopy
    1. CMainFrame* pFrame = static_cast<CMainFrame*>(m_pMainWnd);  
    2. if (NULL!=pFrame)  
    3. {  
    4.     // 獲取視圖指針  
    5.      CTestCallbackView *pView = static_cast<CTestCallbackView*>(pFrame->GetActiveView());  
    6.     // 獲取ActiveX控件指針  
    7.      CDDataX* pDataX =  pFrame->GetActiveControl();  
    8.      // 傳遞回調接口指針  
    9.      pDataX->SetCallBackPtr(&m_Sink);  
    10.      // 設置視圖窗口為發送消息的目標窗口  
    11.         m_Sink.SetMsgWnd(pView->GetSafeHwnd());  
    12. }  

     


    下面以鼠標右鍵按下消息的回調代碼。首先在ActiveX控件的視圖類的鼠標按下消息響應函數:


    [cpp] view plaincopy
    1. void CDrawView::OnLButtonDown(UINT nFlags, CPoint point)  
    2. {  
    3.     // TODO: Add your message handler code here and/or call default  
    4.     m_BeginPoint = point;  
    5.     if (NULL!=m_pCallBack)  
    6.     {  
    7.         // 激發客戶端的CSink::raw_FireLButtonDown函數  
    8.         m_pCallBack->FireLButtonDown(point.x,point.y);  
    9.     }  
    10.     CView::OnLButtonDown(nFlags, point);  
    11. }  

     


    客戶端的CSink對象收到通知后,給視圖類發送鼠標按下消息:


    [cpp] view plaincopy
    1. STDMETHODIMP CSink::raw_FireLButtonDown(long x,long y)  
    2. {  
    3.     if (NULL!=m_hMsgWnd)  
    4. {  
    5.         LPARAM   lparam   =   MAKELPARAM(x,y);   
    6.         ::PostMessage(m_hMsgWnd,WM_LBUTTONDOWN,NULL,lparam);  
    7.     }  
    8.     return S_OK;  
    9. }  

     


    效果圖如下:


    the%20effect%20of%20activex%20community%20his%20container




           這種回調機制,在現在的開源或商業產品有沒有應用實例呢?如果你研究過著名的開源的ActiveX控件:MapWinGIS。你會發現MapWinGIS實際上是采用這種回調機制的,比如MapWinGIS中的操作shp文件的類CShapefile的一個接口:


    STDMETHOD(Open)(/*[in]*/ BSTR ShapefileName, /*[in, optional]*/ICallback * cBack, /*[out, retval]*/VARIANT_BOOL * retval);


            這個函數的第二個參數的類型就是MapWinGIS內部定義的回調接口類型:ICallback。


    四.小結


          總結一下開發過程,再和楊老師提供的例程做一個簡單比較。應該說我這個例程和楊老師的例程很類似。有點不同的是楊老師使用的是標準的ATL 組件,我這個使用的是MFC ActiveX控件。從本質而言,ActiveX控件類是一個com接口,只不過這個接口有些特殊,它既是一個窗口,因而也就具有了一般窗口的屬性,能夠接受和響應消息,同時它也是一個接口類。

    本文出現的代碼編譯環境為:VS C++ 2008 + sp1, Win XP + sp3。


         源碼下載:點擊下載 

    from:http://blog.csdn.net/clever101/article/details/5649489


    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全