C++ Programmer's Cookbook

{C++ 基础} {C++ 高级} {C#界面,C++核心算法} {设计模式} {C#基础}

C++ COM

 

前言:为了介绍C#写界面,C++写算法的快捷交互开发方式,首先介绍c++,C#内部的DLL,COM调用。

一,COM
COM (Component Object Model),微软为提高代码的可从用性而开发的组件对象模型的软件架构,在windows系统的开发中大量的使用了这种技术,使用这种技术我们尽可能的把我们的软件划分位许多组件,通过组件的组合调用最总实现软件的目的,COM的使用不仅大大的提高了代码的可从用性,而且减小了代码间的耦合。更多的关于OLE,COM,COM+,DCOM,ActiveX的概念

二,COM的创建
一般COM的创建有2中方法,使用ATL Wizard 和不使用ATL。

1)使用ATL Wizard创建COM非常的简单,可以参考下面2个链接,分别使用ATL6.0和ATL7.0,最新的类似:
     ATL7.0 COM: http://www.codeproject.com/atl/SimpleDlls.asp
     ATL6.0 com: http://www.codeproject.com/atl/com_atl.asp

2)不使用ATL,当然更没有Wizard,只有手动的一步一步的实现,不过这也是学习COM最好的方法和必经之路。可以参考:
     http://www.codeproject.com/com/LocalCOMServerClient.asp
    具体的步骤如下:
    1)建立一个Win32的DLL。例如CarLocalServer。
    2)首先加入IDL接口描述文件CarLocalServerTypeInfo.idl,编译后会生成4个文件CarLocalServerTypeInfo_h.h,CarLocalServerTypeInfo_i.cpp,CarLocalServerTypeInfo_p.cpp,      dlldata.cpp 。
    

import "oaidl.idl";
import 
"ocidl.idl";

// define IStats interface
[
object, uuid(FE78387F-D150-4089-832C-BBF02402C872),
 oleautomation, helpstring(
"Get the status information about this car")]
interface IStats : IUnknown
{
   HRESULT DisplayStats();
   HRESULT GetPetName([out,retval] BSTR
* petName);
};

// define the IEngine interface
[
object, uuid(E27972D8-717F-4516-A82D-B688DC70170C),
 oleautomation, helpstring(
"Rev your car and slow it down")]
interface IEngine : IUnknown
{
   HRESULT SpeedUp();
   HRESULT GetMaxSpeed([out,retval] 
int* maxSpeed);
   HRESULT GetCurSpeed([out,retval] 
int* curSpeed);
};

// define the ICreateMyCar interface
[
object, uuid(5DD52389-B1A4-4fe7-B131-0F8EF73DD175),
 oleautomation, helpstring(
"This lets you create a car object")]
interface ICreateMyCar : IUnknown
{
   HRESULT SetPetName([in]BSTR petName);
   HRESULT SetMaxSpeed([in] 
int maxSp);
};

// library statement
[uuid(957BF83F
-EE5A-42eb-8CE5-6267011F0EF9), version(1.0),
 helpstring(
"Car server with typeLib")]
library CarLocalServerLib
{
   importlib(
"stdole32.tlb");
   [uuid(1D66CBA8
-CCE2-4439-8596-82B47AA44E43)]
   coclass MyCar
   {
      [default] interface ICreateMyCar;
      interface IStats;
      interface IEngine;
   };
};


    3) 加入生命周期管理类managesycle.h
 

#pragma once

class CManageSycle
{
public:
    CManageSycle(void);
    ~CManageSycle(void);

    static void InObject()  {
++m_nObject;}
    static void DeObject()  {
--m_nObject;}
    static bool IsZeroObject() { return m_nObject
==0 ;}
    static void InLock()  {
++m_nLock;}
    static void DeLock()  {
--m_nLock;}
    static bool IsZeroLock() { return m_nLock
==0 ; }
private:
    static ULONG m_nObject;
    static ULONG m_nLock;
};

