程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> COM技術初探(三):一個真正的COM

COM技術初探(三):一個真正的COM

編輯:關於VC++

一、實現ISmipleMath,IAdvancedMath接口和DllGetClassObject()

1.1 實現ISmipleMath和IAdvancedMath接口

讓我們將原來的CMath 類(CMath其實就是"COM技術初探(二)COM基礎知識"裡的那個CMath類)修改來實現ISmipleMath接口和IAdvancedMath接口。

修改的地方如下:

1) Math.h文件 /*@**#---2003-10-29 21:33:44 (tulip)---#**@
#include "interface.h"*/
#include "MathCOM.h"//新增加的,以替換上面的東東
class CMath : public ISimpleMath,
       public IAdvancedMath
{
private:
  ULONG m_cRef;
private:
  int calcFactorial(int nOp);
  int calcFabonacci(int nOp);
public:
  CMath();
  //IUnknown Method
  STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
  STDMETHOD_(ULONG, AddRef)();
  STDMETHOD_(ULONG, Release)();
  //  ISimpleMath Method
  STDMETHOD (Add)(int nOp1, int nOp2,int * pret);
  STDMETHOD (Subtract)(int nOp1, int nOp2,int *pret);
  STDMETHOD (Multiply)(int nOp1, int nOp2,int *pret);
  STDMETHOD (Divide)(int nOp1, int nOp2,int * pret);
  //  IAdvancedMath Method
  STDMETHOD (Factorial)(int nOp,int *pret);
  STDMETHOD (Fabonacci)(int nOp,int *pret);
};
2) Math.cpp文件 /*@**#---2003-10-29 21:32:35 (tulip)---#**@
#include "math.h"
STDMETHODIMP CMath::QueryInterface(REFIID riid, void **ppv)
{//  這裡這是實現dynamic_cast的功能,但由於dynamic_cast與編譯器相關。
  if(riid == IID_ISimpleMath)
    *ppv = static_cast<ISimpleMath *>(this);
  else if(riid == IID_IAdvancedMath)
    *ppv = static_cast<IAdvancedMath *>(this);
  else if(riid == IID_IUnknown)
    *ppv = static_cast<ISimpleMath *>(this);
  else {
    *ppv = 0;
    return E_NOINTERFACE;
  }
  //這裡要這樣是因為引用計數是針對組件的
  reinterpret_cast<IUnknown *>(*ppv)->AddRef();
  return S_OK;
}
STDMETHODIMP_(ULONG) CMath::AddRef()
{
  return ++m_cRef;
}
STDMETHODIMP_(ULONG) CMath::Release()
{
  // 使用臨時變量把修改後的引用計數值緩存起來
  ULONG res = --m_cRef;
  // 因為在對象已經銷毀後再引用這個對象的數據將是非法的
  if(res == 0)
    delete this;
  return res;
}
STDMETHODIMP CMath::Add(int nOp1, int nOp2,int * pret)
{
   *pret=nOp1+nOp2;
   return S_OK;
}
STDMETHODIMP CMath::Subtract(int nOp1, int nOp2,int * pret)
{
  *pret= nOp1 - nOp2;
  return S_OK;
}
STDMETHODIMP CMath::Multiply(int nOp1, int nOp2,int * pret)
{
  *pret=nOp1 * nOp2;
  return S_OK;
}
STDMETHODIMP CMath::Divide(int nOp1, int nOp2,int * pret)
{
  *pret= nOp1 / nOp2;
  return S_OK;
}
int CMath::calcFactorial(int nOp)
{
  if(nOp <= 1)
    return 1;
  return nOp * calcFactorial(nOp - 1);
}
STDMETHODIMP CMath::Factorial(int nOp,int * pret)
{
  *pret=calcFactorial(nOp);
  return S_OK;
}
int CMath::calcFabonacci(int nOp)
{
  if(nOp <= 1)
    return 1;
  return calcFabonacci(nOp - 1) + calcFabonacci(nOp - 2);
}
STDMETHODIMP CMath::Fabonacci(int nOp,int * pret)
{
  *pret=calcFabonacci(nOp);
  return S_OK;
}
CMath::CMath()
{
  m_cRef=0;
}

1.2 COM組件調入大致過程

創建過程示意圖如下:

1) COM庫初始化 使用CoInitialize COM API函數(客戶端)使組件加入套間,關於套間的概念我們以後再講。(客戶端)

2)激活COM(客戶端)

3) 通過注冊表項將對應的DLL調入COM庫中(COM運行環境)

4) 調用COM組件內的DllGetClassObject()導出函數(COM組件)

5)通過類廠返回類廠的接口指針(如果客戶端是使用CoCreateInstance創建組件時不需要緩存類廠接口指針,則此步在COM運行環境中完成不返回給客戶端,同時馬上進入第6步。與此相反,客戶端會緩存組件類廠接口指針。此方法當客戶需要創建多個實例時很有用。)(COM庫)

6)通過類廠接口指針調用CreateInstance創建組件實例。

1.3 DllGetClassObject()實現

在MathCOM.cpp裡加入下列語句#include "math.h"
#include "MathCOM_i.c"
並將MathCOM.cpp裡的DllGetClassObject()修改成如下:
/*********************************************************************
* Function Declare : DllGetClassObject
* Explain :
* Parameters :
* REFCLSID rclsid --
* REFIID riid --
* void **ppv --
* Return :
* STDAPI --
* Author : tulip
* Time : 2003-10-29 22:03:53
*********************************************************************/
STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void **ppv)
{
  if(rclsid == CLSID_MathCom)
{
  
  return pm_math->QueryInterface(riid,ppv);
}
  return CLASS_E_CLASSNOTAVAILABLE;
}

