程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> COM應用軟件開發技術

COM應用軟件開發技術

編輯:關於C++

1.COM技術概述

COM表示Component Object Model(組件對象模型),它是Microsoft大力推廣的軟件開發技術。采用COM規范開發的應用軟件具有強大的功能,主要有如下幾點:

◆COM是二進制編程規范,可以編寫被多種語言使用的代碼。

◆用於創建ActiveX控件。

◆通過OLE Automation 控制其它的程序。

◆與其它機器上的對象或程序進行對話,構成分布式應用程序。

Microsoft推出Windows 98和Windows NT 5.0後,整個操作系統的核心都圍繞著COM來建立。我們可以把Windows系統看作是一系列的COM接口,在需要是可以調用這些接口。如DirectX就是一系列的COM接口服務程序,通過它可以進行高性能的Windows圖形程序設計。

用COM技術開發的應用程序從理論上說是客戶/服務器模式的程序。程序員可以使用一系列的COM服務程序來構造他們自己的應用程序,這些服務程序可以根據需要隨時嵌入到主程序中。在分布式系統中,可以通過網絡來訪問這些服務程序。將來,操作系統和整個網絡可能會被看作是一套以COM對象形式提供的服務集。一部分程序員負責建立這些服務,而另一部分程序員只負責如何調用它們。其目的是實現軟件的即插即用。

開發COM應用程序是比較復雜的,通常需采用ActiveX模板庫(ATL)來編程。在這裡我們推薦采用C++ Builder來開發COM程序,Inprise(Borland)公司的面向對象技術一直處於世界領先水平,C++ Builder采用可視化方法,隱藏了ATL的實現細節,自動生成COM接口所需的代碼。

以下的程序舉例采用C++ Builder 4.0 編制,在中文Windows98環境下運行。

2.建立COM服務程序

COM服務程序有三種形式,第一種是駐留在本地機器上以DLL形式提供,該服務程序被調用時,嵌入到調用程序的線程中運行;第二種是駐留在本地機器上以EXE形式提供,該服務程序被調用時將占用獨立的線程運行;第三種駐留在遠端機器上以EXE形式提供,服務程序通過網絡被調用,它在遠端機器上運行,結果通過網絡返回調用者。

在此采用第一種形式建立COM服務程序,這也是最常用的形式,DirectX就是采用這種形式提供的。

C++ Builder建立COM服務程序的方法如下:

2.1創建支持COM接口對象的動態連接庫文件:

◆打開File/New/ActiveX項目頁,選擇ActiveX Library;

◆選擇Save All 將項目以PCOMServer文件名保存;此時C++ Builder 自動生成如下的文件:

PCOMServer.bpr:工程的項目文件;

PCOMServer.h,PCOMServer.cpp:支持COM對象的動態連接庫源文件,其中有許多函數用於COM接口對象的自動裝配,大家不用去編輯它們;

PCOMServer_ATL.h,PCOMServer_ATL.cpp:ATL形式的文件供C++ Builder編譯器調用,大家也不要去編輯它們。

◆打開Project/Options/Linker 屬性頁不選中Use dynamic RTL選項,打開Project/Options/Packages屬性頁不選中Builder with runtime packages選項,這兩步操作可以使開發的COM動態連接庫不依賴C++ Builder的VCL動態連接庫,有利於獨立發行,但在一般情況下還是建議選中這兩項。

2.2建立COM接口對象

打開File/New/ActiveX屬性頁,選擇Automation Object表示向服務程序中插入一個自動類型的COM對象,我們選擇這種類型的COM對象是為了可以自動注冊,並且自動支持可以被其他語言調用。此時出現如下的對話框,輸入COM類的名字MyCOM即可,對話框中的其它選項用於規定COM對象的性質,可查看幫助信息。

2.3通過類型庫編輯器編輯COM對象中相應接口對象的屬性和方法

此時自動進入類型庫編輯器,類型庫用於存儲COM對象的說明,是一個可以被多種語言調用的頭文件包。在類型庫中,可以定義COM對象的接口,定義接口對象的屬性和方法等。類型庫編輯器如下所示:

可以看出此時自動產生了MyCOM類的一個接口類IMyCOM,在COM應用軟件中我們實際上是與接口對象打交道,下面通過類型庫編輯器為IMyCOM接口定義方法和屬性。