managesycle.cpp实现文件:

#include "StdAfx.h"
#include 
"managesycle.h"
ULONG CManageSycle::m_nObject
=0;
ULONG CManageSycle::m_nLock
=0;
CManageSycle::CManageSycle(void)
{
}

CManageSycle::~CManageSycle(void)
{
}


    4)加入真正的com的接口的实现类,MyCar.h (除了实现COM的接口,还必须实现IUnKnown接口)


#pragma once

#include 
"unknwn.h"
#include 
"CarLocalServerTypeInfo_h.h"

const int MAX_SPEED = 500;
const int MAX_NAME_LENGTH = 20;

class MyCar : 
    
public IEngine, 
    
public ICreateMyCar, 
    
public IStats  
{
public:
    MyCar();
    virtual ~MyCar();

    
// IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void
** pIFace);
    STDMETHODIMP_(DWORD)AddRef();
    STDMETHODIMP_(DWORD)Release();

    
// IEngine
    STDMETHODIMP SpeedUp();
    STDMETHODIMP GetMaxSpeed(
int* maxSpeed);
    STDMETHODIMP GetCurSpeed(
int* curSpeed);
    
    
// IStats
    STDMETHODIMP DisplayStats();
    STDMETHODIMP GetPetName(BSTR
* petName);

    
// ICreateMyCar
    STDMETHODIMP SetPetName(BSTR petName);
    STDMETHODIMP SetMaxSpeed(
int maxSp);

private:
    DWORD    m_refCount;
    BSTR    m_petName;
    
int        m_maxSpeed;
    
int        m_currSpeed;
};



MyCar.cpp

#include "stdafx.h"
#include 
<stdio.h> 

#include 
"CarLocalServerTypeInfo_i.c"
#include 
"MyCar.h"
#include 
"ManageSycle.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
MyCar::MyCar() : m_refCount(
0), m_currSpeed(0), m_maxSpeed(0)
{
    m_refCount
=0;
    CManageSycle::InObject();
    m_petName 
= SysAllocString(L"Default Pet Name");
    
}

MyCar::~MyCar()
{
    CManageSycle::DeObject();
    
if(m_petName) SysFreeString(m_petName);
    MessageBox(
NULL,
    L
"MyCar is being distructed. Make sure you see this message, if not, you might have memory leak!",
    L
"Destructor",MB_OK | MB_SETFOREGROUND);
}

// IUnknown
STDMETHODIMP MyCar::QueryInterface(REFIID riid, void
** pIFace)
{
    
// Which aspect of me do they want?
    
if(riid == IID_IUnknown)
    {
        
*pIFace = (IUnknown*)(IEngine*)this;
        
// MessageBox(NULL"Handed out IUnknown","QI",MB_OK | MB_SETFOREGROUND);
    }
    
    
else if(riid == IID_IEngine)
    {
        
*pIFace = (IEngine*)this;
        
// MessageBox(NULL"Handed out IEngine","QI",MB_OK | MB_SETFOREGROUND);
    }
    
    
else if(riid == IID_IStats)
    {
        
*pIFace = (IStats*)this;
        
// MessageBox(NULL"Handed out IStats","QI",MB_OK | MB_SETFOREGROUND);
    }
    
    
else if(riid == IID_ICreateMyCar)
    {
        
*pIFace = (ICreateMyCar*)this;
        
// MessageBox(NULL"Handed out ICreateMyCar","QI",MB_OK | MB_SETFOREGROUND);
    }
    
else
    {
        
*pIFace = NULL;
        return E_NOINTERFACE;
    }

    ((IUnknown
*)(*pIFace))->AddRef();
    return S_OK;
}

STDMETHODIMP_(DWORD) MyCar::AddRef()
{
    
++m_refCount;
    return m_refCount;
}