1.4 客戶端

接下來我們寫個客戶端程序對此COM進行測試。

新建空的名為TestMathCOM一個win32 Console工程加入到MathCOM workspace中。

在TestMathCOM工程裡添加一個名為main.cpp的文件,此文件的內容如下:

//main.cpp文件
#include <windows.h>
#include "../MathCOM.h"//這裡請注意路徑
#include "../MathCOM_i.c"//這裡請注意路徑
#include <iostream>
using namespace std;
void main(void)
{
  //初始化COM庫
  HRESULT hr=::CoInitialize(0);
  ISimpleMath * pSimpleMath=NULL;
  IAdvancedMath * pAdvancedMath=NULL;
  int nReturnValue=0;
  hr=::CoGetClassObject(CLSID_MATHCOM,
        CLSCTX_INPROC,
        NULL,
        IID_ISimpleMath,
        (void **)&pSimpleMath);
  if(SUCCEEDED(hr))
  {
    hr=pSimpleMath->Add(10,4,&nReturnValue);
    if(SUCCEEDED(hr))
      cout << "10 + 4 = " <<nReturnValue<< endl;
    nReturnValue=0;
  }
  //  查詢對象實現的接口IAdvancedMath
  hr=pSimpleMath->QueryInterface(IID_IAdvancedMath, (void **)&pAdvancedMath);
  if(SUCCEEDED(hr))
  {
    hr=pAdvancedMath->Fabonacci(10,&nReturnValue);
    if(SUCCEEDED(hr))
      cout << "10 Fabonacci is " << nReturnValue << endl;
  }
  pAdvancedMath->Release();
  pSimpleMath->Release();
  ::CoUninitialize();
  ::system("pause");
  return ;
}
關於如何調試dll請參閱附錄A

1.5 小結

到現在我們應該有2個工程和8個文件,具體如下

工程 文件 作用 MathCOM Stdafx.h和stdafx.cpp 預編譯文件   MathCOM.cpp Dll入口函數及其他重要函數定義的地方   MathCOM.def 模塊定義文件   MathCOM.idl 接口定義文件(在1.2後如果編譯的話應該還有四個文件)   math.h和math.cpp ISmipleMath,IadvancedMath接口的實現類 TestMathCOM Main.cpp MathCOM的客戶端,用於測試MathCOM組件

在此部分中我們已經完成一個可以實用的接近於完整COM組件( 沒有完整的地方是還沒有實現DllCanUnloadNow() )。我們完成了此COM組件的客戶端。

如果你已經創建COM實例的話,你可能會發現在此部分的客戶端並不是用CoCreateInstance()來創建COM實例,那是因為我們還沒有在此COM組件裡實現IClassFactory接口(此接口在下一部分實現)。

通過這個例子,我希望大家明白以下幾點:

1) DllGetClassObject()的作用(此函數的詳細說明請參閱<<COM本質論>>p86) ,請參看COM組件調入大致過程這一節,同時也請將斷點打在DllGetClassObject()函數上,仔細看看他的實現(在沒有實現IClassFactory接口的情況下)和他的傳入參數。