◆單擊編輯器頂部的Method按鈕;

◆在Arributes頁面的Name字段中輸入方法的名稱,本例中是AddInt用於整數加法;

◆在Parameters頁面中,單擊Add按鈕編輯方法中的參數;

x和y是輸入的兩個整數,ret用於返回運算的結果,必須定義為指針型

◆切換到Flags頁面,可以對接口的屬性作調整;

◆在Text頁面中可以檢查生成的IDL代碼:

[id(0x00000001)]

HRESULT _stdcall AddInt([in] int x, [in] int y, [out, retval] int * ret );

◆單擊Refresh按鈕,此時可以關閉類型庫編輯器。當需要為接口添加新的屬性和方法時,可以通過View/Type Library重新打開編輯器。選擇Save All用C++ Builder提供的缺省文件名保存類型庫的相關文件如下:

PCOMServer.TLB: 類型庫文件;

PCOMServer_TLB.cpp:包含COM接口和對象的說明,其主要目的是方便訪問,在客戶程序中需將本文件包含到客戶程序的工程中;

PCOMServer_TLB.h: PCOMServer_TLB.cpp的頭文件,通過#include引入到客戶程序中。

MyCOMImpl.cpp: 該文件是我們需要編寫程序代碼的地方,實現類型庫定義的接口對象的方法和屬性;

MyCOMImpl.h: MyCOMImpl.cpp的頭文件。

2.4 實現COM接口中的方法

打開MyCOMImpl.cpp文件會發現我們在類型庫編輯器中定義的方法,為該方法編寫代碼如下:

STDMETHODIMP TMyCOMImpl::AddInt(int x, int y, int* ret)
{
*ret=x+y;
return S_OK;
}

2.5 生成DLL文件並注冊COM對象

◆選擇Project/Builder PCOMServer 生成PCOMServer.DLL文件。

◆打開類型庫編輯器,單擊Register按鈕完成對COM對象的注冊。

通過Windows任務欄中的Run菜單運行REGEDIT程序,在Windows注冊表的HKEY_CLASSES_ROOT鍵下查找到PCOMServer.MyCOM子鍵,PCOMServer為DLL文件的名字,MyCOM為COM對象的名字,在下面可以看到該COM對象的全局唯一描述符CLSID如下:

{59834F03-49F1-11D3-B85B-00E09804A418}

注意:不同的機器生成的描述符不同.

在HKEY_CLASSES_ROOT鍵下查找到CLSID子鍵,在它下面找{59834F03-49F1-11D3-B85B-00E09804A418}子鍵,下面有如下的條目:

InprocServer32:存儲PCOMServer.DLL的路徑目錄;

ProgID:存儲COM對象的注冊名:PCOMServer.MyCOM;

Typelib:存儲COM對象的CLSID值{59834F03-49F1-11D3-B85B-00E09804A418}。

COM對象就是通過在注冊表中的紀錄實現DLL與客戶程序的自動連接。

3.建立COM客戶程序

客戶程序將訪問PCOMServer.DLL服務程序中的MyCOM對象,這些對象的說明保存在前面所述的TLB文件中。我們可以直接將PCOMServer_TLB.cpp加入到客戶程序的項目文件中,並在客戶程序中引用PCOMServer_TLB.h文件;也可以通過Project/Import Type Library引用PCOMServer_TLB.TLB文件,重新生成.cpp和.h文件,自動完成上述過程。---www.bianceng.cn。

客戶程序的編程重點是實現對服務程序中COM對象的方法的調用,調用的方法有多種,都是通過所謂的代理接口來完成的,這些代理接口在PCOMServer_TLB.h中有詳細的定義,從這些定義中可以看出這些代理接口調用對象方法的過程。

PCOMServer_TLB.h文件很重要,包含了調用MyCOM對象的各種接口信息,該文件主要內容如下:

// Type Lib: D:\CAI\com\PCOMServer.tlb
// IID\LCID: {5BD378E5-4B57-11D3-B85B-00E09804A418}\0
// Helpfile:
// DepndLst:
// (1) v2.0 stdole, (C:\WINDOWS\SYSTEM\STDOLE2.TLB)
// (2) v4.0 StdVCL, (C:\WINDOWS\SYSTEM\STDVCL40.DLL)
// ************************************************************************
#ifndef __PCOMServer_TLB_h__
#define __PCOMServer_TLB_h__
#pragma option push -b -w-inl
#include <vcl/utilcls.h>
#if !defined(__UTILCLS_H_VERSION) || (__UTILCLS_H_VERSION < 0x0101)
#error "This file requires an newer version of the header file UTILCLS.H"
#endif
#include <olectl.h>
#include <ocidl.h>
#if defined(USING_ATLVCL) || defined(USING_ATL)
#if !defined(__TLB_NO_EVENT_WRAPPERS)
#include <atl/atlmod.h>
#endif
#endif
namespace Stdvcl {class IStrings; class IStringsDisp;}
using namespace Stdvcl;
namespace Pcomserver_tlb
{
DEFINE_GUID(LIBID_PCOMServer, 0x5BD378E5, 0x4B57, 0x11D3, 0xB8, 0x5B, 0x00, 0xE0, 0x98, 0x04, 0xA4, 0x18);
DEFINE_GUID(IID_IMyCOM, 0x5BD378E6, 0x4B57, 0x11D3, 0xB8, 0x5B, 0x00, 0xE0, 0x98, 0x04, 0xA4, 0x18);
DEFINE_GUID(CLSID_MyCOM, 0x5BD378E8, 0x4B57, 0x11D3, 0xB8, 0x5B, 0x00, 0xE0, 0x98, 0x04, 0xA4, 0x18);
interface DECLSPEC_UUID("{5BD378E6-4B57-11D3-B85B-00E09804A418}") IMyCOM;
typedef IMyCOM MyCOM;
#define LIBID_OF_MyCOM (&LIBID_PCOMServer)
interface IMyCOM : public IDispatch
  {
  public:
   virtual HRESULT STDMETHODCALLTYPE AddInt(int x/*[in]*/, int y/*[in]*/, int* ret/*[out,retval]*/) = 0; // [1]
   #if !defined(__TLB_NO_INTERFACE_WRAPPERS)
   int __fastcall AddInt(int x/*[in]*/, int y/*[in]*/)
    {
    int ret;
    OLECHECK(this->AddInt(x, y, &ret));
    return ret;
    }
  #endif // __TLB_NO_INTERFACE_WRAPPERS
};
#if !defined(__TLB_NO_INTERFACE_WRAPPERS)
template <class T /* IMyCOM */ >
class TCOMIMyCOMT : public TComInterface<IMyCOM>, public TComInterfaceBase<IUnknown>
  {
  public:
   TCOMIMyCOMT() {}
   TCOMIMyCOMT(IMyCOM *intf, bool addRef = false) : TComInterface<IMyCOM>(intf, addRef) {}
   TCOMIMyCOMT(const TCOMIMyCOMT& src) : TComInterface<IMyCOM>(src) {}
   TCOMIMyCOMT& operator=(const TCOMIMyCOMT& src) { Bind(src, true); return *this;}
   HRESULT __fastcall AddInt(int x/*[in]*/, int y/*[in]*/, int* ret/*[out,retval]*/);
   int __fastcall AddInt(int x/*[in]*/, int y/*[in]*/);
  };
typedef TCOMIMyCOMT<IMyCOM> TCOMIMyCOM;
template<class T>
class IMyCOMDispT : public TAutoDriver<IMyCOM>
  {
  public:
   IMyCOMDispT(){}
   IMyCOMDispT(IMyCOM *pintf)
    {
    TAutoDriver<IMyCOM>::Bind(pintf);
    }
   IMyCOMDispT& operator=(IMyCOM *pintf)
    {
    TAutoDriver<IMyCOM>::Bind(pintf);
    return *this;
    }
   HRESULT BindDefault(/*Binds to new instance of CoClass MyCOM*/)
    {
    return OLECHECK(Bind(CLSID_MyCOM));
    }
   HRESULT BindRunning(/*Binds to a running instance of CoClass MyCOM*/)
    {
    return BindToActive(CLSID_MyCOM);
    }
   HRESULT __fastcall AddInt(int x/*[in]*/, int y/*[in]*/, int* ret/*[out,retval]*/);
   int __fastcall AddInt(int x/*[in]*/, int y/*[in]*/);
  };
typedef IMyCOMDispT<IMyCOM> IMyCOMDisp;
template <class T> HRESULT __fastcall
TCOMIMyCOMT<T>::AddInt(int x/*[in]*/, int y/*[in]*/, int* ret/*[out,retval]*/)
  {
  return (*this)->AddInt(x, y, ret);
  }
template <class T> int __fastcall
TCOMIMyCOMT<T>::AddInt(int x/*[in]*/, int y/*[in]*/)
  {
  int ret;
  OLECHECK(this->AddInt(x, y, &ret));
  return ret;
  }
template <class T> HRESULT __fastcall
IMyCOMDispT<T>::AddInt(int x/*[in]*/, int y/*[in]*/, int* ret/*[out,retval]*/)
  {
  static _TDispID _dispid(*this, OLETEXT("AddInt"), DISPID(1));
  TAutoArgs<2> _args;
  _args[1] = x /*[VT_INT:0]*/;
  _args[2] = y /*[VT_INT:0]*/;
  return OutRetValSetterPtr(ret /*[VT_INT:1]*/, _args, OleFunction(_dispid, _args));
  }
template <class T> int __fastcall
IMyCOMDispT<T>::AddInt(int x/*[in]*/, int y/*[in]*/)
  {
  int ret;
  this->AddInt(x, y, &ret);
  return ret;
  }
typedef TCoClassCreatorT<TCOMIMyCOM, IMyCOM, &CLSID_MyCOM, &IID_IMyCOM> CoMyCOM;
#endif // __TLB_NO_INTERFACE_WRAPPERS
}; // namespace Pcomserver_tlb
#if !defined(NO_IMPLICIT_NAMESPACE_USE)
using namespace Pcomserver_tlb;
#endif
#pragma option pop
#endif // __PCOMServer_TLB_h__