STDMETHODIMP_(DWORD) MyCar::Release()
{
    
if(--m_refCount == 0)
    {
        delete this;
        return 
0;
    }
    
else
        return m_refCount;
}

// IEngine
STDMETHODIMP MyCar::SpeedUp()
{
    m_currSpeed 
+= 10;
    return S_OK;
}

STDMETHODIMP MyCar::GetMaxSpeed(
int* maxSpeed)
{
    
*maxSpeed = m_maxSpeed;
    return S_OK;
}

STDMETHODIMP MyCar::GetCurSpeed(
int* curSpeed)
{
    
*curSpeed = m_currSpeed;
    return S_OK;
}


// IStats
STDMETHODIMP MyCar::DisplayStats()
{
    
// Need to transfer a BSTR to a char array.
    char buff[MAX_NAME_LENGTH];
    WideCharToMultiByte(CP_ACP, 
NULL, m_petName, -1, buff, 
                        MAX_NAME_LENGTH, 
NULLNULL);

    
//MessageBox(NULL, buff, L"Pet Name",MB_OK | MB_SETFOREGROUND);
    memset(buff, 
0, sizeof(buff));
    sprintf(buff, 
"%d", m_maxSpeed);
    
//MessageBox(NULL, buff,L"Max Speed", MB_OK| MB_SETFOREGROUND);
    return S_OK;
}

STDMETHODIMP MyCar::GetPetName(BSTR
* petName)
{
    
*petName = SysAllocString(m_petName);
    return S_OK;
}


// ICreateMyCar
STDMETHODIMP MyCar::SetPetName(BSTR petName)
{
    SysReAllocString(
&m_petName, petName);
    return S_OK;
}

STDMETHODIMP MyCar::SetMaxSpeed(
int maxSp)
{
    
if(maxSp < MAX_SPEED)
        m_maxSpeed 
= maxSp;
    return S_OK;
}


    5)加入工厂类MyCarClassFactory.h,(实现IUnKnown接口和IClassFactory接口)

// the class object (class factory) for CoMyCar class

#pragma once

class MyCarClassFactory : 
public IClassFactory
{
public:
    MyCarClassFactory();
    virtual ~MyCarClassFactory();

    
// IUnknown
    STDMETHODIMP QueryInterface(REFIID riid,void
** pIFace);
    STDMETHODIMP_(ULONG)AddRef();
    STDMETHODIMP_(ULONG)Release();

    
// IClassFactory
    STDMETHODIMP LockServer(BOOL fLock);
    STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter,REFIID riid,void
** ppv);

private:

    ULONG m_refCount;

};

MyCarClassFactory.cpp

#include "stdafx.h"
#include 
"MyCar.h"
#include 
"MyCarClassFactory.h"
#include 
"locks.h"
#include 
"ManageSycle.h"

MyCarClassFactory::MyCarClassFactory()
{
    m_refCount 
= 0;
}

MyCarClassFactory::~MyCarClassFactory()
{
    MessageBox(
NULL,
    L
"MyCarClassFactory is being distructed. Make sure you see this message, if not, you might have memory leak!",
    L
"Destructor",MB_OK | MB_SETFOREGROUND);
}

STDMETHODIMP_(ULONG) MyCarClassFactory::AddRef()
{
    
//return ++m_refCount;
    return 
10;
}

STDMETHODIMP_(ULONG) MyCarClassFactory::Release()
{
    
/*
    
if ( --m_refCount == 0 )
    {
        delete this;
        return 
0;
    }
    return m_refCount;
    
*/
    return 
20;
}

STDMETHODIMP MyCarClassFactory::QueryInterface(REFIID riid,void
** pIFace)
{
    
if ( riid == IID_IUnknown )
        
*pIFace = (IUnknown*)this;
    
else if ( riid == IID_IClassFactory )
        
*pIFace = (IClassFactory*)this;
    
else
    {
        
*pIFace = NULL;
        return E_NOINTERFACE;
    }
    ((IUnknown
*)(*pIFace))->AddRef();
    return S_OK;
}