2) 為什麼在這個客戶端程序裡不使用CoCreateInstance()來創建COM實例而使用CoGetClassObject()來創建COM實例。你可以試著用CoCreateInstance()來創建Cmath,看看DllGetClassObject()的第一參數是什麼?

3) 實現IClassFactory接口不是必需的,但應該說是必要的(如何實現請看下一章)

4) 應掌握DllRegisterServer()和DllUnregisterServer()的實現。

5) 客戶端在調用COM組件時需要那幾個文件(只要由idl文件產生的兩個文件)

二、類廠

2.1 回顧

在上節裡,我們創建組件實例及得到接口指針的過程如下:首先在客戶端的用戶調用COM API CoCreateInstance,這個函數調用另一個COM API CoGetClassObject獲得組件的類廠接口指針,此時COM庫會加載組件DLL(EXE的處理方式稍有不同,我們會在以後的章節中講解)中的導出函數DllGetClassObject(),獲得類廠接口指針後馬上調用類廠的方法CreateInstance創建對象實例並通過組件對象的QueryInterface()得到用需的接口指針。

此前我們所實現的並不是真正完整的COM組件,現在我們來實現一個真正的COM組件,實現組件創建機制的核心:類廠 - 創建組件對象的對象。COM為通用化以及在管理上統一和方便,COM規范要求所有標准COM組件都應實現IClassFactory接口(有關IClassFactory的功能各個方法的作用請參閱MSND/Welcome to the msnd library/msnd resource/selected online columns/Dr.GUI online/Dr. GUI on Components, COM, and ATL/part 5)

2.2 增加IClassFactory的實現

此次我們將修改的文件如下

工程名 文件名 修改屬性 MathCOM MathCOM.cpp 修改   MathFactory.h和MathFactory.cpp 新增 TestMathCOM Main.cpp 修改

2.2.1 MathCOM.cpp

#include "math.h"
#include "MathCOM_i.c"
#include "MathFactory.h"
//////////////////////////////////////////////////////
// 服務器鎖, 如果標志為S_OK,就可以卸載當前組件的服務器,
// 關於引用計數及服務器跟COM對象的關系,後緒章節再講解
LONG g_cObjectAndLocks=0;
//standard self-registration table
const char * g_RegTable[][3]={………………….
…………………………
STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void **ppv)
{
  if(rclsid==CLSID_MATHCOM)
  {
    CMathFactory *pFactory = new CMathFactory;  // 創建類廠對象
    if(pFactory == NULL)
      return E_OUTOFMEMORY;
    ///////////////////////////////////////////
    // 一般情況下COM希望用戶只查詢IClassFactory,
    // IClassFactory2及IUnknow
    HRESULT hr = pFactory->QueryInterface(iid, ppv);
    return hr;
  }
  return CLASS_E_CLASSNOTAVAILABLE;
}
STDAPI DllCanUnloadNow(void)
{
  return (g_cObjectAndLocks==0)?S_OK:E_FAIL;
}
2.2.2 MathFactory.h // MathFactory.h: interface for the CMathFactory class.
//
//////////////////////////////////////////////////////////////////////
#ifndef MATHFACTORY_H
#define MATHFACTORY_H
#include <unknwn.h>
class CMathFactory :public IClassFactory
{
public:
  CMathFactory():m_cRef(0){}
  //IUnknow Method
  STDMETHODIMP QueryInterface(REFIID,void**);
  STDMETHODIMP_(ULONG) AddRef();
  STDMETHODIMP_(ULONG) Release();
  //IClassFactory Method
  STDMETHODIMP CreateInstance(IUnknown * ,REFIID ,void **);
  STDMETHODIMP LockServer(BOOL fLock);
protected:
  LONG m_cRef;
};
#endif
2.2.3

MathFactory.cpp