下面是文件中說明的主要對象及其定義:

interface IMyCOM : public IDispatch
class TCOMIMyCOMT : public TComInterface<IMyCOM>
class IMyCOMDispT : public TAutoDriver<IMyCOM>
class CoMyCOM: public CoClassCreator

◆IMyCOM:通過IDispatch接口來調用對象的方法,該接口可以使對象被不支持虛擬函數表(VTable)的語言(如Visual Basic)說調用。這是一種很慢很苯的接口調用方式。

◆TCOMIMyCOMT:通過所謂的智能接口來調用對象的方法,既可以實現IDispatch調用,也可以采用VTable進行調用,從而實現最快的調用速度。

◆IMyCOMDispT:通過disp接口來調用對象的方法,可以提高Idispatch接口的訪問速度,但還是比不上VTable接口。

◆CoMyCOM:通過使用CoClassCreator可以自動產生TCOMIMyCOM代理的實例。

下面介紹一下實現智能接口和Disp接口調用的客戶程序。這個客戶程序很簡單,有兩個按鈕分別完成兩種接口調用的方法,一個編輯框顯示結果。

◆智能接口的VTable調用方法如下:

int x=6,y=6;
TCOMIMyCOM O;
O=CoMyCOM::Create(); //通過CoClassCreator完成初始化
O->AddInt(x,y,&y); //Vtable形式調用
Edit1->Text=IntToStr(y);

◆DISP接口的調用方法如下:

int x=6,y=6;
IMyCOMDisp app;
app.BindDefault(); //通過Bind完成初始化
app.AddInt(x,y,&y); //Disp形式調用
Edit1->Text=IntToStr(y);

4.小結

上面的程序舉例是很簡單的,但卻詳細說明了COM應用軟件的開發過程。COM技術不是一個編程語言,而是一種編程規范和方法。采用COM技術可以開發功能強大的軟件,有利於分布式應用技術的實現,有利於多人合作開發,也可以幫助我們理解Windows系統本身。COM的接口技術是比較復雜的,想進一步了解COM技術可參閱清華大學出版社的《COM技術內幕》一書。

C++ Builder是開發COM應用軟件的好工具,它隱含了COM實現的細節,我們只需與它打交道就可以開發完善和強大的COM應用程序。希望有更多的人轉到COM應用軟件的開發上來,COM技術是軟件技術未來的發展方向,是實現軟件工程中軟件即插即用的有效途徑。

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