STDMETHODIMP MyCarClassFactory::LockServer(BOOL fLock)
{
    (VARIANT_TRUE 
== fLock) ? CManageSycle::InLock() : CManageSycle::DeLock();
    return S_OK;
}

STDMETHODIMP MyCarClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,REFIID riid,void
** ppv)
{
    
if ( pUnkOuter != NULL ) return CLASS_E_NOAGGREGATION;

    MyCar
* pMyCarObj = NULL;
    HRESULT hr;

    pMyCarObj 
= new MyCar();
    hr 
= pMyCarObj->QueryInterface(riid,ppv);

    
if ( FAILED(hr) ) delete pMyCarObj;
    return hr;
}


    6)实现COM入口和自注册函数CarLocalServer.cpp,

// CarLocalServer.cpp : Defines the entry point for the DLL application.
//

#include 
"stdafx.h"
#include 
<iostream>    
#include 
<string.h>

#include 
"CarLocalServerTypeInfo_h.h"
//#include "CarLocalServerTypeInfo_i.c"
#include 
"MyCarClassFactory.h"    
#include 
"ManageSycle.h"

using namespace std;

#ifdef _MANAGED
#pragma managed(push, off)
#endif

HMODULE g_hmodule;

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    g_hmodule 
= static_cast<HMODULE>(hModule);
    return 
TRUE;
}


STDAPI DllCanUnloadNow()
{
    bool bDllCanUnloadNow 
= CManageSycle::IsZeroObject() && CManageSycle::IsZeroLock();
    return bDllCanUnloadNow ? S_OK : S_FALSE;
}

STDAPI DllGetClassObject(REFCLSID rclsid,REFIID riid,LPVOID
* ppv)
{
    
*ppv = NULL;
    
if(__uuidof(MyCar) != rclsid)
    {
        return E_NOINTERFACE;
    }

    MyCarClassFactory
* pBirdFactory = new MyCarClassFactory();
    
if(NULL == pBirdFactory)
    {
        return E_OUTOFMEMORY;
    }

    HRESULT hr 
= pBirdFactory->QueryInterface(riid,ppv);
    
if(FAILED(hr))
    {
        delete pBirdFactory;
    }
    return hr;
}

STDAPI DllRegisterServer()
{
    HKEY hRoot, hNew;
    ::RegOpenKey(HKEY_CLASSES_ROOT,L
"CLSID",&hRoot);
    ::RegCreateKey(hRoot,L
"{1F0A9759-FCBE-4870-8336-971BD19A7452}\\InprocServer32",&hNew);
    wchar_t strFile[MAX_PATH];
    ::GetModuleFileName(g_hmodule,strFile,MAX_PATH);
    ::RegSetValue(hNew,
NULL,REG_SZ,strFile,MAX_PATH);
    ::RegCloseKey(hRoot);
    return S_OK;
}

STDAPI DllUnregisterServer()
{
    HKEY hRoot;
    ::RegOpenKey(HKEY_CLASSES_ROOT,L
"CLSID",&hRoot);
    ::RegDeleteKey(hRoot,L
"{1F0A9759-FCBE-4870-8336-971BD19A7452}");
    ::RegDeleteKey(hRoot,L
"{1F0A9759-FCBE-4870-8336-971BD19A7452}\\InprocServer32");
    ::RegCloseKey(hRoot);
    return S_OK;
}
#ifdef _MANAGED
#pragma managed(pop)
#endif


    7)增加def到处文件CarLocalServer.def,

LIBRARY    "CarLocalServer"

EXPORTS
    DllCanUnloadNow                
PRIVATE
    DllGetClassObject               
PRIVATE
    DllRegisterServer               
PRIVATE
    DllUnregisterServer            
PRIVATE


  