// MathFactory.cpp: implementation of the CMathFactory class.
//
//////////////////////////////////////////////////////////////////////
#include "math.h"
#include "MathFactory.h"
extern LONG g_cObjectAndLocks;
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
STDMETHODIMP_(ULONG) CMathFactory::AddRef(void)
{
  return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) CMathFactory::Release(void)
{
  return ::InterlockedDecrement(&m_cRef);
}
STDMETHODIMP CMathFactory::QueryInterface(REFIID riid,void ** ppv)
{
  *ppv=NULL;
  if(riid==IID_IUnknown||riid==IID_IClassFactory)
  {
    *ppv=static_cast<IClassFactory *>(this);
    reinterpret_cast<IUnknown*>(*ppv)->AddRef();
    return S_OK;
  }
  else
    return (*ppv=0),E_NOINTERFACE;
}
STDMETHODIMP CMathFactory::CreateInstance(IUnknown * pUnkOuter,REFIID riid,void ** ppv)
{
  *ppv=NULL;
  //  現在不支持聚合
  if(pUnkOuter!=NULL)
    return CLASS_E_NOAGGREGATION;
  CMath * pMath=new CMath;
  if(pMath==NULL)
    return E_OUTOFMEMORY;
  HRESULT hr=pMath->QueryInterface(riid,ppv);
  if(FAILED(hr))
    delete pMath;
  return hr;
}
STDMETHODIMP CMathFactory::LockServer(BOOL fLock)
{
  if(fLock)
    ::InterlockedIncrement(&g_cObjectAndLocks);
  else
    ::InterlockedDecrement(&g_cObjectAndLocks);
  return NOERROR;
}
2.2.4 main.cpp #include <windows.h>
#include "../MathCOM.h"
#include "../MathCOM_i.c"
#include <iostream>
using namespace std;
void main(void)
{
  //初始化COM庫
  HRESULT hr=::CoInitialize(0);
  ISimpleMath * pSimpleMath=NULL;
  IAdvancedMath * pAdvancedMath=NULL;
  int nReturnValue=0;
  //如果還想用CoCreateInstance()得到接口指針,請如下使用先傳一個IClassFactory接口
  /*************** 這裡請注意 *****************/
  // 方法一
  hr = ::CoCreateInstance(CLSID_MATHCOM,
        CLSCTX_INPROC,
        NULL,
        IID_ISimpleMath,
        (void*)&pSimpleMath);
  ///////////////////////////////////////////////////////////////////////
  // 這個方法的好處是不需要太多的代碼, 讓COM來處理真正的類廠創建對象的過程.
  // 同時這個函數的改進版CoCreateInstanceEx在分布式對象應用中可以一次取回多
  // 個接口指針. 避免過多的在網絡上浪費時間關於這個函數的用法我也會在以後的
  // 章節中講解. 您也可以參考MSDN或相關書藉
  //  方法二
  IClassFactory * pClassFactory=NULL;  // 類廠接口指針
  //  獲取對象的類廠接口指針
  hr=::CoGetClassObject(CLSID_MATHCOM,
        CLSCTX_INPROC,
        NULL,
        IID_IClassFactory,
        (void**)&pClassFactory);
  // 真正創建對象
  hr = pClassFactory->CreateInstance(NULL,IID_ISimpleMath,(void**)&pSimpleMath);
  // 此方法的好處在於可以一次創建多個對象, 不需要
  
  // 下面測試 IDispatch 接口
  if(SUCCEEDED(hr))
  {
    hr=pSimpleMath->Add(10,4,&nReturnValue);
    if(SUCCEEDED(hr))
      cout << "10 + 4 = " <<nReturnValue<< endl;
    nReturnValue=0;
  }
  //查詢對象實現的接口IAdvancedMath
  hr=pSimpleMath->QueryInterface(IID_IAdvancedMath, (void **)&pAdvancedMath);
  if(SUCCEEDED(hr))
  {
    hr=pAdvancedMath->Fabonacci(10,&nReturnValue);
    if(SUCCEEDED(hr))
      cout << "10 Fabonacci is " << nReturnValue << endl;
  }
  pAdvancedMath->Release();
  pSimpleMath->Release();
  ::CoUninitialize();
  return ;
}

2.3 小結

在此部分我們實現了IClassFactory接口,可以說實現了一個真正的COM對象了,我們可以在C++中使用一般的COM客戶端方法創建對象,執行方法等。但是不知道用戶有沒有發現,COM的目標是語言無關,像VB中不支持指針,腳本是解釋執行(但是COM需要在編譯期間綁定接口調用)。怎麼辦呢?有沒有辦法實現呢?方法當然是有的。接下來的一節我不為大家講解為不支持指針的語言而提供支持的特殊接口IDispatch - 派發接口(也稱調度接口)。為上述語言提供一個效率上稍差但靈活性更高的解決方法。接下來這一章讓我們實現IDispatch接口,以便在腳本環境中使用我們的COM組件。

三、實現派發接口

為了在腳本語言環境中使用COM組件,COM規范規定要在腳本語言環境使用的COM必須實現IDispatch接口,此時的COM接口被稱為派發接口。還記得我們在講IDL的時個描述接口的內容嗎?當時我們並沒有設置這個關於接口的說明:custom和dual。前者是自定義接口,也就是從IUnknown派生的接口。後者是從IDispatch派生的接口,當然IDispatch也必須從IUnknown派生。只有當此接口從IDispatch派生時,此COM組件可在不支持指針及腳本語言環境下使用。現在讓我們的組件也支持雙接口。

3.1 IDispatch接口

IDispatch接口共有四個方法,其中只有比較重要(Invoke)。其它方法我們暫時用不到,就沒有實現。如果讀者有興趣自己可以參考相關資料來實現一下。並不是很難。像GetIDsOfName等可以交給ITypeInfo等接口處理。講到這裡可能就復雜了。我們以後再為讀者講解。我們的MathCOM組件也只實現了Invoke方法,其他方法都返回E_NOTIMPL表示沒有實現。

關於IDispatch接口請參閱MSND文檔或<<COM本質論>>p295頁

3.2 支持派發接口的MathCOM組件

要讓我們的MathCOM組件支持雙接口,需要修改如下文件

工程名 文件名 MathCOM MathCOM.idl   Math.h和Math.cpp TestMathCOM Main.cpp

3.2.1修改MathCOM.idl

MathCOM.idl文件需要修改三個地方。

1) 增加daul屬性,以便表明我們的接口是雙接口。

2) 將接口的基類由IUnknow接口變成IDispatch接口

3) 在接口的每個方法加上序號屬性

修改後的MathCOM.idl如下(請自己找出修改的地方)

// MathCOM.idl : IDL source for MathCOM.dll
//
// This file will be processed by the MIDL tool to
// produce the type library (MathCOM.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
[
  uuid(FAEAE6B7-67BE-42a4-A318-3256781E945A),
  helpstring("ISimpleMath Interface"),
  object,
  dual,    //  這個標識我們前面沒遇到過。
  pointer_default(unique)
]
interface ISimpleMath : IDispatch
{
  [id(1)] HRESULT Add([in]int nOp1,[in]int nOp2,[out,retval]int * pret);
  [id(2)] HRESULT Subtract([in]int nOp1,[in]int nOp2,[out,retval]int * pret);
  [id(3)] HRESULT Multiply([in]int nOp1,[in]int nOp2,[out,retval] int * pret);
  [id(4)] HRESULT Divide([in]int nOp1,[in]int nOp2,[out,retval]int * pret);
};
[
  uuid(01147C39-9DA0-4f7f-B525-D129745AAD1E),
  helpstring("IAdvancedMath Interface"),
  object,
  dual,
  pointer_default(unique)
]
interface IAdvancedMath : IDispatch
{
  [id(1)] HRESULT Factorial([in]int nOp1,[out,retval]int * pret);
  [id(2)] HRESULT Fabonacci([in]int nOp1,[out,retval]int * pret);
};
[
  uuid(CA3B37EA-E44A-49b8-9729-6E9222CAE844),
  version(1.0),
  helpstring("MATHCOM 1.0 Type Library")
]
library MATHCOMLib
{
  importlib("stdole32.tlb");
  importlib("stdole2.tlb");
  [
    uuid(3BCFE27E-C88D-453C-8C94-F5F7B97E7841),
    helpstring("MATHCOM Class")
  ]
  coclass MATHCOM
  {
    [default] interface ISimpleMath;
    interface IAdvancedMath;
  };
};

3.2.2 修改math.h文件

在math.h增加IDispatch接口的四個方法聲明,並對三個進行簡單的實現。修改後的math.h見下

//前面相同
CMath();
//IUnknown Method
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
//IDispatch Method
STDMETHOD(GetTypeInfoCount)(UINT * pit){  return E_NOTIMPL;}
STDMETHOD(GetTypeInfo)(UINT it,LCID lcid,ITypeInfo **ppti){ return E_NOTIMPL; }
STDMETHOD(GetIDsOfNames)(REFIID riid,
      OLECHAR ** pNames,
      UINT nNames,
      LCID lcid,
      DISPID * pdispids){ return E_NOTIMPL; }