三,COM的调用过程
通过一个创建COM组件的最小框架结构,然后看一看其内部处理流程是怎样的

    IUnknown *pUnk=NULL;
    IObject *pObject=NULL;
    CoInitialize(NULL);
    CoCreateInstance(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, (void**)&pUnk);
    pUnk->QueryInterface(IID_IOjbect, (void**)&pObject);
    pUnk->Release();
    pObject->Func();
    pObject->Release();
    CoUninitialize();

这就是一个典型的创建COM组件的框架,不过我的兴趣在CoCreateInstance身上,让我们来看看它内部做了一些什么事情。
以下是它内部实现的一个伪代码
:

    CoCreateInstance(....)
    {
    .......
    IClassFactory *pClassFactory=NULL;
    CoGetClassObject(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pClassFactory);
    pClassFactory->CreateInstance(NULL, IID_IUnknown, (void**)&pUnk);
    pClassFactory->Release();
    ........
   }

这段话的意思就是先得到类厂对象,再通过类厂创建组件从而得到IUnknown指针。继续深入一步,看看CoGetClassObject的内部伪码:

   CoGetClassObject(.....)
   {
    //通过查注册表CLSID_Object,得知组件DLL的位置、文件名
    //装入DLL
    //使用函数GetProcAddress(...)得到DLL库中函数DllGetClassObject的函数指针。
    //调用DllGetClassObject
   }
DllGetClassObject是干什么的,它是用来获得类厂对象的。只有先得到类厂才能去创建组件.
    下面是DllGetClassObject的伪码:
    DllGetClassObject(...)
    {
    ......
    CFactory* pFactory= new CFactory; //类厂对象
    pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory);
    //查询IClassFactory指针
    pFactory->Release();
    ......
    }
    CoGetClassObject的流程已经到此为止,现在返回CoCreateInstance,看看CreateInstance的伪码:
    CFactory::CreateInstance(.....)
    {
    ...........
    CObject *pObject = new CObject; //组件对象
    pObject->QueryInterface(IID_IUnknown, (void**)&pUnk);
    pObject->Release();
    ...........
    }


四,COM的调用方法
对上面手动创建的COM的调用实例:

int main()
{
    
// initialize the COM runtime
    cout 
<< "Initialize the COM runtime";
    CoInitialize(
NULL);
    cout 
<< "success." << endl;

    
// declare variables
    HRESULT hr;
    IClassFactory
* pICF = NULL;
    ICreateMyCar
*  pICreateMyCar = NULL;
    IEngine
*       pIEngine = NULL;
    IStats
*        pIStats = NULL;

    cout 
<< endl << "Get the class factory interface for the Car class";
    hr 
= CoGetClassObject(CLSID_MyCar,CLSCTX_LOCAL_SERVER,NULL,IID_IClassFactory,(void**)&pICF);
    
if ( FAILED(hr) )
    {
        cout
<<"fail";
        
exit(1);
    }
    
else cout << "success." << endl;
    
    cout 
<< "Create the Car object and get back the ICreateMyCar interface";
    hr 
= pICF->CreateInstance(NULL,IID_ICreateMyCar,(void**)&pICreateMyCar);
    
if ( FAILED(hr) )
    {
        
//ShowErrorMessage("CoGetClassObject()",hr);
        
exit(1);
    }
    
else cout << "success." << endl;
    
    
// set parameters on the car
    cout 
<< endl << "Set different parameters on the car";
    pICreateMyCar
->SetMaxSpeed(30);
    BSTR carName 
= SysAllocString(OLESTR("COMCar?!"));
    pICreateMyCar
->SetPetName(carName);
    SysFreeString(carName);
    cout 
<< "success." << endl;

    cout 
<< endl << "Query the IStats interface";
    pICreateMyCar
->QueryInterface(IID_IStats,(void**)&pIStats);
    cout 
<< "success." << endl;

    cout 
<< endl << "Use the IStats interface to display the status of the car:" << endl;
    pIStats
->DisplayStats();

    cout 
<< endl << "Query the IEngine interface";
    pICreateMyCar
->QueryInterface(IID_IEngine,(void**)&pIEngine);
    cout 
<< "success." << endl;

    cout 
<< endl << "Start to use the engine" << endl;
    
int curSp = 0;
    
int maxSp = 0;
    pIEngine
->GetMaxSpeed(&maxSp);
    
do
    {
        pIEngine
->SpeedUp();
        pIEngine
->GetCurSpeed(&curSp);
        cout 
<< "current speed is: " << curSp << endl;
    } 
while (curSp <= maxSp);

    cout 
<< endl << "Report status again: " << endl;
    pIStats
->DisplayStats();

    
if ( pICF )         pICF->Release();
    
if ( pICreateMyCar) pICreateMyCar->Release();
    
if ( pIStats )      pIStats->Release();
    
if ( pIEngine )     pIEngine->Release();

    cout 
<< endl << "Close the COM runtime";
    CoUninitialize();
    cout 
<< "success." << endl;

    return 
0;
}


方法一:向上面的调用,通过包含#include "../CarLocalServer/CarLocalServerTypeInfo_h.h"和#include "../CarLocalServer/CarLocalServerTypeInfo_i.c"

方法二:使用#import导入tlb

可以使用: CComBSTR ,CComPtr<> 和 CComQIPtr<> 等来简化调用。

五,总结
COM比一般的DLL有很多的优点,COM没有重名问题,因为根本不是通过函数名来调用函数,而是通过虚函数表,自然也不会有函数名修饰的问题。路径问题也不复存在,因为是通过查注册表来找组件的,放在什么地方都可以,即使在别的机器上也可以。也不用考虑和EXE的依赖关系了,它们二者之间是松散的结合在一起,可以轻松的换上组件的一个新版本,而应用程序混然不觉。

但是COM仍然是有问题的,比如说版本控制的问题,.NET将逐步代替COM的使用。

六,参考
1)OLE/COM/COM+/DCOM/ActiveX/ActiveX contorl ( 概念)
2)用VC进行COM编程所必须掌握的理论知识
3)COM编程入门(1)
4)COM编程入门(2)
5)c++中使用com的方法

posted on 2007-05-28 19:45 梦在天涯 阅读(8065) 评论(5)  编辑 收藏 引用 所属分类: CPlusPlus

评论

# re: C++ COM 2007-05-28 21:39 梦在天涯

以上的手动创建COM实例,还有一定的问题,有那位高手愿意指点,使我改正,共同进步,还有COM的其他的调用方法,还有文章中的其他的问题,请帮忙指出,我将非常的感谢!

勇于共享!  回复  更多评论   

# re: C++ COM 2007-05-28 22:32 万连文

其实代码逻辑可以用C++抽象机制+设计模式去实现,而com的重点在夸语言平台,把它作为一种interop手段是再好不过。在开发后期如果设计得当,用com做一层wrapper是很容易的事情。  回复  更多评论   

# re: C++ COM 2007-05-29 00:15 meet-dream

其实增加一层wrapper也是一件很ugly的事,如果效率是可以接受的,建议直接在对象模型上去实现。在wrapper同样会把效率降的更低。  回复  更多评论   

# re: C++ COM 2007-06-22 12:26 .net编程

学习中  回复  更多评论   

# re: C++ COM 2014-02-10 15:49 sunnyes2008

MARK  回复  更多评论   


只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


公告

EMail:itech001#126.com

导航

统计

  • 随笔 - 461
  • 文章 - 4
  • 评论 - 746
  • 引用 - 0

常用链接

随笔分类

随笔档案

收藏夹

Blogs

c#(csharp)

C++(cpp)

Enlish

Forums(bbs)

My self

Often go

Useful Webs

Xml/Uml/html

搜索

  •  

积分与排名

  • 积分 - 1626423
  • 排名 - 5

最新评论

阅读排行榜