STDMETHOD(Invoke)(DISPID id,
    REFIID riid,
    LCID lcid,
    WORD wFlags,
    DISPPARAMS *pd,
    VARIANT * pVarResult,
    EXCEPINFO * pe,
    UINT *pu);
// ISimpleMath Method
STDMETHOD (Add)(int nOp1, int nOp2,int * pret);
STDMETHOD (Subtract)(int nOp1, int nOp2,int *pret);
STDMETHOD (Multiply)(int nOp1, int nOp2,int *pret);
STDMETHOD (Divide)(int nOp1, int nOp2,int * pret);
//後面相同
3.2.3 修改math.cpp文件

增加IDispatch接口中的Invoke方法的實現。修改後的math.cpp文件見下 //前面相同
CMath::CMath()
{
  m_cRef=0;
}
/*********************************************************************
* Function Declare : CMath::Invoke
* Explain : IDispatch接口的Invoke方法
* Parameters :
* DISPID id -- 方法的調度ID(請與idl中序號相比較)
* REFIID riid -- 接口IID
* LCID lcid -- 語言ID
* WORD wFlags --
* DISPPARAMS * pd -- 傳入參數結構(具體結構請參閱MSDN或本質論p294)
* VARIANT * pVarResult -- 出參(VARIANT請參閱MSDN文檔或<<深入解析ATL>>p56
* EXCEPINFO * pe -- 異常(一般為NULL)
* UINT * pu --
* Return :
* STDMETHODIMP --
* Author : tulip
* Time : 2003-10-30 15:56:37
*********************************************************************/
STDMETHODIMP CMath::Invoke(DISPID id,
REFIID riid,
LCID lcid,WORD wFlags,
            DISPPARAMS * pd,
VARIANT * pVarResult,
EXCEPINFO * pe,
UINT * pu)
{
  if(riid==IID_ISimpleMath)
  { 
  if(1==id)
     return Add(pd->rgvarg[0].intVal,pd->rgvarg[1].intVal,&pVarResult->intVal);
  else if (2==id)
     return Subtract(pd->rgvarg[0].intVal,pd->rgvarg[1].intVal,&pVarResult->intVal);
  else if (3==id)
     return Multiply(pd->rgvarg[0].intVal,pd->rgvarg[1].intVal,&pVarResult->intVal);
  else if (4==id)
     return Divide(pd->rgvarg[0].intVal,pd->rgvarg[1].intVal,&pVarResult->intVal);
  else
    return E_FAIL;
   }
   else if (riid==IID_IAdvancedMath)
   {
  if(1 == id)
    return Factorial(pd->rgvarg[0].intVal,&pVarResult->intVal);
  else if (2 == id)
    return Fabonacci(pd->rgvarg[0].intVal,&pVarResult->intVal);
  else
    return E_FAIL;
   }
   else
  return E_NOINTERFACE;
}

在修改後上述三個文件,請重新編譯,(如果你在上次注冊前已經移動過過工程,請重新注冊)

3.2.4 修改main.cpp文件

修改main.cpp文件的目的是通過IDispatch接口中的Invoke方法來測試我們的組件。

修改後的main.cpp文件見下#include <windows.h>
#include "../MathCOM.h"
#include "../MathCOM_i.c"
#include <iostream>
using namespace std;
#include <atlbase.h>
void main(void)
{
  /////////////////////////////////////////////////
  // 初始化COM庫,這裡的真正含義是把COM加入適當的套間,
  // 關於套間的概念及其它相關概念會在後緒文章中講解。
  HRESULT hr = ::CoInitialize(NULL);
  ISimpleMath * pSimpleMath=NULL;
  IAdvancedMath * pAdvancedMath=NULL;
  IDispatch * pDispatch=NULL;    //  派發接口指針
  int nReturnValue=0;
  
  hr = ::CoCreateInstance(CLSID_MATHCOM,
         CLSCTX_INPROC,
         NULL,
         IID_ISimpleMath,
         (void*)&pSimpleMath);
  /////////////////////////
  // 下面測試IDispatch接口
  //
  pSimpleMath->QueryInterface(IID_IDispatch,(void**)&pDispatch);
  if(SUCCEEDED(hr))
  {
    cout<<"下面進行IDispatch接口"<<endl;
    CComVariant varResult;
    CComVariant varResult2;
    CComVariant *pvars=new CComVariant[2];
    CComVariant *pvars2=new CComVariant[1];
    varResult.Clear();
    pvars[1]=4;
    pvars[0]=10;
    DISPPARAMS disp = { pvars, NULL, 2, 0 };
    hr=pDispatch->Invoke(0x1,
          IID_ISimpleMath,
          LOCALE_USER_DEFAULT,
          DISPATCH_METHOD,
          &disp,
          &varResult,
          NULL,
          NULL);
    if(SUCCEEDED(hr))
      cout << "10 + 4 = " <<varResult.intVal<< endl;
    varResult2.Clear();
    pvars2[0]=10;
    DISPPARAMS disp2={pvars2,NULL,1,0};
    hr=pDispatch->Invoke(0x2,
          IID_IAdvancedMath,
          LOCALE_USER_DEFAULT,
          DISPATCH_METHOD,
          &disp,
          &varResult2,
          NULL,
          NULL);
    if(SUCCEEDED(hr))
        cout << "10 Fabonacci is " <<varResult2.intVal << endl;
    cout<<"IDispatch接口測試完畢"<<endl;
    ::system("pause");
  }
  /*
  *IDispatch接口測試完畢
  */
  if(SUCCEEDED(hr))
  {
    hr=pSimpleMath->Add(10,4,&nReturnValue);
    if(SUCCEEDED(hr))
      cout << "10 + 4 = " <<nReturnValue<< endl;
    nReturnValue=0;
  }
  //  查詢對象實現的接口IAdvancedMath
  hr=pSimpleMath->QueryInterface(IID_IAdvancedMath, (void **)&pAdvancedMath);
  if(SUCCEEDED(hr))
  {
    hr=pAdvancedMath->Fabonacci(10,&nReturnValue);
    if(SUCCEEDED(hr))
      cout << "10 Fabonacci is " << nReturnValue << endl;
  }
  pAdvancedMath->Release();
  pSimpleMath->Release();
  pDispatch->Release();
  ::CoUninitialize();
  return ;
}

四 總結

真的好高興啊,終於要寫總結了。

我們再回顧一下,我們到底干了些什麼,我們實現了類廠,實現了一個真正的COM對象,同時為了在不支持指針及腳本環境中使用COM我們又實現了IDispatch派發接口。至於,為什麼要實現IDispach,IDispatch是怎麼實現的,也就是早期捆綁和晚期捆綁的概念。我們將在下一章中講解。希望大家看到這裡不至於沒方向了。不過裡面尚有不少你們看到但不知道什麼意義,不急。慢慢來,我會在以後各章詳細講述這些概念。現在我想讓你們知道一個COM組件大致的創建過程,對COM有一定宏觀上的認識。畢竟我的水平有限,沒有大師級講解透徹。我只是將我對COM的一點認識與大家共享一下。希望大家一起進步。

在第一部分,我想大家了解COM的好處。接下來第二部分,我們看到COM核心結構圖(接下來所有的例子都是以圖1.3為中心的)。在第三部分,我step by step 講如何實現一個完整的COM組件。

COM原理比較簡單,但在實現時比較靈活,這就造成了大多數人感到難以掌握,其實只要抓住COM的本質,其他的東西都比較簡單。如果你了解了”**”這個東東,那你懂大部分了。如果你完全搞明白了什麼是接口類,實現類,類對象以及他們之間的關系,那你又前進了一大步。

最後希望大家在學習COM的道路能夠感受到快樂。以後有空我們一起交流,哈哈!!

“COM IS LOVE”。

五 參考資料

COM方面 (在COM方面除潘愛明的作著可以看看外,國內其他作者的東西都是垃圾。)

<<COM本質論>> Don Box (如果沒有看過這本書那你的COM就那個樣了^_^)

<<COM技術內幕>> (比較淺,適合入門)

<<COM原理實現與應用>> (潘愛明,本書比較詳細講解了COM的幾大核心應用)

ATL方面

<<深入解析ATL>> (相當於COM界裡的<<COM本質論>>)

MSDN

MSJ 雜志的<<House of COM>>由Don Box主持 (現在MSDN雜志已經沒有此專欄,但你還是可以在MSDN許多相當文章)

MSDN Dr.GUI online 具體位置在MSND/Welcome to the msnd library/

msnd resource/selected online columns/Dr.GUI online/

MSDN SDK 文檔 具體位置 MSDN/Platform SDK Documentation/Component Servies

MSDN 的技術文章 位置 MSDN/Technical Articles/Component Object Model

MSDN BackGrounders 有一些相當於白皮書之類的東西。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved